diff --git a/manifest.json b/manifest.json index c75cdf27..7610bd80 100644 --- a/manifest.json +++ b/manifest.json @@ -26,12 +26,7 @@ ], "workspace_templates": [ {"name": "claude-code-default", "repo": "Molecule-AI/molecule-ai-workspace-template-claude-code", "ref": "main"}, - {"name": "langgraph", "repo": "Molecule-AI/molecule-ai-workspace-template-langgraph", "ref": "main"}, - {"name": "crewai", "repo": "Molecule-AI/molecule-ai-workspace-template-crewai", "ref": "main"}, - {"name": "autogen", "repo": "Molecule-AI/molecule-ai-workspace-template-autogen", "ref": "main"}, - {"name": "deepagents", "repo": "Molecule-AI/molecule-ai-workspace-template-deepagents", "ref": "main"}, {"name": "hermes", "repo": "Molecule-AI/molecule-ai-workspace-template-hermes", "ref": "main"}, - {"name": "gemini-cli", "repo": "Molecule-AI/molecule-ai-workspace-template-gemini-cli", "ref": "main"}, {"name": "openclaw", "repo": "Molecule-AI/molecule-ai-workspace-template-openclaw", "ref": "main"}, {"name": "codex", "repo": "Molecule-AI/molecule-ai-workspace-template-codex", "ref": "main"} ], diff --git a/workspace-server/internal/handlers/restart_template_test.go b/workspace-server/internal/handlers/restart_template_test.go index 54c9d323..41fe09b6 100644 --- a/workspace-server/internal/handlers/restart_template_test.go +++ b/workspace-server/internal/handlers/restart_template_test.go @@ -94,12 +94,12 @@ func TestResolveRestartTemplate_ApplyTemplate_NameMatch(t *testing.T) { // the restart handler needs to lay down the new runtime's base files // via `-default/`. Matches the existing behaviour comment. func TestResolveRestartTemplate_ApplyTemplate_RuntimeDefault(t *testing.T) { - root := newTemplateDir(t, "langgraph-default") + root := newTemplateDir(t, "hermes-default") - path, label := resolveRestartTemplate(root, "Some Workspace", "langgraph", restartTemplateInput{ + path, label := resolveRestartTemplate(root, "Some Workspace", "hermes", restartTemplateInput{ ApplyTemplate: true, }) - if path == "" || label != "langgraph-default" { + if path == "" || label != "hermes-default" { t.Errorf("apply_template + dbRuntime should resolve runtime-default; got path=%q label=%q", path, label) } } @@ -227,17 +227,18 @@ func TestResolveRestartTemplate_CWE22_TraversalRuntime_FallsThrough(t *testing.T // string in dbRuntime resolves langgraph-default (the safe default) rather // than any attacker-chosen path. The attacker gains no additional access. func TestResolveRestartTemplate_CWE22_TraversalRuntime_CannotOverrideKnownRuntime(t *testing.T) { - root := newTemplateDir(t, "langgraph-default") + root := newTemplateDir(t, "claude-code-default") path, label := resolveRestartTemplate(root, "Some Workspace", "../../../etc", restartTemplateInput{ ApplyTemplate: true, }) - // Must resolve to langgraph-default, not to an escaped path - expected := filepath.Join(root, "langgraph-default") + // Must resolve to claude-code-default (the safe default after sanitizeRuntime), + // not to an escaped path + expected := filepath.Join(root, "claude-code-default") if path != expected { - t.Errorf("traversal runtime must resolve to langgraph-default; got path=%q", path) + t.Errorf("traversal runtime must resolve to claude-code-default; got path=%q", path) } - if label != "langgraph-default" { - t.Errorf("label must be langgraph-default; got %q", label) + if label != "claude-code-default" { + t.Errorf("label must be claude-code-default; got %q", label) } } diff --git a/workspace-server/internal/handlers/runtime_registry.go b/workspace-server/internal/handlers/runtime_registry.go index b5413e15..5d2f4f2d 100644 --- a/workspace-server/internal/handlers/runtime_registry.go +++ b/workspace-server/internal/handlers/runtime_registry.go @@ -73,15 +73,10 @@ type manifestFile struct { // supported in the wild. "external" is always a valid runtime — // manifest or not — because it has no template repo. var fallbackRuntimes = map[string]struct{}{ - "langgraph": {}, "claude-code": {}, - "openclaw": {}, - "crewai": {}, - "autogen": {}, - "deepagents": {}, "hermes": {}, + "openclaw": {}, "codex": {}, - "gemini-cli": {}, "external": {}, } diff --git a/workspace-server/internal/handlers/workspace_provision.go b/workspace-server/internal/handlers/workspace_provision.go index 6339fb43..edaa40c0 100644 --- a/workspace-server/internal/handlers/workspace_provision.go +++ b/workspace-server/internal/handlers/workspace_provision.go @@ -510,13 +510,13 @@ func yamlQuote(s string) string { func sanitizeRuntime(raw string) string { raw = strings.TrimSpace(raw) if raw == "" { - return "langgraph" + return "claude-code" } if _, ok := knownRuntimes[raw]; ok { return raw } - log.Printf("provisioner: rejected unknown runtime %q, falling back to langgraph", raw) - return "langgraph" + log.Printf("provisioner: rejected unknown runtime %q, falling back to claude-code", raw) + return "claude-code" } // ensureDefaultConfig generates minimal config files in memory for workspaces without a template. @@ -562,12 +562,7 @@ func (h *WorkspaceHandler) ensureDefaultConfig(workspaceID string, payload model // and preflight already validates that the env vars are present before // the agent loop starts. Hardcoding token names here caused #1028 // (expired CLAUDE_CODE_OAUTH_TOKEN baked into config.yaml). - switch runtime { - case "langgraph", "deepagents": - // These runtimes read API keys from env directly, no runtime_config needed. - default: - configYAML += "runtime_config:\n timeout: 0\n" - } + configYAML += "runtime_config:\n timeout: 0\n" files["config.yaml"] = []byte(configYAML) diff --git a/workspace-server/internal/handlers/workspace_provision_test.go b/workspace-server/internal/handlers/workspace_provision_test.go index 3610f3be..86bf74fe 100644 --- a/workspace-server/internal/handlers/workspace_provision_test.go +++ b/workspace-server/internal/handlers/workspace_provision_test.go @@ -189,14 +189,14 @@ func TestResolveOrgTemplate_NoMatchInOrgTemplates(t *testing.T) { // ==================== ensureDefaultConfig ==================== -func TestEnsureDefaultConfig_LangGraph(t *testing.T) { +func TestEnsureDefaultConfig_Hermes(t *testing.T) { broadcaster := newTestBroadcaster() handler := NewWorkspaceHandler(broadcaster, nil, "http://localhost:8080", t.TempDir()) payload := models.CreateWorkspacePayload{ Name: "Test Agent", Tier: 1, - Runtime: "langgraph", + Runtime: "hermes", } files := handler.ensureDefaultConfig("ws-test-123", payload) @@ -212,14 +212,14 @@ func TestEnsureDefaultConfig_LangGraph(t *testing.T) { if !contains(content, `name: "Test Agent"`) { t.Errorf("config.yaml missing quoted name, got:\n%s", content) } - if !contains(content, "runtime: langgraph") { + if !contains(content, "runtime: hermes") { t.Errorf("config.yaml missing runtime, got:\n%s", content) } if !contains(content, "tier: 1") { t.Errorf("config.yaml missing tier, got:\n%s", content) } if !contains(content, `model: "anthropic:claude-opus-4-7"`) { - t.Errorf("config.yaml should use default langgraph model, got:\n%s", content) + t.Errorf("config.yaml should use default non-claude model, got:\n%s", content) } } @@ -342,7 +342,7 @@ func TestEnsureDefaultConfig_CrewAIGetsRuntimeConfig(t *testing.T) { } } -func TestEnsureDefaultConfig_EmptyRuntimeDefaultsToLangGraph(t *testing.T) { +func TestEnsureDefaultConfig_EmptyRuntimeDefaultsToClaudeCode(t *testing.T) { broadcaster := newTestBroadcaster() handler := NewWorkspaceHandler(broadcaster, nil, "http://localhost:8080", t.TempDir()) @@ -353,11 +353,11 @@ func TestEnsureDefaultConfig_EmptyRuntimeDefaultsToLangGraph(t *testing.T) { files := handler.ensureDefaultConfig("ws-empty-rt", payload) configYAML := string(files["config.yaml"]) - if !contains(configYAML, "runtime: langgraph") { - t.Errorf("empty runtime should default to langgraph, got:\n%s", configYAML) + if !contains(configYAML, "runtime: claude-code") { + t.Errorf("empty runtime should default to claude-code, got:\n%s", configYAML) } - if !contains(configYAML, `model: "anthropic:claude-opus-4-7"`) { - t.Errorf("langgraph default model should be anthropic (quoted), got:\n%s", configYAML) + if !contains(configYAML, `model: "sonnet"`) { + t.Errorf("claude-code default model should be sonnet (quoted), got:\n%s", configYAML) } } @@ -367,7 +367,7 @@ func TestEnsureDefaultConfig_EmptyNameAndRole(t *testing.T) { payload := models.CreateWorkspacePayload{ Tier: 1, - Runtime: "langgraph", + Runtime: "hermes", } files := handler.ensureDefaultConfig("ws-empty-name", payload) @@ -376,41 +376,11 @@ func TestEnsureDefaultConfig_EmptyNameAndRole(t *testing.T) { if !contains(configYAML, "name: ") { t.Errorf("config.yaml should have name field, got:\n%s", configYAML) } - if !contains(configYAML, "runtime: langgraph") { + if !contains(configYAML, "runtime: hermes") { t.Errorf("config.yaml should have runtime, got:\n%s", configYAML) } } -func TestEnsureDefaultConfig_DeepAgents(t *testing.T) { - broadcaster := newTestBroadcaster() - handler := NewWorkspaceHandler(broadcaster, nil, "http://localhost:8080", t.TempDir()) - - payload := models.CreateWorkspacePayload{ - Name: "Deep Agent", - Tier: 2, - Runtime: "deepagents", - Model: "google_genai:gemini-2.5-flash", - } - - files := handler.ensureDefaultConfig("ws-deep", payload) - - configYAML := string(files["config.yaml"]) - if !contains(configYAML, "runtime: deepagents") { - t.Errorf("config.yaml missing runtime, got:\n%s", configYAML) - } - if !contains(configYAML, `model: "google_genai:gemini-2.5-flash"`) { - t.Errorf("config.yaml should have model at top level (quoted), got:\n%s", configYAML) - } - // deepagents should NOT have runtime_config block - if contains(configYAML, "runtime_config:") { - t.Errorf("config.yaml should NOT have runtime_config for deepagents, got:\n%s", configYAML) - } - // Should NOT have auth token - if _, ok := files[".auth-token"]; ok { - t.Error("deepagents should not get .auth-token") - } -} - func TestEnsureDefaultConfig_ModelAlwaysTopLevel(t *testing.T) { broadcaster := newTestBroadcaster() handler := NewWorkspaceHandler(broadcaster, nil, "http://localhost:8080", t.TempDir()) @@ -458,8 +428,8 @@ func TestEnsureDefaultConfig_RejectsInjectedRuntime(t *testing.T) { t.Errorf("injected initial_prompt key survived as top-level YAML: %+v", parsed) } // Runtime collapsed to default. - if got := parsed["runtime"]; got != "langgraph" { - t.Errorf("runtime = %v, want langgraph (unknown runtime should fall back)", got) + if got := parsed["runtime"]; got != "claude-code" { + t.Errorf("runtime = %v, want claude-code (unknown runtime should fall back)", got) } } @@ -507,19 +477,19 @@ func TestSanitizeRuntime_Allowlist(t *testing.T) { cases := []struct { in, want string }{ - {"", "langgraph"}, - {" ", "langgraph"}, - {"langgraph", "langgraph"}, + {"", "claude-code"}, + {" ", "claude-code"}, {"claude-code", "claude-code"}, {"openclaw", "openclaw"}, - {"deepagents", "deepagents"}, {"hermes", "hermes"}, {"codex", "codex"}, - {"crewai", "crewai"}, - {"autogen", "autogen"}, - {"not-a-runtime", "langgraph"}, // unknown → default - {"../../sensitive", "langgraph"}, // path traversal probe → default - {"langgraph\nevil", "langgraph"}, // newline injection → default (not in allowlist) + {"langgraph", "claude-code"}, // deprecated → default + {"deepagents", "claude-code"}, // deprecated → default + {"crewai", "claude-code"}, // deprecated → default + {"autogen", "claude-code"}, // deprecated → default + {"not-a-runtime", "claude-code"}, // unknown → default + {"../../sensitive", "claude-code"}, // path traversal probe → default + {"langgraph\nevil", "claude-code"}, // newline injection → default (not in allowlist) } for _, tc := range cases { if got := sanitizeRuntime(tc.in); got != tc.want {