diff --git a/.github/workflows/cache-warm.yml b/.github/workflows/cache-warm.yml index 946466909..1471f5d52 100644 --- a/.github/workflows/cache-warm.yml +++ b/.github/workflows/cache-warm.yml @@ -33,7 +33,6 @@ jobs: node-version: '22.22.0' lockfiles: | packages/claude-sdk-adapter/pnpm-lock.yaml - packages/mcode-harness/package-lock.json - run: pnpm --dir packages/claude-sdk-adapter fetch - uses: ./.github/actions/mcode-companion diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 0a307c7ad..99f556c93 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -231,7 +231,6 @@ jobs: with: lockfiles: | packages/claude-sdk-adapter/pnpm-lock.yaml - packages/mcode-harness/package-lock.json - name: Test and package Harness adapters run: make check-claude-sdk check-mcode-harness diff --git a/.github/workflows/native.yml b/.github/workflows/native.yml index 2511a7908..7c4837d7e 100644 --- a/.github/workflows/native.yml +++ b/.github/workflows/native.yml @@ -57,7 +57,6 @@ jobs: node-version: '22.22.0' lockfiles: | packages/claude-sdk-adapter/pnpm-lock.yaml - packages/mcode-harness/package-lock.json - name: Build the native daemon and test bundle boundaries id: build run: | diff --git a/apps/daemon/internal/agent/mcode/discovery_workspace.go b/apps/daemon/internal/agent/mcode/discovery_workspace.go index 39f932a57..e406c99bb 100644 --- a/apps/daemon/internal/agent/mcode/discovery_workspace.go +++ b/apps/daemon/internal/agent/mcode/discovery_workspace.go @@ -51,7 +51,7 @@ func discoverWorkspace(parent context.Context, options agent.DiscoveryOptions, r fail(err) return nil } - c, err := ConfigureLocal(binary, node, os.Getenv("OAC_RUNTIME_MCODE_WORKSPACE_BRIDGE"), options.RuntimeRoot, os.Getenv("OAC_RUNTIME_WORKSPACE"), binding.NetworkPolicy()) + c, err := ConfigureLocal(binary, node, os.Getenv("OAC_RUNTIME_MCODE_COMPANION"), os.Getenv("OAC_RUNTIME_WORKSPACE"), binding.NetworkPolicy()) if err == nil { err = CheckWorkspace(parent, c) } diff --git a/apps/daemon/internal/agent/mcode/environment_mcp.go b/apps/daemon/internal/agent/mcode/environment_mcp.go index c675465c8..867aa8c6f 100644 --- a/apps/daemon/internal/agent/mcode/environment_mcp.go +++ b/apps/daemon/internal/agent/mcode/environment_mcp.go @@ -17,7 +17,7 @@ func runtimeMCP(req proto.PromptRequestPayload) ([]map[string]any, error) { } var servers []map[string]any for _, binding := range bindings { - if binding.ServerLabel == "oac_workspace" || binding.ConnectionOrigin != "environment" || binding.AllowedTools != nil || binding.Required { + if binding.ConnectionOrigin != "environment" || binding.AllowedTools != nil || binding.Required { return nil, fmt.Errorf("mcode: unsupported MCP binding") } if binding.Transport == "http" { diff --git a/apps/daemon/internal/agent/mcode/environment_mcp_test.go b/apps/daemon/internal/agent/mcode/environment_mcp_test.go index dae9ac41f..d9a9f7c80 100644 --- a/apps/daemon/internal/agent/mcode/environment_mcp_test.go +++ b/apps/daemon/internal/agent/mcode/environment_mcp_test.go @@ -50,15 +50,15 @@ func TestEnvironmentMCPUsesFixedLauncherForNewAndLoadedSessions(t *testing.T) { Env []map[string]string } `json:"mcpServers"` } - if json.Unmarshal(raw, ¶ms) != nil || len(params.MCP) != 2 || params.MCP[0].Name != "oac_workspace" { - t.Fatal("environment MCP displaced workspace tools") + if json.Unmarshal(raw, ¶ms) != nil || len(params.MCP) != 1 { + t.Fatal("native Session did not receive exactly the declared MCP server") } cwd, err := os.ReadFile(record + ".cwd") workspace, pathErr := filepath.EvalSymlinks(req.LocalEnvironment.WorkspaceRoot) if err != nil || pathErr != nil || string(cwd) != workspace || params.Cwd != req.LocalEnvironment.WorkspaceRoot { t.Fatalf("native process and ACP Session must use the declared workspace: process=%q ACP=%q", cwd, params.Cwd) } - server := params.MCP[1] + server := params.MCP[0] executable, _ := os.Executable() if server.Name != "proof.server" || server.Command != executable || server.Env == nil || len(server.Env) != 0 || !reflect.DeepEqual(server.Args, []string{"runtime-mcp-exec", "/private/runtime/capabilities", "plugins/fixture", "proof.server"}) { @@ -72,7 +72,7 @@ func TestEnvironmentMCPUsesFixedLauncherForNewAndLoadedSessions(t *testing.T) { } func TestEnvironmentMCPRejectsUnqualifiedAuthorityBeforePreparation(t *testing.T) { - for _, name := range []string{"http-headers", "http-bearer-insecure", "http-bearer-missing", "restricted", "disabled", "duplicate", "reserved"} { + for _, name := range []string{"http-headers", "http-bearer-insecure", "http-bearer-missing", "restricted", "disabled", "duplicate"} { t.Run(name, func(t *testing.T) { c, req, _ := workspaceFixture(t) c.Network, req.LocalEnvironment.NetworkAccess = "enabled", "enabled" @@ -95,8 +95,6 @@ func TestEnvironmentMCPRejectsUnqualifiedAuthorityBeforePreparation(t *testing.T c.Network, req.LocalEnvironment.NetworkAccess = name, name case "duplicate": req.LocalEnvironment.MCP = append(req.LocalEnvironment.MCP, environmentMCPFixture()) - case "reserved": - req.LocalEnvironment.MCP[0].Server.Name = "oac_workspace" } if _, err := prepareWorkspaceOptions(testStateRoot(t), c, req); err == nil || strings.Contains(err.Error(), "confidential-http-token") { t.Fatal("unqualified declaration accepted or credential exposed") @@ -205,10 +203,10 @@ func TestEnvironmentHTTPMCPUsesEphemeralACPConfiguration(t *testing.T) { Headers []map[string]string } `json:"mcpServers"` } - if json.Unmarshal(raw, ¶ms) != nil || len(params.MCP) != 2 { - t.Fatal("HTTP MCP displaced workspace tools") + if json.Unmarshal(raw, ¶ms) != nil || len(params.MCP) != 1 { + t.Fatal("native Session did not receive exactly the declared MCP server") } - server := params.MCP[1] + server := params.MCP[0] if server.Name != "remote" || server.Type != "http" || server.URL != item.Server.URL || server.Headers == nil { t.Fatal("invalid native HTTP projection") } @@ -220,7 +218,7 @@ func TestEnvironmentHTTPMCPUsesEphemeralACPConfiguration(t *testing.T) { t.Fatal("anonymous MCP inherited credentials") } dataDir := resource.(*executor).opts.DataDir - for _, name := range []string{"config.yaml", "mcp.json", "workspace-profile.json"} { + for _, name := range []string{"config.yaml", "mcp.json"} { body, err := os.ReadFile(filepath.Join(dataDir, name)) if err != nil && !os.IsNotExist(err) { t.Fatal(err) diff --git a/apps/daemon/internal/agent/mcode/events.go b/apps/daemon/internal/agent/mcode/events.go index a41789fd1..1ee505dca 100644 --- a/apps/daemon/internal/agent/mcode/events.go +++ b/apps/daemon/internal/agent/mcode/events.go @@ -37,18 +37,25 @@ func (s *Session) handle(frame rpcFrame) error { s.mu.Lock() s.steeringReady = true s.mu.Unlock() + var chunk struct { + Type string `json:"type"` + Text string `json:"text"` + } + if kind := event.Update.Kind; (kind == "agent_message_chunk" || kind == "agent_thought_chunk") && json.Unmarshal(event.Update.Content, &chunk) != nil { + return fmt.Errorf("mcode: invalid session update") + } switch event.Update.Kind { case "agent_message_chunk": - if event.Update.Content.Type != "text" { + if chunk.Type != "text" { return fmt.Errorf("mcode: unsupported response content") } - text := event.Update.Content.Text + text := chunk.Text s.content.WriteString(text) s.sequence++ s.emit(proto.TypeDelta, proto.DeltaPayload{Delta: text, Sequence: s.sequence}) case "agent_thought_chunk": s.sequence++ - s.emit(proto.TypeThinking, proto.ThinkingPayload{Text: event.Update.Content.Text, Sequence: s.sequence}) + s.emit(proto.TypeThinking, proto.ThinkingPayload{Text: chunk.Text, Sequence: s.sequence}) case "tool_call", "tool_call_update": return s.emitTool(event.Update.toolUpdate) } @@ -80,7 +87,7 @@ func (s *Session) emitTool(update toolUpdate) error { return err } } - if previous.mcp == nil && workspaceToolObservation(previous, "before") == nil { + if previous.mcp == nil && s.nativeToolObservation(previous, "before") == nil { if err := s.emitToolStage(update, "before"); err != nil { return err } diff --git a/apps/daemon/internal/agent/mcode/execution.go b/apps/daemon/internal/agent/mcode/execution.go index a2204e470..d9f510088 100644 --- a/apps/daemon/internal/agent/mcode/execution.go +++ b/apps/daemon/internal/agent/mcode/execution.go @@ -29,16 +29,6 @@ func validateExecutionRequest(req proto.PromptRequestPayload) error { return nil } -func configureTextExecution(config map[string]any) { - config["agents"] = map[string]any{"default": map[string]any{ - "tools": []string{}, "builtinTools": []string{}, "skills": []string{}, - // Native web search calls MiniMax's cloud with a MiniMax account credential that Core does not hold. - "features": map[string]bool{"mavis": false, "delegation": false, "webSearch": false}, - }} - config["askUser"] = map[string]bool{"enabled": false} - config["beta"] = map[string]bool{"browserUseTooling": false, "mcodeTools": false, "threadGoal": false} -} - // Harness children use the daemon user's ordinary environment. func executionEnvironment() []string { return os.Environ() } diff --git a/apps/daemon/internal/agent/mcode/execution_test.go b/apps/daemon/internal/agent/mcode/execution_test.go index 9e8860680..7ee412b86 100644 --- a/apps/daemon/internal/agent/mcode/execution_test.go +++ b/apps/daemon/internal/agent/mcode/execution_test.go @@ -14,7 +14,7 @@ func TestExecutionOptionsInheritUserEnvironment(t *testing.T) { r := testRequest(t) t.Setenv("OAC_TEST_SECRET_CANARY", "secret") t.Setenv("NODE_OPTIONS", "--import=untrusted") - opts, err := prepareOptions(testStateRoot(t), r) + opts, err := prepareOptions(testStateRoot(t), r, nil) if err != nil { t.Fatal(err) } @@ -50,7 +50,7 @@ func TestExecutionRejectsUnqualifiedAuthority(t *testing.T) { } { r := testRequest(t) change(&r) - if _, err := prepareOptions(testStateRoot(t), r); err == nil { + if _, err := prepareOptions(testStateRoot(t), r, nil); err == nil { t.Fatal("unsupported execution accepted") } } diff --git a/apps/daemon/internal/agent/mcode/executor.go b/apps/daemon/internal/agent/mcode/executor.go index 0ea08bf3b..71e292a0c 100644 --- a/apps/daemon/internal/agent/mcode/executor.go +++ b/apps/daemon/internal/agent/mcode/executor.go @@ -46,7 +46,7 @@ func NewExecutorFactory(runtimeRoot string, config *WorkspaceConfig) agent.Execu var opts launchOptions binary := defaultBinary() if frozen == nil { - opts, err = prepareOptions(runtimeRoot, req) + opts, err = prepareOptions(runtimeRoot, req, nil) } else { binary = frozen.Binary opts, err = prepareWorkspaceOptions(runtimeRoot, *frozen, req) diff --git a/apps/daemon/internal/agent/mcode/executor_test.go b/apps/daemon/internal/agent/mcode/executor_test.go index d9936a712..e639d3a66 100644 --- a/apps/daemon/internal/agent/mcode/executor_test.go +++ b/apps/daemon/internal/agent/mcode/executor_test.go @@ -30,7 +30,7 @@ func workspaceFixture(t *testing.T) (WorkspaceConfig, proto.PromptRequestPayload if err := os.WriteFile(binary, []byte(script), 0700); err != nil { t.Fatal(err) } - return WorkspaceConfig{Binary: binary, Node: "/usr/bin/node", Bridge: "/opt/bridge.mjs", Directory: r.LocalEnvironment.WorkspaceRoot, Network: "enabled", Scratch: t.TempDir()}, r, record + return WorkspaceConfig{Binary: binary, Node: "/usr/bin/node", Companion: "/opt/mcode-harness", Directory: r.LocalEnvironment.WorkspaceRoot, Network: "enabled"}, r, record } func executorFixture(t *testing.T, scenario string, workspace bool) (*executor, string) { @@ -143,15 +143,15 @@ func TestExecutorCancellationRetiresOwnerAndLateCancelCannotRetarget(t *testing. if !disabled { // A terminal native history record still cannot prove Bash cleanup. dir := t.TempDir() - node, bridge := filepath.Join(dir, "node"), filepath.Join(dir, "bridge.mjs") + node := filepath.Join(dir, "node") body := "#!/bin/sh\nprintf '%s\\n' '{\"version\":1,\"complete\":true,\"rootSessionId\":\"native-1\",\"sessions\":[{\"id\":\"native-1\",\"turns\":[{\"id\":\"root-turn\",\"status\":\"aborted\"}]}]}'\n" - for name, data := range map[string]string{node: body, bridge: "", filepath.Join(dir, "subagent-snapshot.mjs"): ""} { + for name, data := range map[string]string{node: body, filepath.Join(dir, "subagent-snapshot.mjs"): ""} { if err := os.WriteFile(name, []byte(data), 0700); err != nil { t.Fatal(err) } } t.Setenv("OAC_RUNTIME_MCODE_NODE", node) - t.Setenv("OAC_RUNTIME_MCODE_WORKSPACE_BRIDGE", bridge) + t.Setenv("OAC_RUNTIME_MCODE_COMPANION", dir) } out := make(chan proto.Envelope, 32) second, err := e.StartTurn(t.Context(), "second", proto.TextInput("wait"), out) diff --git a/apps/daemon/internal/agent/mcode/harness_config_test.go b/apps/daemon/internal/agent/mcode/harness_config_test.go index 1de262c5d..0cc11b16d 100644 --- a/apps/daemon/internal/agent/mcode/harness_config_test.go +++ b/apps/daemon/internal/agent/mcode/harness_config_test.go @@ -8,11 +8,11 @@ import ( func TestNativeConfigDoesNotPretendToSupportParameters(t *testing.T) { req := testRequest(t) req.AgentOptions["harness_config"] = map[string]any{} - if _, err := prepareOptions(testStateRoot(t), req); err != nil { + if _, err := prepareOptions(testStateRoot(t), req, nil); err != nil { t.Fatal(err) } req.AgentOptions["harness_config"] = map[string]any{"temperature": 0.5} - if _, err := prepareOptions(testStateRoot(t), req); err != harnessconfig.ErrHarnessConfig { + if _, err := prepareOptions(testStateRoot(t), req, nil); err != harnessconfig.ErrHarnessConfig { t.Fatalf("unsupported parameter accepted: %v", err) } } diff --git a/apps/daemon/internal/agent/mcode/installation.go b/apps/daemon/internal/agent/mcode/installation.go index 5f6937fc1..ce35f0f42 100644 --- a/apps/daemon/internal/agent/mcode/installation.go +++ b/apps/daemon/internal/agent/mcode/installation.go @@ -12,7 +12,7 @@ import ( func Installation() agent.Installation { return agent.Installation{AgentKind: "mcode", Version: SupportedVersion, Supported: func() bool { return runtime.GOOS == "linux" || runtime.GOOS == "darwin" }, Environment: func(dir, node string) map[string]string { - return map[string]string{"OAC_RUNTIME_MCODE_BIN": filepath.Join(dir, "native", "cli.js"), "OAC_RUNTIME_MCODE_NODE": node, "OAC_RUNTIME_MCODE_WORKSPACE_BRIDGE": filepath.Join(dir, "bridge.mjs")} + return map[string]string{"OAC_RUNTIME_MCODE_BIN": filepath.Join(dir, "native", "cli.js"), "OAC_RUNTIME_MCODE_NODE": node, "OAC_RUNTIME_MCODE_COMPANION": dir} }, Check: func(ctx context.Context, dir, node string, env []string) error { got, err := installroot.Probe(ctx, node, []string{filepath.Join(dir, "native", "cli.js"), "--version"}, env, dir) diff --git a/apps/daemon/internal/agent/mcode/mcp_observations.go b/apps/daemon/internal/agent/mcode/mcp_observations.go index 364d8995b..fa719802b 100644 --- a/apps/daemon/internal/agent/mcode/mcp_observations.go +++ b/apps/daemon/internal/agent/mcode/mcp_observations.go @@ -18,7 +18,7 @@ type mcpToolIdentity struct{ server, tool string } // Read its exact mapping instead of reversing lossy native name normalization. func (s *Session) environmentMCPIdentity(name string) (*mcpToolIdentity, error) { if s.req.LocalEnvironment == nil || - !strings.HasPrefix(name, "mcp__") || strings.HasPrefix(name, "mcp__oac_workspace__") { + !strings.HasPrefix(name, "mcp__") { return nil, nil } bindings, err := agent.ResolveMCPBindings(s.req) @@ -55,7 +55,7 @@ func (s *Session) environmentMCPIdentity(name string) (*mcpToolIdentity, error) declared++ } } - if found != nil || declared != 1 || server.Raw == "oac_workspace" || tool.Raw == "" || + if found != nil || declared != 1 || tool.Raw == "" || json.Unmarshal([]byte(server.Key), &key) != nil || len(key) != 2 || key[0] != "configured" || key[1] != server.Raw { return nil, fmt.Errorf("mcode: ambiguous or undeclared native MCP identity") } diff --git a/apps/daemon/internal/agent/mcode/mcp_observations_test.go b/apps/daemon/internal/agent/mcode/mcp_observations_test.go index 9f613f1a9..923a751fc 100644 --- a/apps/daemon/internal/agent/mcode/mcp_observations_test.go +++ b/apps/daemon/internal/agent/mcode/mcp_observations_test.go @@ -164,18 +164,6 @@ func TestEnvironmentMCPResultMustMatchStartAndUnsettledCallsCloseOnce(t *testing } } -func TestEnvironmentMCPDoesNotPublishInternalWorkspaceUtilities(t *testing.T) { - s, out := mcpObservationSession(t) - for _, name := range []string{"workspace_read", "workspace_write", "workspace_edit", "workspace_glob", "workspace_grep"} { - if err := s.emitTool(toolUpdate{ID: name, Name: "mcp__oac_workspace__" + name, Status: "completed"}); err != nil { - t.Fatal(err) - } - } - if len(out) != 0 { - t.Fatal("internal workspace utility became a public MCP call") - } -} - func TestEnvironmentMCPNativeJSONRetainsIntegerPrecision(t *testing.T) { s, out := mcpObservationSession(t) frames := []string{ diff --git a/apps/daemon/internal/agent/mcode/model_provider_test.go b/apps/daemon/internal/agent/mcode/model_provider_test.go index bdef5fa6c..72089c3db 100644 --- a/apps/daemon/internal/agent/mcode/model_provider_test.go +++ b/apps/daemon/internal/agent/mcode/model_provider_test.go @@ -16,7 +16,7 @@ func TestOptionsModelProviderProtocols(t *testing.T) { req := testRequest(t) req.AgentOptions["model_provider"].(map[string]any)["protocol"] = tc.protocol req.AgentOptions["model"] = "chosen-model" - opts, err := prepareOptions(testStateRoot(t), req) + opts, err := prepareOptions(testStateRoot(t), req, nil) if err != nil { t.Fatal(err) } @@ -60,7 +60,7 @@ func TestOptionsRejectInvalidProvider(t *testing.T) { t.Run(tc.name, func(t *testing.T) { req := testRequest(t) req.AgentOptions["model_provider"].(map[string]any)[tc.field] = tc.value - if _, err := prepareOptions(testStateRoot(t), req); err == nil { + if _, err := prepareOptions(testStateRoot(t), req, nil); err == nil { t.Fatal("invalid model provider accepted") } }) @@ -69,7 +69,7 @@ func TestOptionsRejectInvalidProvider(t *testing.T) { req := testRequest(t) req.AgentOptions["mcode_provider"] = req.AgentOptions["model_provider"] delete(req.AgentOptions, "model_provider") - if _, err := prepareOptions(testStateRoot(t), req); err == nil { + if _, err := prepareOptions(testStateRoot(t), req, nil); err == nil { t.Fatal("retired native provider option accepted") } }) @@ -78,7 +78,7 @@ func TestOptionsRejectInvalidProvider(t *testing.T) { func TestOptionsAllowLoopbackProviderFixture(t *testing.T) { req := testRequest(t) req.AgentOptions["model_provider"].(map[string]any)["base_url"] = "http://127.0.0.1:4321/v1" - if _, err := prepareOptions(testStateRoot(t), req); err != nil { + if _, err := prepareOptions(testStateRoot(t), req, nil); err != nil { t.Fatal(err) } } diff --git a/apps/daemon/internal/agent/mcode/options.go b/apps/daemon/internal/agent/mcode/options.go index 22e49a66c..5d0223ea1 100644 --- a/apps/daemon/internal/agent/mcode/options.go +++ b/apps/daemon/internal/agent/mcode/options.go @@ -18,7 +18,17 @@ type launchOptions struct { MCP []map[string]any } -func prepareOptions(runtimeRoot string, req proto.PromptRequestPayload) (launchOptions, error) { +// workspaceProfile selects the native tools of a Session that has an Environment. +type workspaceProfile struct { + // ToolEnv is the Runtime's frozen tool environment as KEY=value entries. + ToolEnv []string + // WebFetch keeps the native web_fetch tool. + WebFetch bool +} + +// prepareOptions writes the native configuration. A nil workspace is the none +// placement: the native process has no builtin file, process or fetch tools. +func prepareOptions(runtimeRoot string, req proto.PromptRequestPayload, workspace *workspaceProfile) (launchOptions, error) { var result launchOptions if _, err := harnessconfiguration.Declaration.Configuration.PrepareHarnessConfig(req.AgentOptions); err != nil { return result, err @@ -56,13 +66,14 @@ func prepareOptions(runtimeRoot string, req proto.PromptRequestPayload) (launchO return result, err } config["custom_provider"] = map[string]any{"oac": provider} - configureTextExecution(config) - if !req.DisableSubagents { - config["agents"] = map[string]any{"default": map[string]any{ - "tools": []string{"task", "task_append", "task_query", "task_output", "task_stop"}, - "builtinTools": []string{"task", "task_append", "task_query", "task_output", "task_stop"}, "skills": []string{}, - "features": map[string]bool{"mavis": false, "delegation": true, "webSearch": false}, - }} + config["agents"] = map[string]any{"default": agentCapabilities(req, workspace)} + // Unattended: no ask-user. Native mcode-tools needs a MiniMax account sign-in that Core does not hold. + // Thread goals schedule continuation Turns after the Turn ends. + config["askUser"] = map[string]bool{"enabled": false} + config["beta"] = map[string]bool{"mcodeTools": false, "threadGoal": false} + if workspace != nil { + // Unattended: asks are allowed; the few that bypass cannot lift are answered cancelled and fail the tool call. + config["permissionMode"] = "bypassPermissions" } data, err := json.Marshal(config) if err != nil { @@ -71,7 +82,12 @@ func prepareOptions(runtimeRoot string, req proto.PromptRequestPayload) (launchO if err := os.WriteFile(filepath.Join(result.DataDir, "config.yaml"), data, 0o600); err != nil { return result, err } - result.Env = append(executionEnvironment(), "OAC_RUNTIME_MCODE_TOOL_POLICY=protected-mcp-v1") + result.Env = executionEnvironment() + if workspace != nil { + result.Env = append(result.Env, workspace.ToolEnv...) + } + // Background bash outlives the Turn and escapes the process group; Subagent task output stays. + result.Env = append(result.Env, "OAC_RUNTIME_MCODE_BACKGROUND_BASH=off") if err := os.WriteFile(filepath.Join(result.DataDir, "mcp.json"), []byte(`{"mcpServers":{}}`), 0o600); err != nil { return result, err } @@ -86,6 +102,36 @@ func prepareOptions(runtimeRoot string, req proto.PromptRequestPayload) (launchO return result, nil } +// agentCapabilities is the native agents.default ceiling. A native ID list is +// all-or-nothing: one unknown ID drops the whole list and enables every tool. +func agentCapabilities(req proto.PromptRequestPayload, workspace *workspaceProfile) map[string]any { + tools := []string{} + if workspace != nil { + // website_deploy needs a MiniMax account and stays out of the list. + tools = append(tools, "read", "write", "edit", "bash", "grep", "glob", "todowrite") + if workspace.WebFetch { + tools = append(tools, "web_fetch") + } + } + capabilities := map[string]any{ + "tools": tools, + // Builtin Matrix MCP tools call MiniMax's cloud with an account credential that Core does not hold. + "builtinTools": []string{}, + "features": map[string]bool{ + // Mavis adds cron scheduling and memory that run beyond a Turn, and needs a MiniMax account. + "mavis": false, + "delegation": !req.DisableSubagents, + // Native web search calls MiniMax's cloud with a MiniMax account credential that Core does not hold. + "webSearch": false, + }, + } + if workspace == nil { + // The none placement reads no Skills; a workspace keeps the native selection with the linked Skills. + capabilities["skills"] = []string{} + } + return capabilities +} + func optionString(options map[string]any, key string) string { value, _ := options[key].(string) return value diff --git a/apps/daemon/internal/agent/mcode/options_test.go b/apps/daemon/internal/agent/mcode/options_test.go index 6334e978d..1dd26f6a8 100644 --- a/apps/daemon/internal/agent/mcode/options_test.go +++ b/apps/daemon/internal/agent/mcode/options_test.go @@ -13,7 +13,7 @@ import ( func TestOptionsRefreshManagedState(t *testing.T) { t.Setenv("MINIMAX_DATA_DIR", "/wrong") req := testRequest(t) - opts, err := prepareOptions(testStateRoot(t), req) + opts, err := prepareOptions(testStateRoot(t), req, nil) if err != nil { t.Fatal(err) } @@ -31,7 +31,7 @@ func TestOptionsRefreshManagedState(t *testing.T) { } req.AgentOptions["system_prompt"] = "" req.AgentSessionID = "native-1" - refreshed, err := prepareOptions(testStateRoot(t), req) + refreshed, err := prepareOptions(testStateRoot(t), req, nil) if err != nil { t.Fatal(err) } @@ -56,6 +56,9 @@ func TestOptionsRefreshManagedState(t *testing.T) { if cfg["skills"].(map[string]any)["external"].(map[string]any)["enabled"] != false { t.Fatal("external discovery enabled") } + if skills, ok := cfg["agents"].(map[string]any)["default"].(map[string]any)["skills"].([]any); !ok || len(skills) != 0 { + t.Fatal("text profile selected native Skills") + } info, _ := os.Stat(filepath.Join(opts.DataDir, "config.yaml")) if info.Mode().Perm() != 0600 { t.Fatal("native credentials file is not private") @@ -75,7 +78,7 @@ func TestOptionsRejectDroppedContext(t *testing.T) { t.Run(tt.name, func(t *testing.T) { req := testRequest(t) tt.edit(&req) - if _, err := prepareOptions(testStateRoot(t), req); err == nil { + if _, err := prepareOptions(testStateRoot(t), req, nil); err == nil { t.Fatal("expected validation failure") } }) diff --git a/apps/daemon/internal/agent/mcode/protocol.go b/apps/daemon/internal/agent/mcode/protocol.go index 116d8c024..516ca2183 100644 --- a/apps/daemon/internal/agent/mcode/protocol.go +++ b/apps/daemon/internal/agent/mcode/protocol.go @@ -42,11 +42,9 @@ type toolUpdate struct { type sessionUpdate struct { SessionID string `json:"sessionId"` Update struct { - Kind string `json:"sessionUpdate"` - Content struct { - Type string `json:"type"` - Text string `json:"text"` - } `json:"content"` + Kind string `json:"sessionUpdate"` + // Message chunks carry one content block; native tool updates carry a list, which is not read. + Content json.RawMessage `json:"content"` toolUpdate } `json:"update"` } diff --git a/apps/daemon/internal/agent/mcode/session.go b/apps/daemon/internal/agent/mcode/session.go index 528d0338b..e655d1199 100644 --- a/apps/daemon/internal/agent/mcode/session.go +++ b/apps/daemon/internal/agent/mcode/session.go @@ -74,7 +74,7 @@ func (s *Session) prepareNative() error { Meta struct { Subagents struct { Version, MaxConcurrent int - WorkspaceTools string + BackgroundBash *bool } `json:"oac/subagents"` } `json:"_meta"` } @@ -84,8 +84,11 @@ func (s *Session) prepareNative() error { if initialized.ProtocolVersion != 1 { return fmt.Errorf("mcode: unsupported ACP protocol version %d", initialized.ProtocolVersion) } - if !s.req.DisableSubagents && (initialized.Meta.Subagents.Version != 1 || initialized.Meta.Subagents.WorkspaceTools != "protected-mcp-v1" || s.req.MaxConcurrentSubagents == nil || initialized.Meta.Subagents.MaxConcurrent != *s.req.MaxConcurrentSubagents) { - return fmt.Errorf("mcode: native Subagent admission is unavailable") + // Only the patched CLI reports that background bash is off; an unpatched one would leave work after a Turn. + meta := initialized.Meta.Subagents + if meta.Version != 2 || meta.BackgroundBash == nil || *meta.BackgroundBash || + (!s.req.DisableSubagents && (s.req.MaxConcurrentSubagents == nil || meta.MaxConcurrent != *s.req.MaxConcurrentSubagents)) { + return fmt.Errorf("mcode: patched native CLI is unavailable") } params := map[string]any{"cwd": s.opts.Dir, "mcpServers": s.opts.MCP} method := "session/new" diff --git a/apps/daemon/internal/agent/mcode/session_test.go b/apps/daemon/internal/agent/mcode/session_test.go index 9f4726fe0..22f39b9be 100644 --- a/apps/daemon/internal/agent/mcode/session_test.go +++ b/apps/daemon/internal/agent/mcode/session_test.go @@ -6,6 +6,7 @@ import ( "encoding/json" "os" "path/filepath" + "strconv" "strings" "testing" "time" @@ -161,7 +162,7 @@ func TestResumeSelectsModelWhenNativeSelectorIsMissing(t *testing.T) { } func TestSessionFailuresAreReported(t *testing.T) { - for _, scenario := range []string{"malformed", "exit", "unknown-model"} { + for _, scenario := range []string{"malformed", "exit", "unknown-model", "background-bash"} { t.Run(scenario, func(t *testing.T) { ctx, cancel := context.WithTimeout(t.Context(), 10*time.Second) defer cancel() @@ -254,7 +255,10 @@ func TestMCodeProcess(t *testing.T) { if scenario == "unattended" && strings.Contains(string(frame.Params), "elicitation") { os.Exit(7) } - result = map[string]int{"protocolVersion": 1} + // The patched CLI reports its Subagent limit and whether bash may outlive a Turn. + limit, _ := strconv.Atoi(os.Getenv("OAC_RUNTIME_MCODE_MAX_SUBAGENTS")) + result = map[string]any{"protocolVersion": 1, "_meta": map[string]any{"oac/subagents": map[string]any{ + "version": 2, "maxConcurrent": limit, "backgroundBash": scenario == "background-bash" || os.Getenv("OAC_RUNTIME_MCODE_BACKGROUND_BASH") != "off"}}} case "session/new", "session/load": if scenario == "prepared-mcp-cancel" { if writeMCPRegistry(os.Getenv("MINIMAX_DATA_DIR"), mcpRegistryEntry("proof.server", "proof_server", "read.status", "read_status")) != nil { @@ -306,7 +310,7 @@ func TestMCodeProcess(t *testing.T) { continue } update("agent_message_chunk", map[string]any{"content": map[string]string{"type": "text", "text": input.Prompt[0]["text"]}}) - update("tool_call", map[string]any{"toolCallId": "repeated-call", "name": "mcp__oac_workspace__workspace_bash", "status": "in_progress", "rawInput": map[string]any{"command": "true"}}) + update("tool_call", map[string]any{"toolCallId": "repeated-call", "name": "bash", "status": "in_progress", "rawInput": map[string]any{"command": "true"}}) update("tool_call_update", map[string]any{"toolCallId": "repeated-call", "status": "completed"}) raw, _ := json.Marshal(map[string]string{"stopReason": "end_turn"}) send(rpcFrame{JSONRPC: "2.0", ID: frame.ID, Result: raw}) @@ -345,7 +349,7 @@ func TestMCodeProcess(t *testing.T) { } update("agent_message_chunk", map[string]any{"content": map[string]string{"type": "text", "text": "Hello "}}) update("agent_message_chunk", map[string]any{"content": map[string]string{"type": "text", "text": "world"}}) - update("tool_call", map[string]any{"toolCallId": "tool-1", "name": "mcp__oac_workspace__workspace_bash", "status": "in_progress", "rawInput": map[string]any{"command": "cat fixture.txt"}}) + update("tool_call", map[string]any{"toolCallId": "tool-1", "name": "bash", "status": "in_progress", "rawInput": map[string]any{"command": "cat fixture.txt"}}) for range 2 { update("tool_call_update", map[string]any{"toolCallId": "tool-1", "status": "completed", "rawOutput": "fixture"}) } diff --git a/apps/daemon/internal/agent/mcode/subagent_messages.go b/apps/daemon/internal/agent/mcode/subagent_messages.go index 00f20d105..4b20c429a 100644 --- a/apps/daemon/internal/agent/mcode/subagent_messages.go +++ b/apps/daemon/internal/agent/mcode/subagent_messages.go @@ -72,7 +72,7 @@ func (s *Session) projectSubagentMessages(session nativeSubagentSession, turn na status = "failed" } update := toolUpdate{ID: tool.ID, Name: tool.Name, Status: status, RawInput: args, RawOutput: result} - observation := workspaceToolObservation(update, "after") + observation := s.nativeToolObservation(update, "after") if observation == nil { var err error update.mcp, err = s.environmentMCPIdentity(update.Name) diff --git a/apps/daemon/internal/agent/mcode/subagents.go b/apps/daemon/internal/agent/mcode/subagents.go index 65c05a7b2..56ea16303 100644 --- a/apps/daemon/internal/agent/mcode/subagents.go +++ b/apps/daemon/internal/agent/mcode/subagents.go @@ -53,9 +53,9 @@ type nativeSubagentTask struct { } func subagentReader() (string, string, error) { - node, bridge := os.Getenv("OAC_RUNTIME_MCODE_NODE"), os.Getenv("OAC_RUNTIME_MCODE_WORKSPACE_BRIDGE") - reader := filepath.Join(filepath.Dir(bridge), "subagent-snapshot.mjs") - for _, path := range []string{node, bridge, reader} { + node := os.Getenv("OAC_RUNTIME_MCODE_NODE") + reader := filepath.Join(os.Getenv("OAC_RUNTIME_MCODE_COMPANION"), "subagent-snapshot.mjs") + for _, path := range []string{node, reader} { resolved, err := filepath.EvalSymlinks(path) if err != nil || !filepath.IsAbs(path) || resolved != path { return "", "", fmt.Errorf("mcode: protected Subagent reader is unavailable") diff --git a/apps/daemon/internal/agent/mcode/subagents_test.go b/apps/daemon/internal/agent/mcode/subagents_test.go index 25a1773af..7761d5024 100644 --- a/apps/daemon/internal/agent/mcode/subagents_test.go +++ b/apps/daemon/internal/agent/mcode/subagents_test.go @@ -72,10 +72,9 @@ func TestSubagentSnapshotRejectsMissingParentProvenance(t *testing.T) { // The ACP cancelled response may precede the root native Turn becoming terminal. func TestSubagentSettlementIncludesActiveRootTurn(t *testing.T) { dir := t.TempDir() - node, bridge := filepath.Join(dir, "node"), filepath.Join(dir, "bridge.mjs") + node := filepath.Join(dir, "node") for name, data := range map[string]string{ - node: "#!/bin/sh\ncat \"$3/snapshot.json\"\n", - bridge: "", + node: "#!/bin/sh\ncat \"$3/snapshot.json\"\n", filepath.Join(dir, "subagent-snapshot.mjs"): "", } { if err := os.WriteFile(name, []byte(data), 0700); err != nil { @@ -83,7 +82,7 @@ func TestSubagentSettlementIncludesActiveRootTurn(t *testing.T) { } } t.Setenv("OAC_RUNTIME_MCODE_NODE", node) - t.Setenv("OAC_RUNTIME_MCODE_WORKSPACE_BRIDGE", bridge) + t.Setenv("OAC_RUNTIME_MCODE_COMPANION", dir) for _, status := range []string{"accepted", "aborted"} { t.Run(status, func(t *testing.T) { raw := []byte(`{"version":1,"complete":true,"rootSessionId":"root","sessions":[{"id":"root","turns":[{"id":"current","status":"` + status + `"}]}]}`) diff --git a/apps/daemon/internal/agent/mcode/tool_environment_test.go b/apps/daemon/internal/agent/mcode/tool_environment_test.go index fd047583a..1388e8c00 100644 --- a/apps/daemon/internal/agent/mcode/tool_environment_test.go +++ b/apps/daemon/internal/agent/mcode/tool_environment_test.go @@ -2,10 +2,10 @@ package mcode import ( "bytes" - "encoding/json" "io/fs" "os" "path/filepath" + "slices" "testing" "github.com/MiniMax-AI/OpenAgentCore/apps/daemon/internal/localworkspace" @@ -64,21 +64,14 @@ func TestWorkspaceCredentialsRemainInRuntimeSnapshotAcrossReconnect(t *testing.T if err != nil { t.Fatal(err) } - raw, err := os.ReadFile(filepath.Join(opts.DataDir, "workspace-profile.json")) - if err != nil { - t.Fatal(err) - } - var profile struct { - ToolEnvFile string `json:"toolEnvFile"` - } - if err := json.Unmarshal(raw, &profile); err != nil { - t.Fatal(err) - } - if profile.ToolEnvFile != filepath.Join(initialization, "tool-env.json") { - t.Fatal("native profile did not retain the Runtime snapshot reference") + // The native process, and so its tools, sees the Runtime snapshot, not a later change to the source. + for _, want := range []string{"MCP_TEST_TOKEN=fixture-bearer-canary", "TOOL_SECRET=fixture-tool-canary"} { + if !slices.Contains(opts.Env, want) { + t.Fatal("native environment lacks the frozen tool environment", want) + } } // Assert the actual env-selected HTTP credential, not a manually injected token. - headers, ok := opts.MCP[1]["headers"].([]map[string]string) + headers, ok := opts.MCP[0]["headers"].([]map[string]string) if !ok || len(headers) != 1 || headers[0]["name"] != "Authorization" || headers[0]["value"] != "Bearer fixture-bearer-canary" { t.Fatal("frozen MCP credential was lost or replaced") } diff --git a/apps/daemon/internal/agent/mcode/tool_observations.go b/apps/daemon/internal/agent/mcode/tool_observations.go index 1dbd6d643..252657b27 100644 --- a/apps/daemon/internal/agent/mcode/tool_observations.go +++ b/apps/daemon/internal/agent/mcode/tool_observations.go @@ -2,6 +2,8 @@ package mcode import ( "encoding/json" + "math" + "strings" "github.com/MiniMax-AI/OpenAgentCore/internal/agentdaemon/proto" ) @@ -11,7 +13,7 @@ func (s *Session) emitToolStage(update toolUpdate, stage string) error { if stage == "after" { payload.Result = map[string]any{"output": update.RawOutput, "status": update.Status} } - payload.Observation = workspaceToolObservation(update, stage) + payload.Observation = s.nativeToolObservation(update, stage) if payload.Observation == nil { var err error payload.Observation, err = environmentMCPObservation(update, stage) @@ -19,7 +21,7 @@ func (s *Session) emitToolStage(update toolUpdate, stage string) error { return err } } - // Native task/skill bookkeeping has no qualified public item mapping. + // Native task/Skill/file bookkeeping has no qualified public item mapping. if payload.Observation == nil { return nil } @@ -27,35 +29,68 @@ func (s *Session) emitToolStage(update toolUpdate, stage string) error { return nil } -func workspaceToolObservation(update toolUpdate, stage string) *proto.ToolObservation { - if update.Name != "mcp__oac_workspace__workspace_bash" { - return nil - } - command, _ := update.RawInput["command"].(string) - if command == "" { - return nil - } - cwd := "/workspace" - result := &proto.ToolObservation{Kind: "command", Command: command, Cwd: &cwd, Status: "in_progress"} - if stage == "after" { - result.Status = update.Status - // ACP reports MCP content but no structured exit code or duration. - raw, _ := json.Marshal(update.RawOutput) - var output struct { - Content []struct { - Type string `json:"type"` - Text string `json:"text"` - } `json:"content"` +// nativeToolObservation projects the native bash and web_fetch tools. A tool +// without its arguments yet has no observation. +func (s *Session) nativeToolObservation(update toolUpdate, stage string) *proto.ToolObservation { + switch update.Name { + case "bash": + command, _ := update.RawInput["command"].(string) + if command == "" { + return nil } - if json.Unmarshal(raw, &output) == nil && output.Content != nil { - text := "" - for _, part := range output.Content { - if part.Type == "text" { - text += part.Text - } + cwd := s.opts.Dir + result := &proto.ToolObservation{Kind: "command", Command: command, Cwd: &cwd, Status: "in_progress"} + if stage == "after" { + result.Status = update.Status + result.Output, _ = json.Marshal(toolResultText(update.RawOutput)) + // Only a finished command reports its exit code; a failed one carries prose, which is not parsed. + if code, ok := nativeExitCode(update.RawOutput); ok { + result.ExitCode = &code } - result.Output, _ = json.Marshal(text) } + return result + case "web_fetch": + url, _ := update.RawInput["url"].(string) + if url == "" { + return nil + } + result := &proto.ToolObservation{Kind: "web_search", Status: "in_progress", Action: &proto.ToolWebSearchAction{Type: "open_page", URL: &url}} + if stage == "after" { + result.Status = update.Status + } + return result + } + return nil +} + +// toolResultText joins the text parts of a native tool result; a Subagent history keeps plain text results. +func toolResultText(output any) string { + if text, ok := output.(string); ok { + return text + } + result, _ := output.(map[string]any) + parts, _ := result["content"].([]any) + var text strings.Builder + for _, part := range parts { + if item, _ := part.(map[string]any); item["type"] == "text" { + value, _ := item["text"].(string) + text.WriteString(value) + } + } + return text.String() +} + +// nativeExitCode reads the structured exit code of a finished bash command. +func nativeExitCode(output any) (int64, bool) { + result, _ := output.(map[string]any) + details, _ := result["details"].(map[string]any) + process, _ := details["processOutput"].(map[string]any) + switch code := process["exitCode"].(type) { + case json.Number: + value, err := code.Int64() + return value, err == nil + case float64: + return int64(code), code == math.Trunc(code) } - return result + return 0, false } diff --git a/apps/daemon/internal/agent/mcode/tool_observations_test.go b/apps/daemon/internal/agent/mcode/tool_observations_test.go index 703e3cb90..12817a217 100644 --- a/apps/daemon/internal/agent/mcode/tool_observations_test.go +++ b/apps/daemon/internal/agent/mcode/tool_observations_test.go @@ -8,18 +8,32 @@ import ( "github.com/MiniMax-AI/OpenAgentCore/internal/agentdaemon/proto" ) -func TestWorkspaceCommandObservationsWaitForArgumentsAndRetainOutcome(t *testing.T) { - for _, status := range []string{"completed", "failed"} { - t.Run(status, func(t *testing.T) { - out := make(chan proto.Envelope, 8) - s := &Session{ctx: context.Background(), outputContext: context.Background(), req: proto.PromptRequestPayload{RunID: "run"}, out: out, tools: map[string]toolUpdate{}, completedTools: map[string]bool{}} - s.emitTool(toolUpdate{ID: "call", Name: "mcp__oac_workspace__workspace_bash"}) +func observationSession() (*Session, chan proto.Envelope) { + out := make(chan proto.Envelope, 8) + return &Session{ctx: context.Background(), outputContext: context.Background(), req: proto.PromptRequestPayload{RunID: "run"}, opts: launchOptions{Dir: "/environment/workspace"}, + out: out, tools: map[string]toolUpdate{}, completedTools: map[string]bool{}}, out +} + +func TestNativeBashBecomesCommandObservation(t *testing.T) { + for _, test := range []struct { + status string + output map[string]any + exit *int64 + text string + }{ + {"completed", map[string]any{"content": []any{map[string]any{"type": "text", "text": "/environment/workspace"}}, + "details": map[string]any{"processOutput": map[string]any{"exitCode": json.Number("0")}}}, ptr(0), "/environment/workspace"}, + // A failed command reports only prose, which is not parsed for an exit code. + {"failed", map[string]any{"content": []any{map[string]any{"type": "text", "text": "Command exited with code 3"}}}, nil, "Command exited with code 3"}, + } { + t.Run(test.status, func(t *testing.T) { + s, out := observationSession() + _ = s.emitTool(toolUpdate{ID: "call", Name: "bash"}) if len(out) != 0 { t.Fatal("command item emitted before native arguments") } - s.emitTool(toolUpdate{ID: "call", RawInput: map[string]any{"command": "pwd"}}) - s.emitTool(toolUpdate{ID: "call", Status: status, RawOutput: map[string]any{"content": []any{map[string]string{"type": "text", "text": "/workspace"}}}}) - s.emitTool(toolUpdate{ID: "call", Status: status}) + _ = s.emitTool(toolUpdate{ID: "call", RawInput: map[string]any{"command": "pwd"}}) + _ = s.emitTool(toolUpdate{ID: "call", Status: test.status, RawOutput: test.output}) if len(out) != 2 { t.Fatalf("events=%d", len(out)) } @@ -30,20 +44,40 @@ func TestWorkspaceCommandObservationsWaitForArgumentsAndRetainOutcome(t *testing t.Fatal(before) } n := after.Observation - if n == nil || n.Command != "pwd" || n.Cwd == nil || *n.Cwd != "/workspace" || n.Status != status { + output, _ := json.Marshal(test.text) + if n == nil || n.Command != "pwd" || n.Cwd == nil || *n.Cwd != "/environment/workspace" || n.Status != test.status || string(n.Output) != string(output) { t.Fatal(after) } - if string(n.Output) != `"/workspace"` || n.ExitCode != nil || n.DurationMS != nil { - t.Fatal(n) + if (n.ExitCode == nil) != (test.exit == nil) || (n.ExitCode != nil && *n.ExitCode != *test.exit) || n.DurationMS != nil { + t.Fatal("exit code", n.ExitCode) } }) } } -func TestPrivateUtilitiesAreNotInventedPublicFunctionCalls(t *testing.T) { - for _, name := range []string{"mcp__oac_workspace__workspace_read", "skill", "task_query", "task_output", "task_stop", "mcp__unregistered__workspace_bash"} { - if workspaceToolObservation(toolUpdate{Name: name}, "before") != nil { +func TestNativeWebFetchBecomesOpenPageObservation(t *testing.T) { + s, out := observationSession() + _ = s.emitTool(toolUpdate{ID: "call", Name: "web_fetch", RawInput: map[string]any{"url": "https://example.test/doc"}}) + _ = s.emitTool(toolUpdate{ID: "call", Status: "failed", RawOutput: map[string]any{}}) + var calls [2]proto.ToolCallPayload + for i := range calls { + _ = json.Unmarshal((<-out).Payload, &calls[i]) + } + for i, status := range []string{"in_progress", "failed"} { + n := calls[i].Observation + if n == nil || n.Kind != "web_search" || n.Status != status || n.Action == nil || n.Action.Type != "open_page" || n.Action.URL == nil || *n.Action.URL != "https://example.test/doc" { + t.Fatal(calls[i]) + } + } +} + +func TestOtherNativeToolsAreNotInventedPublicItems(t *testing.T) { + s, _ := observationSession() + for _, name := range []string{"read", "write", "edit", "grep", "glob", "todowrite", "skill", "task_query", "task_output", "task_stop"} { + if s.nativeToolObservation(toolUpdate{Name: name, RawInput: map[string]any{"command": "x", "url": "x"}}, "before") != nil { t.Fatal(name) } } } + +func ptr(value int64) *int64 { return &value } diff --git a/apps/daemon/internal/agent/mcode/workspace.go b/apps/daemon/internal/agent/mcode/workspace.go index 80e4fe020..fafc68bc1 100644 --- a/apps/daemon/internal/agent/mcode/workspace.go +++ b/apps/daemon/internal/agent/mcode/workspace.go @@ -1,11 +1,12 @@ package mcode import ( - "encoding/json" "fmt" + "maps" "os" "path/filepath" "runtime" + "slices" "github.com/MiniMax-AI/OpenAgentCore/apps/daemon/internal/localworkspace" "github.com/MiniMax-AI/OpenAgentCore/internal/agentdaemon/proto" @@ -14,25 +15,24 @@ import ( // WorkspaceConfig is frozen deployment input, separate from public Agent options. type WorkspaceConfig struct { - Binary, Node, Bridge, Directory, Network, Scratch string - AllowedDomains []string + Binary, Node, Companion, Directory, Network string + AllowedDomains []string } -func ConfigureLocal(binary, node, bridge, root, workspace string, network agentnetwork.Policy) (WorkspaceConfig, error) { - c := WorkspaceConfig{Binary: binary, Node: node, Bridge: bridge, Directory: workspace, Network: network.Access, AllowedDomains: network.Hosts(), - Scratch: filepath.Join(root, "runtime", "mcode-tools", "scratch")} +func ConfigureLocal(binary, node, companion, workspace string, network agentnetwork.Policy) (WorkspaceConfig, error) { + c := WorkspaceConfig{Binary: binary, Node: node, Companion: companion, Directory: workspace, Network: network.Access, AllowedDomains: network.Hosts()} if runtime.GOOS == "windows" || network.Access != "enabled" || len(network.AllowedDomains) != 0 { return c, fmt.Errorf("mcode: native execution requires Linux or macOS and unrestricted host access") } if network.Validate() != nil { return c, fmt.Errorf("mcode: explicit workspace network policy is required") } - for _, path := range []string{binary, node, bridge, root, workspace} { + for _, path := range []string{binary, node, companion, workspace} { if !filepath.IsAbs(path) || filepath.Clean(path) != path || path == "/" { return c, fmt.Errorf("mcode: canonical absolute deployment paths are required") } } - for _, path := range []string{binary, node, bridge} { + for _, path := range []string{binary, node} { info, err := os.Stat(path) if err != nil || !info.Mode().IsRegular() { return c, fmt.Errorf("mcode: runtime program unavailable") @@ -42,10 +42,6 @@ func ConfigureLocal(binary, node, bridge, root, workspace string, network agentn if err != nil || !bound.IsDir() { return c, fmt.Errorf("mcode: workspace directory unavailable") } - - if err := os.MkdirAll(c.Scratch, 0700); err != nil { - return c, err - } return c, nil } @@ -60,13 +56,23 @@ func prepareWorkspaceOptions(runtimeRoot string, c WorkspaceConfig, req proto.Pr if err != nil { return launchOptions{}, err } + values, err := localworkspace.ReadOptionalToolEnvironment() + if err != nil { + return launchOptions{}, err + } + // A disabled web_search control removes web_fetch as well; without the control the native default applies. + searchDisabled := req.ExecutionControls != nil && req.ExecutionControls.WebSearch != nil && req.ExecutionControls.WebSearch.Mode == "disabled" + profile := &workspaceProfile{WebFetch: !searchDisabled} + for _, key := range slices.Sorted(maps.Keys(values)) { + profile.ToolEnv = append(profile.ToolEnv, key+"="+values[key]) + } // Reuse public option validation and private Session state provisioning. - // The native process, ACP Session and workspace tools share the declared cwd. + // The native process runs in the declared workspace. private := req private.LocalEnvironment, private.DisableExecutionEnvironment = nil, true // Public declarations have already been resolved into the transient ACP map. private.MCPHTTPServers = nil - opts, err := prepareOptions(runtimeRoot, private) + opts, err := prepareOptions(runtimeRoot, private, profile) if err != nil { return opts, err } @@ -90,72 +96,6 @@ func prepareWorkspaceOptions(runtimeRoot string, c WorkspaceConfig, req proto.Pr } } } - - raw, err := os.ReadFile(filepath.Join(opts.DataDir, "config.yaml")) - if err != nil { - return opts, err - } - var config map[string]any - if err = json.Unmarshal(raw, &config); err != nil { - return opts, err - } - config["permissionMode"] = "bypassPermissions" - if len(req.LocalEnvironment.Skills) > 0 { - selected := config["agents"].(map[string]any)["default"].(map[string]any) - names := make([]string, 0, len(req.LocalEnvironment.Skills)) - for _, skill := range req.LocalEnvironment.Skills { - names = append(names, skill.Metadata.Name) - } - selected["skills"] = names - for _, key := range []string{"tools", "builtinTools"} { - selected[key] = append(selected[key].([]any), "skill") - } - } - raw, err = json.Marshal(config) - if err != nil { - return opts, err - } - if err = os.WriteFile(filepath.Join(opts.DataDir, "config.yaml"), raw, 0600); err != nil { - return opts, err - } - profile := map[string]any{"capabilityRoot": req.LocalEnvironment.CapabilityRoot, "workspace": "/workspace", "scratch": c.Scratch, "network": c.Network, "allowedDomains": (agentnetwork.Policy{Access: c.Network, AllowedDomains: c.AllowedDomains}).Hosts(), "skills": len(req.LocalEnvironment.Skills) > 0} - - profile["workspace"] = c.Directory - { - file, err := localworkspace.ToolEnvironmentFile() - if err != nil { - return opts, err - } - if file != "" { - profile["toolEnvFile"] = file - } - } - - raw, err = json.Marshal(profile) - if err != nil { - return opts, err - } - path := filepath.Join(opts.DataDir, "workspace-profile.json") - if err = os.WriteFile(path, raw, 0600); err != nil { - return opts, err - } - opts.MCP = []map[string]any{{"name": "oac_workspace", "command": c.Node, "args": []string{c.Bridge, path}, "env": []map[string]string{}}} - opts.MCP = append(opts.MCP, servers...) - if !req.DisableSubagents { - // This native data directory belongs to one public Session and its - // descendants. ACP's ephemeral server map otherwise covers only root. - configured := map[string]any{} - for _, server := range opts.MCP[:1] { - name, _ := server["name"].(string) - configured[name] = map[string]any{"type": "stdio", "command": server["command"], "args": server["args"], "env": map[string]string{}, "enabled": true} - } - raw, err := json.Marshal(map[string]any{"mcpServers": configured}) - if err != nil { - return opts, err - } - if err := os.WriteFile(filepath.Join(opts.DataDir, "mcp.json"), raw, 0o600); err != nil { - return opts, err - } - } + opts.MCP = servers return opts, nil } diff --git a/apps/daemon/internal/agent/mcode/workspace_config_test.go b/apps/daemon/internal/agent/mcode/workspace_config_test.go new file mode 100644 index 000000000..ee6aa6bd0 --- /dev/null +++ b/apps/daemon/internal/agent/mcode/workspace_config_test.go @@ -0,0 +1,73 @@ +package mcode + +import ( + "encoding/json" + "os" + "path/filepath" + "reflect" + "slices" + "testing" + + "github.com/MiniMax-AI/OpenAgentCore/internal/agentdaemon/proto" +) + +func readNativeCapabilities(t *testing.T, dataDir string) (map[string]any, map[string]any) { + t.Helper() + raw, err := os.ReadFile(filepath.Join(dataDir, "config.yaml")) + if err != nil { + t.Fatal(err) + } + var config map[string]any + if err := json.Unmarshal(raw, &config); err != nil { + t.Fatal(err) + } + return config, config["agents"].(map[string]any)["default"].(map[string]any) +} + +func TestWorkspaceRunsNativeToolsWithoutExtraServers(t *testing.T) { + native := []any{"read", "write", "edit", "bash", "grep", "glob", "todowrite"} + for _, test := range []struct { + name string + subagents, disabled bool + tools []any + }{ + {"single agent", false, false, append(slices.Clone(native), "web_fetch")}, + {"subagents", true, false, append(slices.Clone(native), "web_fetch")}, + {"search disabled", true, true, native}, + } { + t.Run(test.name, func(t *testing.T) { + c, req, _ := workspaceFixture(t) + if test.subagents { + dir := t.TempDir() + for _, name := range []string{"node", "subagent-snapshot.mjs"} { + if err := os.WriteFile(filepath.Join(dir, name), nil, 0700); err != nil { + t.Fatal(err) + } + } + t.Setenv("OAC_RUNTIME_MCODE_NODE", filepath.Join(dir, "node")) + t.Setenv("OAC_RUNTIME_MCODE_COMPANION", dir) + limit := 2 + req.DisableSubagents, req.MaxConcurrentSubagents = false, &limit + } + if test.disabled { + req.ExecutionControls = &proto.ExecutionControls{WebSearch: &proto.WebSearchControl{Mode: "disabled", ContextSize: "medium"}} + } + opts, err := prepareWorkspaceOptions(testStateRoot(t), c, req) + if err != nil { + t.Fatal(err) + } + config, capabilities := readNativeCapabilities(t, opts.DataDir) + features := capabilities["features"].(map[string]any) + if _, ok := capabilities["skills"]; ok || !reflect.DeepEqual(capabilities["tools"], test.tools) || features["delegation"] != test.subagents || + config["permissionMode"] != "bypassPermissions" || len(opts.MCP) != 0 || opts.Dir != c.Directory { + t.Fatalf("unexpected native configuration: %v %v", config, opts.MCP) + } + if !slices.Contains(opts.Env, "OAC_RUNTIME_MCODE_BACKGROUND_BASH=off") { + t.Fatal("background bash is not disabled") + } + if mcp, err := os.ReadFile(filepath.Join(opts.DataDir, "mcp.json")); err != nil || string(mcp) != `{"mcpServers":{}}` { + t.Fatalf("native MCP configuration: %s %v", mcp, err) + } + }) + } +} diff --git a/apps/daemon/internal/agent/mcode/workspace_native_test.go b/apps/daemon/internal/agent/mcode/workspace_native_test.go new file mode 100644 index 000000000..0b6afc022 --- /dev/null +++ b/apps/daemon/internal/agent/mcode/workspace_native_test.go @@ -0,0 +1,173 @@ +package mcode + +import ( + "context" + "encoding/json" + "os" + "path/filepath" + "strings" + "testing" + "time" + + "github.com/MiniMax-AI/OpenAgentCore/internal/agentdaemon/proto" +) + +// This opt-in test makes real model calls with the native tools in a workspace. +// It needs the built companion (OAC_RUNTIME_MCODE_COMPANION, OAC_RUNTIME_MCODE_NODE), the native CLI +// (OAC_RUNTIME_MCODE_BIN) and private provider options (OAC_TEST_MCODE_REAL_OPTIONS). +func TestNativeMCodeWorkspaceTools(t *testing.T) { + binary, node, companion, options := os.Getenv("OAC_RUNTIME_MCODE_BIN"), os.Getenv("OAC_RUNTIME_MCODE_NODE"), os.Getenv("OAC_RUNTIME_MCODE_COMPANION"), os.Getenv("OAC_TEST_MCODE_REAL_OPTIONS") + if binary == "" || node == "" || companion == "" || options == "" { + t.Skip("native executable, companion and private provider options required") + } + raw, err := os.ReadFile(options) + if err != nil { + t.Fatal("private provider options unavailable") + } + type result struct { + workspace, content string + calls map[string][]proto.ToolCallPayload + events map[string]int + } + // run executes one Turn; a nil config selects the none placement. + run := func(t *testing.T, c *WorkspaceConfig, search *proto.WebSearchControl, prompt string) result { + t.Helper() + ctx, cancel := context.WithTimeout(t.Context(), 6*time.Minute) + defer cancel() + workspace := t.TempDir() + if c != nil { + c.Directory = workspace + } + req := testRequest(t) + req.RunID, req.Input = "", nil + if json.Unmarshal(raw, &req.AgentOptions) != nil { + t.Fatal("invalid private provider options") + } + limit := 2 + req.DisableSubagents, req.MaxConcurrentSubagents = false, &limit + if c != nil { + req.DisableExecutionEnvironment = false + req.LocalEnvironment = &proto.LocalEnvironment{ID: "environment", NetworkAccess: "enabled", WorkspaceRoot: workspace} + } + if search != nil { + req.ExecutionControls = &proto.ExecutionControls{WebSearch: search} + } + if err := os.WriteFile(filepath.Join(workspace, "notes.txt"), []byte("alpha\n"), 0600); err != nil { + t.Fatal(err) + } + value, err := NewExecutorFactory(testStateRoot(t), c)(ctx, req) + if err != nil { + t.Fatal("native Executor preparation failed:", err) + } + e := value.(*executor) + defer func() { + cleanup, stop := context.WithTimeout(context.Background(), 15*time.Second) + defer stop() + if e.Close(cleanup) != nil { + t.Error("native owner cleanup failed") + } + }() + out := make(chan proto.Envelope, 1024) + turn, err := e.StartTurn(ctx, "turn", proto.TextInput(prompt), out) + if err != nil { + t.Fatal(err) + } + r := result{workspace: workspace, calls: map[string][]proto.ToolCallPayload{}, events: map[string]int{}} + for event := range out { + r.events[event.Type]++ + switch event.Type { + case proto.TypeError: + t.Fatalf("native Turn reported an error: %s", event.Payload) + case proto.TypeToolCall: + var call proto.ToolCallPayload + if json.Unmarshal(event.Payload, &call) != nil { + t.Fatal("invalid tool call") + } + r.calls[call.ID] = append(r.calls[call.ID], call) + case proto.TypeDone: + var done proto.DonePayload + _ = json.Unmarshal(event.Payload, &done) + r.content = done.Content + } + } + if _, err := turn.AwaitSettlement(ctx); err != nil { + t.Fatal(err) + } + return r + } + workspace := &WorkspaceConfig{Binary: binary, Node: node, Companion: companion, Network: "enabled"} + // finished returns the final observation of each command call, keyed by command text. + finished := func(calls map[string][]proto.ToolCallPayload) map[string]*proto.ToolObservation { + result := map[string]*proto.ToolObservation{} + for _, stages := range calls { + if last := stages[len(stages)-1]; last.Stage == "after" && last.Observation != nil && last.Observation.Kind == "command" { + result[last.Observation.Command] = last.Observation + } + } + return result + } + t.Run("files and commands", func(t *testing.T) { + r := run(t, workspace, nil, "Use your tools. 1) Read notes.txt. 2) Edit notes.txt, replacing alpha with beta. 3) Run exactly this bash command: echo bash-ok > out.txt. 4) Run exactly this bash command: sh -c 'exit 3'. Then reply DONE.") + notes, _ := os.ReadFile(filepath.Join(r.workspace, "notes.txt")) + out, _ := os.ReadFile(filepath.Join(r.workspace, "out.txt")) + if strings.TrimSpace(string(notes)) != "beta" || strings.TrimSpace(string(out)) != "bash-ok" { + t.Fatalf("workspace not changed by native tools: %q %q", notes, out) + } + commands := finished(r.calls) + write, failed := commands["echo bash-ok > out.txt"], commands["sh -c 'exit 3'"] + if write == nil || write.Status != "completed" || write.ExitCode == nil || *write.ExitCode != 0 || write.Cwd == nil || *write.Cwd != r.workspace { + t.Fatalf("command observation: %+v", write) + } + if failed == nil || failed.Status != "failed" || failed.ExitCode != nil || failed.Output == nil { + t.Fatalf("failed command observation: %+v", failed) + } + }) + t.Run("bash stays in the foreground", func(t *testing.T) { + r := run(t, workspace, nil, "Use bash. 1) Run exactly this command and wait for it: sleep 20; echo slow-done. 2) Call bash with run_in_background set to true and the command: touch bg-ran. Report both tool replies.") + slow := finished(r.calls)["sleep 20; echo slow-done"] + if slow == nil || slow.Status != "completed" || !strings.Contains(string(slow.Output), "slow-done") { + t.Fatalf("slow command was not completed in the foreground: %+v", slow) + } + background := false + for _, stages := range r.calls { + if stages[0].Args["run_in_background"] == true { + background = true + if last := stages[len(stages)-1]; last.Observation == nil || last.Observation.Status != "failed" { + t.Fatalf("background bash was not refused: %+v", last.Observation) + } + } + } + time.Sleep(3 * time.Second) + if _, err := os.Stat(filepath.Join(r.workspace, "bg-ran")); err == nil || !background { + t.Fatal("background bash ran or was never requested") + } + }) + t.Run("Subagents use the native tools", func(t *testing.T) { + r := run(t, workspace, nil, "Use the task tool to start one worker Subagent with this instruction: run the bash command `echo child-ok > child.txt`, then reply done. Wait for its result, then reply DONE.") + child, _ := os.ReadFile(filepath.Join(r.workspace, "child.txt")) + if strings.TrimSpace(string(child)) != "child-ok" || r.events[proto.TypeSubagentIdentity] == 0 || r.events[proto.TypeSubagentItem] == 0 { + t.Fatalf("Subagent did not run in the workspace: %q %v", child, r.events) + } + }) + t.Run("web search control", func(t *testing.T) { + const prompt = "Without calling any tool, list the exact names of every tool you can call, comma separated." + tools := run(t, workspace, nil, prompt).content + disabled := run(t, workspace, &proto.WebSearchControl{Mode: "disabled", ContextSize: "medium"}, prompt).content + if !strings.Contains(tools, "web_fetch") || !strings.Contains(tools, "bash") || strings.Contains(disabled, "web_fetch") || !strings.Contains(disabled, "bash") { + t.Fatalf("tool inventories: %q / %q", tools, disabled) + } + for _, name := range []string{"website_deploy", "ask_user", "mavis", "web_search"} { + if strings.Contains(tools, name) { + t.Fatalf("%s is available: %q", name, tools) + } + } + }) + t.Run("no Environment has no builtin tools", func(t *testing.T) { + tools := run(t, nil, nil, "Without calling any tool, list the exact names of every tool you can call, comma separated.").content + for _, name := range []string{"bash", "read", "write", "edit", "web_fetch"} { + if strings.Contains(tools, name) { + t.Fatalf("%s is available: %q", name, tools) + } + } + }) +} diff --git a/apps/daemon/internal/agent/mcode/workspace_readiness.go b/apps/daemon/internal/agent/mcode/workspace_readiness.go index 7f44d672a..5621f588a 100644 --- a/apps/daemon/internal/agent/mcode/workspace_readiness.go +++ b/apps/daemon/internal/agent/mcode/workspace_readiness.go @@ -16,7 +16,7 @@ import ( func CheckWorkspace(ctx context.Context, c WorkspaceConfig) error { ctx, cancel := context.WithTimeout(ctx, 15*time.Second) defer cancel() - p, err := clirunner.Start(clirunner.StartOptions{Parent: ctx, Binary: c.Node, Args: []string{filepath.Join(filepath.Dir(c.Bridge), "check.mjs")}, Env: executionEnvironment()}) + p, err := clirunner.Start(clirunner.StartOptions{Parent: ctx, Binary: c.Node, Args: []string{filepath.Join(c.Companion, "check.mjs")}, Env: executionEnvironment()}) if err != nil { return err } @@ -40,7 +40,7 @@ func ValidateWorkspaceReadiness(raw []byte) error { Protocol int `json:"protocol"` Native, Source string } - if len(raw) > 4096 || json.Unmarshal(raw, &info) != nil || info.Protocol != 2 || info.Native != SupportedVersion || info.Source != "33b259bbbeb1c16433390869938191d09bdb0680" { + if len(raw) > 4096 || json.Unmarshal(raw, &info) != nil || info.Protocol != 3 || info.Native != SupportedVersion || info.Source != "33b259bbbeb1c16433390869938191d09bdb0680" { return fmt.Errorf("mcode: workspace companion check failed") } return nil diff --git a/apps/daemon/internal/agent/mcode/workspace_readiness_test.go b/apps/daemon/internal/agent/mcode/workspace_readiness_test.go index 6fca32fe8..910a4d78d 100644 --- a/apps/daemon/internal/agent/mcode/workspace_readiness_test.go +++ b/apps/daemon/internal/agent/mcode/workspace_readiness_test.go @@ -14,7 +14,7 @@ func TestWorkspaceReadinessRejectsOldCleanupContract(t *testing.T) { if runtime.GOOS == "windows" { t.Skip("MiniMax adapter supports Linux and macOS") } - for _, protocol := range []int{1, 2} { + for _, protocol := range []int{2, 3} { t.Run(fmt.Sprint(protocol), func(t *testing.T) { dir := t.TempDir() node := filepath.Join(dir, "node") @@ -22,8 +22,8 @@ func TestWorkspaceReadinessRejectsOldCleanupContract(t *testing.T) { if err := os.WriteFile(node, []byte(body), 0700); err != nil { t.Fatal(err) } - err := CheckWorkspace(context.Background(), WorkspaceConfig{Node: node, Bridge: filepath.Join(dir, "bridge.mjs")}) - if (err == nil) != (protocol == 2) { + err := CheckWorkspace(context.Background(), WorkspaceConfig{Node: node, Companion: dir}) + if (err == nil) != (protocol == 3) { t.Fatalf("protocol %d readiness: %v", protocol, err) } }) @@ -32,9 +32,9 @@ func TestWorkspaceReadinessRejectsOldCleanupContract(t *testing.T) { func TestValidateWorkspaceReadinessRejectsInvalidDescriptors(t *testing.T) { for _, raw := range []string{"", "null", "{", strings.Repeat(" ", 4097), - `{"protocol":1,"native":"0.4.12","source":"33b259bbbeb1c16433390869938191d09bdb0680"}`, - `{"protocol":2,"native":"0.4.11","source":"33b259bbbeb1c16433390869938191d09bdb0680"}`, - `{"protocol":2,"native":"0.4.12","source":"other"}`} { + `{"protocol":2,"native":"0.4.12","source":"33b259bbbeb1c16433390869938191d09bdb0680"}`, + `{"protocol":3,"native":"0.4.11","source":"33b259bbbeb1c16433390869938191d09bdb0680"}`, + `{"protocol":3,"native":"0.4.12","source":"other"}`} { if err := ValidateWorkspaceReadiness([]byte(raw)); err == nil || err.Error() != "mcode: workspace companion check failed" { t.Fatal("invalid descriptor accepted or exposed", err) } diff --git a/apps/daemon/internal/agent/mcode/workspace_skills_test.go b/apps/daemon/internal/agent/mcode/workspace_skills_test.go index 1126eaac9..c063c6553 100644 --- a/apps/daemon/internal/agent/mcode/workspace_skills_test.go +++ b/apps/daemon/internal/agent/mcode/workspace_skills_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "os" "path/filepath" - "reflect" + "slices" "testing" "github.com/MiniMax-AI/OpenAgentCore/internal/agentcapabilities" @@ -42,17 +42,16 @@ func TestWorkspaceSkillsUseSelectedSnapshotAndNativeLoader(t *testing.T) { } var config struct { Agents map[string]struct { - Skills, Tools, BuiltinTools []string + Skills, Tools []string } } if err := json.Unmarshal(raw, &config); err != nil { t.Fatal(err) } selected := config.Agents["default"] - if !reflect.DeepEqual(selected.Skills, []string{"proof"}) || - !reflect.DeepEqual(selected.Tools, []string{"skill"}) || - !reflect.DeepEqual(selected.BuiltinTools, []string{"skill"}) { - t.Fatalf("native selection lost the installed Skill: %+v", selected) + // Skill names are not native IDs, so the selection stays open and no tool list carries "skill". + if selected.Skills != nil || slices.Contains(selected.Tools, "skill") || !slices.Contains(selected.Tools, "bash") { + t.Fatalf("native selection does not leave the installed Skill available: %+v", selected) } } } diff --git a/contracts/agents-api/execution-tools.md b/contracts/agents-api/execution-tools.md index 17e613bd8..913f8df1f 100644 --- a/contracts/agents-api/execution-tools.md +++ b/contracts/agents-api/execution-tools.md @@ -68,7 +68,7 @@ Execution carries the resolved values to the Harness and validates them against | --- | --- | --- | | Codex | `disabled`, `cached` and `live`, with `context_size`, `allowed_domains` and `location`, as native `web_search` and `tools.web_search` settings | `enabled: true` turns on native `code_mode`; `false` turns off `code_mode`, `code_mode_only` and `code_mode_prewarm` and checks native managed requirements before a thread starts or resumes | | Claude Code | `disabled` and `live`, with `context_size: "medium"` and no `allowed_domains` or `location`. On a workspace placement the native `claude_code` tool set runs without ask-user, scheduling, workflow, background and `ListAgents` tools; `disabled` removes `WebSearch` and `WebFetch`, and `live` keeps them. `environment: none` keeps no built-in tools and rejects `live` | `false` only | -| MiniMax Code | `disabled` only, through the native `features.webSearch=false` because the restricted profile has no search tool | `false` only | +| MiniMax Code | `disabled` only, through the native `features.webSearch=false` because native search needs a MiniMax account credential. On a workspace placement the native tools run, and `disabled` also removes `web_fetch` while an omitted control keeps it. `environment: none` has no built-in tools | `false` only | ## HTTP MCP diff --git a/contracts/agents-api/subagents.md b/contracts/agents-api/subagents.md index aaa4b055f..b9badee82 100644 --- a/contracts/agents-api/subagents.md +++ b/contracts/agents-api/subagents.md @@ -56,6 +56,6 @@ An adapter freezes root output before child settlement, keeps its native owner a **Claude Code.** The pinned SDK's native Agent and SendMessage calls run the single child type `oac_worker`, which inherits the model and, on a workspace placement, the parent's native tools, and otherwise has only Agent and SendMessage; the bridge's `subagent_resources` feature gates it. Children use native Bash with the parent's launching-user permissions. Private child records establish parentage, the first own input time and later own Turns; inherited parent context is excluded. The query owner admits children before start and keeps their history through settlement. Confirmed cancellation writes an immutable effect receipt because a native abort can leave no terminal record. There is no close operation: completed or cancelled children stay active. Messages to running children, background work, other child profiles and per-call model overrides are rejected. The [Claude SDK adapter](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/packages/claude-sdk-adapter/README.md#subagents) documents the details. -**MiniMax Code.** Native ACP delegation (`task`, `task_append`, `task_stop`) creates children. Session-private SQLite records supply child identity, accepted inputs, terminal times and own messages. The native task-create transaction enforces the concurrency limit before start, and native preparation must acknowledge that limit and the restricted tool profile before any model input. There is no close or reopen, and native workers do not delegate nested work. +**MiniMax Code.** Native ACP delegation (`task`, `task_append`, `task_stop`) creates children. Session-private SQLite records supply child identity, accepted inputs, terminal times and own messages. The native task-create transaction enforces the concurrency limit before start, and native preparation must acknowledge that limit and that background Bash is off before any model input. There is no close or reopen, and native workers do not delegate nested work. Claude and MiniMax publish verified child history at settlement, not as continuous child progress. Root-completion propagation to children, complete child-delta ordering and unbounded background work across root Turns are not established. diff --git a/contracts/agents-api/zh/execution-tools.md b/contracts/agents-api/zh/execution-tools.md index b55b2bc58..d64bb977d 100644 --- a/contracts/agents-api/zh/execution-tools.md +++ b/contracts/agents-api/zh/execution-tools.md @@ -1,7 +1,7 @@ --- title: "执行工具" source: contracts/agents-api/execution-tools.md -source_hash: ad7fb570c29fc3d346ad406e794b15c7bdabd667634b2ac0e32506e52d964aaa +source_hash: 6b0ae99f9482a8082d20369ebe1facbc262718426dfc5d697ae63bc47ae39853 --- Agent 在 `tools` 中声明应用函数、控制项和 MCP 服务器,并可在 `text.format` 中声明输出 schema。本契约说明 Core 如何验证声明、哪些内容跨越 Runtime 边界,以及调用方如何恢复待执行操作。[Harness 能力](harness-capabilities.md)列出各 Harness 在不同部署位置支持的操作。原生工作区工具和 Environment Plugin MCP 属于 [Environment](environments.md#skills-plugins-and-environment-mcp)。 @@ -70,7 +70,7 @@ Core 发送 `PromptRequestPayload.ToolSearch` 和每个 `FunctionTool.DeferLoadi | --- | --- | --- | | Codex | `disabled`、`cached` 和 `live`,以及 `context_size`、`allowed_domains` 和 `location`,对应原生 `web_search` 与 `tools.web_search` 设置 | `enabled: true` 开启原生 `code_mode`;`false` 关闭 `code_mode`、`code_mode_only` 和 `code_mode_prewarm`,并在启动或恢复线程前检查原生受管要求 | | Claude Code | `disabled` 和 `live`,需使用 `context_size: "medium"`,且不支持 `allowed_domains` 或 `location`。在工作区位置运行原生 `claude_code` 工具集,但移除向用户提问、定时、工作流、后台和 `ListAgents` 工具;`disabled` 会移除 `WebSearch` 和 `WebFetch`,`live` 保留它们。`environment: none` 不保留内置工具,并拒绝 `live` | 仅 `false` | -| MiniMax Code | 仅 `disabled`,受限配置没有搜索工具,通过原生 `features.webSearch=false` 实现 | 仅 `false` | +| MiniMax Code | 仅 `disabled`,通过原生 `features.webSearch=false` 实现,因为原生搜索需要 MiniMax 账号凭据。在工作区位置上运行原生工具,`disabled` 还会移除 `web_fetch`,省略该控制项则保留。`environment: none` 没有内置工具 | 仅 `false` | ## HTTP MCP {#http-mcp} diff --git a/contracts/agents-api/zh/subagents.md b/contracts/agents-api/zh/subagents.md index f84d00619..e41cf6022 100644 --- a/contracts/agents-api/zh/subagents.md +++ b/contracts/agents-api/zh/subagents.md @@ -1,7 +1,7 @@ --- title: "子 Agent" source: contracts/agents-api/subagents.md -source_hash: 2d566c29c98abe79571dc8dbdb43629b8cf87487a3ebc4c23b290a08ef336e0c +source_hash: 130f61f7c544d334df4e80734049bea624951d861a786995196735c8ecf14758 --- 启用 `multi_agent.enabled` 后,Harness 可以启动原生子 Agent。Core 通过锁定版本的 SDK(见 [`upstream.json`](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/contracts/agents-api/upstream.json))中的六项 Subagent 读取操作公开它们,并根据适配器观察结果记录它们。当 `multi_agent.enabled=false` 时,Runtime 会移除原生子 Agent 工具。[Harness capabilities](harness-capabilities.md) 列出各 Harness 在哪些组合中支持子 Agent。 @@ -58,6 +58,6 @@ Core 根据已授权的 Session 绑定关系分配合公开 ID 和所有权。 **Claude Code.** 锁定版本 SDK 的原生 Agent 和 SendMessage 调用会运行唯一的子级类型 `oac_worker`;该类型继承模型;在工作区位置上继承父级的原生工具,否则只有 Agent 和 SendMessage;bridge 的 `subagent_resources` 特性控制是否启用它。子级使用原生 Bash,权限继承自父级的启动用户。私有子级记录用于确定父子关系、首次自身输入时间及后续自身 Turns;继承的父级上下文会被排除。查询所有者会在启动前接纳子级,并将子级历史保留至工作收敛。已确认的取消操作会写入不可变的操作回执,因为原生中止可能不会留下终态记录。不存在关闭操作:已完成或已取消的子级保持 active 状态。向运行中的子级发送消息、使用后台工作、采用其他子级配置以及按次覆盖模型都会被拒绝。[Claude SDK adapter](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/packages/claude-sdk-adapter/README.md#subagents) 记录了详细信息。 -**MiniMax Code.** 原生 ACP 委派(`task`、`task_append`、`task_stop`)会创建子级。Session 私有 SQLite 记录提供子级身份、已接受的输入、终态时间和自身消息。原生 task-create 事务会在启动前强制执行并发限制,并且原生准备过程必须在任何模型输入前确认该限制和受限工具配置。不存在关闭或重新打开操作,原生工作器也不会委派嵌套工作。 +**MiniMax Code.** 原生 ACP 委派(`task`、`task_append`、`task_stop`)会创建子级。Session 私有 SQLite 记录提供子级身份、已接受的输入、终态时间和自身消息。原生 task-create 事务会在启动前强制执行并发限制,并且原生准备过程必须在任何模型输入前确认该限制并确认后台 Bash 已关闭。不存在关闭或重新打开操作,原生工作器也不会委派嵌套工作。 Claude 和 MiniMax 在子级工作收敛时发布经验证的子级历史,而不是持续发布子级进度。向子级传播根级完成状态、完整的子级增量排序以及跨根级 Turns 的无界后台工作均尚未得到支持。 diff --git a/docs/development.md b/docs/development.md index b886ae8a8..8e136b683 100644 --- a/docs/development.md +++ b/docs/development.md @@ -38,7 +38,7 @@ export OAC_TEST_OFFICIAL_SDK_PYTHON="$PWD/.venv/bin/python" ### Node dependency boundaries -Each pnpm module owns a `pnpm-lock.yaml` beside its `package.json`. Website and the Claude Code adapter are independent pnpm projects with their own workspace boundaries. The Web/example/client workspace uses `sharedWorkspaceLockfile: false`: it links declared workspace dependencies without sharing dependency resolution or a virtual store. Root scripts only orchestrate module commands and have no installed tool dependencies. Declare build and test tools in the module that imports or executes them. The MiniMax companion uses its own npm manifest and `package-lock.json` and is outside the pnpm workspace. +Each pnpm module owns a `pnpm-lock.yaml` beside its `package.json`. Website and the Claude Code adapter are independent pnpm projects with their own workspace boundaries. The Web/example/client workspace uses `sharedWorkspaceLockfile: false`: it links declared workspace dependencies without sharing dependency resolution or a virtual store. Root scripts only orchestrate module commands and have no installed tool dependencies. Declare build and test tools in the module that imports or executes them. The MiniMax companion has no dependencies and is outside the pnpm workspace. Install one module with `pnpm --dir website install --frozen-lockfile`, or include its workspace dependencies with `pnpm --filter @oac/web... install --frozen-lockfile`. Add or update dependencies through the same package filter and commit that module's manifest and lockfile. `make node-deps` installs all pnpm modules for full local validation. Web and the example share `packages/agents-client` through explicit `workspace:*` dependencies; their checks install that client too. Component Make targets use filtered installs and checks. CI caches use only the job's dependency locks; the [CI selection policy](./maintainers.md#continuous-integration) owns which checks a change selects. diff --git a/docs/zh/development.md b/docs/zh/development.md index 555298b89..42dcb22f6 100644 --- a/docs/zh/development.md +++ b/docs/zh/development.md @@ -1,7 +1,7 @@ --- title: "开发 OpenAgentCore" source: docs/development.md -source_hash: b3296dd624ef1cd6f4fc474c664d37380762b095a8ef4ed2bfa57cc77c5a6bad +source_hash: 1e525c548fb44a07bcd8331b3b3768fd144c1bf029bcdc1ac9d38f9c48435b9d --- 准备工作副本,构建组件并验证修改。如需使用已安装的实例,从[概览](getting-started/index.md)开始。修改代码前阅读[贡献者规则](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/CONTRIBUTING.md)。 @@ -40,7 +40,7 @@ export OAC_TEST_OFFICIAL_SDK_PYTHON="$PWD/.venv/bin/python" ### Node 依赖边界 {#node-dependency-boundaries} -每个 pnpm 模块在 `package.json` 旁拥有自己的 `pnpm-lock.yaml`。Website 和 Claude Code adapter 是独立 pnpm 项目,各自拥有工作区边界。Web/example/client 工作区使用 `sharedWorkspaceLockfile: false`:链接已声明的工作区依赖,但不共享依赖解析或虚拟存储。根脚本仅编排模块命令,不安装工具依赖。构建与测试工具应在导入或执行它们的模块中声明。MiniMax companion 使用自己的 npm manifest 和 `package-lock.json`,位于 pnpm 工作区之外。 +每个 pnpm 模块在 `package.json` 旁拥有自己的 `pnpm-lock.yaml`。Website 和 Claude Code adapter 是独立 pnpm 项目,各自拥有工作区边界。Web/example/client 工作区使用 `sharedWorkspaceLockfile: false`:链接已声明的工作区依赖,但不共享依赖解析或虚拟存储。根脚本仅编排模块命令,不安装工具依赖。构建与测试工具应在导入或执行它们的模块中声明。MiniMax companion 没有依赖,位于 pnpm 工作区之外。 使用 `pnpm --dir website install --frozen-lockfile` 安装一个模块,或使用 `pnpm --filter @oac/web... install --frozen-lockfile` 连同工作区依赖一起安装。通过同一 package filter 添加或更新依赖,并提交该模块的 manifest 与 lockfile。`make node-deps` 安装所有 pnpm 模块,用于完整本地验证。Web 和示例通过明确的 `workspace:*` 依赖共享 `packages/agents-client`;它们的检查也安装该 client。组件 Make target 使用过滤后的安装与检查。CI 缓存仅使用该作业的依赖锁;[CI 选择策略](maintainers.md#continuous-integration) 负责决定修改选择哪些检查。 diff --git a/internal/harnessconfig/mcode/declaration.go b/internal/harnessconfig/mcode/declaration.go index 185fc5b32..5824704c5 100644 --- a/internal/harnessconfig/mcode/declaration.go +++ b/internal/harnessconfig/mcode/declaration.go @@ -74,8 +74,8 @@ var Declaration = harnessconfig.Declaration{ return errors.New("The configured engine does not support public functions.") } for _, server := range mcp { - if server.ServerLabel == "oac_workspace" || server.AllowedTools != nil || server.Required { - return errors.New("The configured engine requires an unreserved MCP label, allowed_tools=null and required=false.") + if server.AllowedTools != nil || server.Required { + return errors.New("The configured engine requires allowed_tools=null and required=false.") } } return nil diff --git a/packages/mcode-harness/README.md b/packages/mcode-harness/README.md index d0893d1c8..8a66d12fd 100644 --- a/packages/mcode-harness/README.md +++ b/packages/mcode-harness/README.md @@ -1,45 +1,31 @@ -# MiniMax Code workspace bridge +# MiniMax Code companion -This companion package lets the daemon run MiniMax Code with OpenAgentCore's workspace. MiniMax Code keeps its own ACP Session, model loop and history. The package supplies a trusted MCP server (`bridge.mjs`), which the daemon registers as `oac_workspace` (its MCP server info names it `oac-workspace`). It exposes six native MiniMax Code tools rooted at the Session's workspace: `workspace_read`, `workspace_write`, `workspace_edit`, `workspace_bash`, `workspace_grep` and `workspace_glob`. The tools run as the daemon's user with ordinary permissions; the package adds no inner sandbox. The [MiniMax Code Runtime](../../services/core/deploy/mcode/README.md) guide owns the Runtime image, configuration and qualified deployment. +This companion package carries what the daemon needs beside the native MiniMax Code CLI: one patch to the pinned CLI source, a readiness check and a read-only Subagent history reader. MiniMax Code keeps its own ACP Session, model loop, tools and history, and the workspace tools are the native ones. The [MiniMax Code Runtime](../../services/core/deploy/mcode/README.md) guide owns the Runtime image, configuration and qualified deployment. The daemon finds the installed package through `OAC_RUNTIME_MCODE_COMPANION`. -One patch script (`patch-native.mjs`) makes three edits to the pinned native CLI source. The SQLite task-admission transaction enforces the daemon's Subagent concurrency limit before child work starts; foreground, background, nested and idle-child append admissions share that transaction, and terminal native tasks release capacity. ACP initialization reports `oac/subagents` metadata: its version, the applied workspace tool policy and the admission limit. The native tool catalog applies the `protected-mcp-v1` tool gate described under [Subagents and cancellation](#subagents-and-cancellation). No second model or scheduling loop is introduced. Hosted public execution is not qualified by this package alone. +## Patch -## Workspace tools +`patch-native.mjs` edits the pinned native CLI source before its upstream build. `native-patch.json` in the artifact records the upstream revision and exact patch hashes. -The native process, its ACP Session and the workspace tools share the Session's workspace as their working directory; native configuration, Skills and history stay in the private Session data directory. Builtin file tools are disabled, so project files are read and written through the bridge's tools. Only the adapter registers this bridge; callers cannot supply its command, profile, working directory or environment. Native diff/undo capture is not provided by this path. Common Files and Artifacts use the same bound workspace. +- The SQLite task-admission transaction enforces the daemon's Subagent concurrency limit before child work starts. Foreground, background, nested and idle-child append admissions share that transaction, and terminal native tasks release capacity. +- Background Bash is native behavior tied to `task_output`. When `OAC_RUNTIME_MCODE_BACKGROUND_BASH=off`, the executor neither promotes Bash to the background nor accepts `run_in_background`, so no work outlives a Turn. +- ACP initialization reports `oac/subagents` metadata: its version, whether background Bash is on and the admission limit. The daemon rejects an unpatched CLI, or one with background Bash on, before accepting model input. -For each tool call, the bridge starts `launch.mjs` with the Session's private profile (`workspace-profile.json`, written by the daemon). The launcher checks that the profile's `workspace` is a canonical absolute path and that `network` is `enabled`, creates the `scratch` directory, and runs the worker in the workspace. An optional `toolEnvFile` supplies the Runtime's frozen tool environment, read only at launch. A present `capabilityRoot` must be a canonical absolute path, and `skills` requires one. A mismatched or invalid profile rejects the call. +No second model or scheduling loop is introduced. Hosted public execution is not qualified by this package alone. ## Build -`source.json` pins the CLI and native tool source. The pinned npm package supplies native runtime dependencies; the CLI is built from source into the same artifact. On Linux x86_64 or macOS arm64: +`source.json` pins the CLI source. The pinned npm package supplies native runtime dependencies; the CLI is built from the patched source with its upstream build script and lockfile. On Linux x86_64 or macOS arm64: ```sh MCODE_NATIVE_SOURCE=/absolute/upstream/checkout MCODE_CLI_DIR=/absolute/pinned/package bash scripts/build-mcode-harness.sh ``` -This standalone companion uses its own npm lock and is excluded from the root pnpm workspace. The build archives the exact source revision, bundles its native tools and installs pinned MCP dependencies. The same source archive builds the CLI with its upstream build script and lockfile; the pinned package supplies only native runtime dependencies. `native-patch.json` in the artifact records the upstream revision and exact patch hashes, and `provenance.json` the hash of every artifact file. The artifact lands in `MCODE_HARNESS_BUILD_DIR`, or a new `${OAC_DEV_HOME:-$HOME/.oac}/build/mcode-harness-` directory. Runtime packaging uses this single CLI artifact and installs it immutably at `/opt/mcode-harness`. +`provenance.json` records the hash of every artifact file. The artifact lands in `MCODE_HARNESS_BUILD_DIR`, or a new `${OAC_DEV_HOME:-$HOME/.oac}/build/mcode-harness-` directory. Runtime packaging uses this single artifact and installs it immutably at `/opt/mcode-harness`. -## Subagents and cancellation +## Subagent history -`subagent-snapshot.mjs` is a daemon-only reader of the private native SQLite history. It opens a read-only transaction, scopes recursive descendants to the bound root, and fails explicitly if a complete snapshot exceeds its bounds. It never executes a model or exposes native history to workspace tools. The adapter freezes root output and keeps the same ACP owner for bounded child settlement. A completed or idle task remains an active public Subagent; native abort is a Turn cancellation and never implies a closed Subagent. - -The bridge owns each launcher until exit. MCP cancellation and transport shutdown stop all owned workers before releasing the bridge. The outer Runtime owns the native process group. Both boundaries require real Docker cancellation tests. - -The daemon sets the protected `protected-mcp-v1` tool policy independently of the concurrency limit. The native catalog applies it to root and child profiles, withholding direct native filesystem and process tools. The Session-private `oac_workspace` MCP server supplies the authorized workspace tools to workers. Other MCP servers keep their native selection rules. Native Explore and Verifier profiles keep their stricter native capability ceiling. ACP initialization reports the applied policy and admission limit; enabled Subagents reject an unpatched CLI before accepting model input. - -Native tool schemas are retained. Text and image results use standard MCP content; video results are rejected. The pinned native CLI may add task and Skill utility tools, so qualification must inspect the actual inventory rather than assume exactly six. +`subagent-snapshot.mjs` is a daemon-only reader of the private native SQLite history. It opens a read-only transaction, scopes recursive descendants to the bound root, and fails explicitly if a complete snapshot exceeds its bounds. It never executes a model. The adapter freezes root output and keeps the same ACP owner for bounded child settlement. A completed or idle task remains an active public Subagent; native abort is a Turn cancellation and never implies a closed Subagent. Native Explore and Verifier profiles keep their stricter native capability ceiling. ## Tests -`make check-mcode-harness` runs the package's Node tests and syntax checks. Qualify changes with the [Harness acceptance checklist](../../contracts/agents-api/harness-onboarding.md#qualify-the-adapter); synthetic probes and native model runs do not complete public Files/Artifacts or independent Core acceptance. - -For the packaged Linux regression, provide an operator-owned private profile and artifact directory, then run `native.test.mjs` inside the qualified Docker Runtime: - -```sh -OAC_TEST_MCODE_NATIVE_PROFILE=/absolute/private-profile.json \ -OAC_TEST_MCODE_NATIVE_ARTIFACT=/opt/mcode-harness \ -node --test packages/mcode-harness/native.test.mjs -``` - -It verifies a writable native TMPDIR, large Bash output retention and a later native Read. The repository gate skips this case without those inputs; it does not replace Docker cancellation or real-model acceptance. +`make check-mcode-harness` runs the package's Node tests and syntax checks. The build applies the patch and fails when the pinned source no longer matches it. Qualify changes with the [Harness acceptance checklist](../../contracts/agents-api/harness-onboarding.md#qualify-the-adapter); synthetic probes and native model runs do not complete public Files/Artifacts or independent Core acceptance. diff --git a/packages/mcode-harness/bridge.mjs b/packages/mcode-harness/bridge.mjs deleted file mode 100644 index 89b9bd3b9..000000000 --- a/packages/mcode-harness/bridge.mjs +++ /dev/null @@ -1,35 +0,0 @@ -import { Server } from '@modelcontextprotocol/sdk/server/index.js'; -import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; -import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js'; -import { readFileSync } from 'node:fs'; -import { isAbsolute } from 'node:path'; -import { ToolExecutor } from './tool-executor.mjs'; - -const profile = process.argv[2]; -if (!profile || !isAbsolute(profile)) throw new Error('Private workspace profile is required'); -const workspace = JSON.parse(readFileSync(profile, 'utf8')).workspace; -const definitions = JSON.parse(readFileSync(new URL('./dist/tools.json', import.meta.url), 'utf8')); -const tools = new Map(definitions.map(tool => ['workspace_' + tool.name, tool])); -const executor = new ToolExecutor(profile); -const server = new Server({ name: 'oac-workspace', version: '1' }, { capabilities: { tools: {} } }); -server.setRequestHandler(ListToolsRequestSchema, async () => ({ - tools: [...tools].map(([name, tool]) => ({ ...tool, name, - description: 'Bound working directory: ' + workspace + '. ' + tool.description })), -})); -server.setRequestHandler(CallToolRequestSchema, async (request, extra) => { - const tool = tools.get(request.params.name); - if (!tool) throw new Error('Unknown workspace tool'); - const result = await executor.execute(tool.name, request.params.arguments, extra.signal); - if (result.content.some(item => item.type !== 'text' && item.type !== 'image')) - return { isError: true, content: [{ type: 'text', text: 'This workspace transport supports text and image results only.' }] }; - return { isError: result.isError === true, - content: result.content.length ? result.content : [{ type: 'text', text: result.text }] }; -}); - -let shutdown; -const close = () => shutdown ??= executor.close().then(() => server.close()); -server.onclose = () => { void close(); }; -process.stdin.on('end', () => { void close(); }); -process.on('SIGTERM', () => { void close(); }); -process.on('SIGINT', () => { void close(); }); -await server.connect(new StdioServerTransport()); diff --git a/packages/mcode-harness/build.mjs b/packages/mcode-harness/build.mjs deleted file mode 100644 index f2adcd897..000000000 --- a/packages/mcode-harness/build.mjs +++ /dev/null @@ -1,31 +0,0 @@ -import { build } from 'esbuild'; -import { readFileSync, writeFileSync, mkdirSync } from 'node:fs'; -import { resolve, dirname } from 'node:path'; -import { fileURLToPath } from 'node:url'; -const here = dirname(fileURLToPath(import.meta.url)); -const source = process.env.MCODE_SOURCE; -if (!source) throw new Error('MCODE_SOURCE is required'); -const revision = '33b259bbbeb1c16433390869938191d09bdb0680'; -if (readFileSync(resolve(source, '.oac-source-revision'), 'utf8').trim() !== revision) - throw new Error('source revision mismatch'); -const paths = JSON.parse(readFileSync(resolve(source, 'tsconfig.standalone.json'))).compilerOptions.paths; -const plugin = { name: 'pinned-native-source', setup(builder) { - builder.onResolve({ filter: /^[^./]/ }, ({ path }) => { - if (path === '@earendil-works/pi-coding-agent') return { path: resolve(here, 'native-pi-tools.ts') }; - if (path.startsWith('@native/')) return { path: resolve(source, 'packages/agent-tools/src/desktop', path.slice(8) + '.ts') }; - if (path.startsWith('@pi/')) return { path: resolve(source, 'third_party/pi-mono/packages/coding-agent/src', path.slice(4) + '.ts') }; - if (paths[path]) return { path: resolve(source, paths[path][0]) }; - }); -}}; -mkdirSync(resolve(here, 'dist'), { recursive: true }); -const result = await build({ entryPoints: [resolve(here, 'worker.ts')], outfile: resolve(here, 'dist/worker.mjs'), - bundle: true, platform: 'node', format: 'esm', target: 'node22', packages: 'external', - plugins: [plugin], tsconfig: resolve(source, 'tsconfig.standalone.json'), metafile: true, - banner: { js: 'import { createRequire as workerCreateRequire } from "node:module"; const require = workerCreateRequire(import.meta.url);' }, - logLevel: 'info' }); -writeFileSync(resolve(here, 'dist/metafile.json'), JSON.stringify(result.metafile, null, 2) + '\n'); -const external = [...new Set(Object.values(result.metafile.outputs).flatMap(o => o.imports) - .filter(i => i.external && !i.path.startsWith('node:')).map(i => i.path))]; -writeFileSync(resolve(here, 'dist/build-evidence.json'), JSON.stringify({ revision, - sourceFiles: Object.keys(result.metafile.inputs).length, external }, null, 2) + '\n'); -console.log(JSON.stringify({ sourceFiles: Object.keys(result.metafile.inputs).length, external })); diff --git a/packages/mcode-harness/cancellation.test.mjs b/packages/mcode-harness/cancellation.test.mjs deleted file mode 100644 index 5910df622..000000000 --- a/packages/mcode-harness/cancellation.test.mjs +++ /dev/null @@ -1,63 +0,0 @@ -import assert from 'node:assert/strict'; -import test from 'node:test'; -import { existsSync } from 'node:fs'; -import { mkdtemp, mkdir, writeFile, readFile, rm } from 'node:fs/promises'; -import { tmpdir } from 'node:os'; -import { dirname, join } from 'node:path'; -import { fileURLToPath, pathToFileURL } from 'node:url'; -import { setTimeout as delay } from 'node:timers/promises'; - -const artifact = process.env.OAC_TEST_MCODE_CLEANUP_ARTIFACT ?? dirname(fileURLToPath(import.meta.url)); -const available = existsSync(join(artifact, 'dist/worker.mjs')); -const quote = value => "'" + value.replaceAll("'", "'\\''") + "'"; - -for (const operation of ['cancel', 'close']) { - test(`native Bash ${operation} settles detached parent and child before returning`, { skip: !available, timeout: 20_000 }, async t => { - const root = await mkdtemp(join(tmpdir(), 'oac-mcode-cancel-')); - const scratch = join(root, 'scratch'); - await mkdir(scratch); - const pids = []; - let cleaned = false; - t.after(async () => { - if (!cleaned) for (const pid of pids) { try { process.kill(pid, 'SIGKILL'); } catch {} } - await rm(root, { recursive: true, force: true }); - }); - await writeFile(join(root, 'ticks.mjs'), ` - import { spawn } from 'node:child_process'; - import { appendFileSync, writeFileSync } from 'node:fs'; - const name=process.argv[2]; - process.on('SIGTERM',()=>{}); - writeFileSync(name+'.pid',String(process.pid)); - if(name==='parent') spawn(process.execPath,['ticks.mjs','child'],{stdio:'inherit'}); - setInterval(()=>appendFileSync(name+'.ticks','tick\\n'),20); - `); - const profile = join(root, 'profile.json'); - await writeFile(profile, JSON.stringify({ workspace: root, scratch, network: 'enabled' })); - const { ToolExecutor } = await import(pathToFileURL(join(artifact, 'tool-executor.mjs'))); - const executor = new ToolExecutor(profile); - t.after(() => executor.close()); - const abort = new AbortController(); - const finished = executor.execute('bash', { command: `${quote(process.execPath)} ticks.mjs parent` }, abort.signal); - const rejected = assert.rejects(finished); - for (const name of ['parent', 'child']) { - let started = false; - for (let i = 0; i < 500; i++) { - try { - if ((await readFile(join(root, name+'.ticks'))).length > 0) { - pids.push(Number(await readFile(join(root, name+'.pid'), 'utf8'))); - started = true; break; - } - } catch {} - await delay(10); - } - assert.equal(started, true, name+' began writing'); - } - if (operation === 'cancel') abort.abort(); else await executor.close(); - await rejected; - const before = await Promise.all(['parent','child'].map(name => readFile(join(root,name+'.ticks'),'utf8'))); - await delay(300); - const after = await Promise.all(['parent','child'].map(name => readFile(join(root,name+'.ticks'),'utf8'))); - assert.deepEqual(after, before, 'cancel receipt preceded native descendant cleanup'); - cleaned = true; - }); -} diff --git a/packages/mcode-harness/check.mjs b/packages/mcode-harness/check.mjs index e77cb1c92..1794221c0 100644 --- a/packages/mcode-harness/check.mjs +++ b/packages/mcode-harness/check.mjs @@ -1,18 +1,15 @@ -import '@modelcontextprotocol/sdk/server/index.js'; import { accessSync, readFileSync } from 'node:fs'; import { fileURLToPath } from 'node:url'; import { execFileSync } from 'node:child_process'; import { createRequire } from 'node:module'; if (!['linux', 'darwin'].includes(process.platform)) throw new Error('MiniMax native execution requires Linux or macOS'); -for (const file of ['bridge.mjs', 'launch.mjs', 'tool-executor.mjs', 'dist/worker.mjs', - 'subagent-snapshot.mjs', - 'native/cli.js', 'native-patch.json']) accessSync(new URL(file, import.meta.url)); +for (const file of ['subagent-snapshot.mjs', 'native/cli.js', 'native-patch.json']) accessSync(new URL(file, import.meta.url)); for (const command of ['bash', 'rg']) execFileSync('which', [command], { stdio: 'ignore' }); const pin = JSON.parse(readFileSync(new URL('source.json', import.meta.url))); const patch = JSON.parse(readFileSync(new URL('native-patch.json', import.meta.url))); if (patch.revision !== pin.revision || !/^[a-f0-9]{64}$/.test(patch.files?.['patch-native.mjs'] ?? '') || - !/^[a-f0-9]{64}$/.test(patch.files?.['native-subagent-admission.mjs'] ?? '')) throw new Error('Native admission patch provenance mismatch'); + !/^[a-f0-9]{64}$/.test(patch.files?.['native-subagent-admission.mjs'] ?? '')) throw new Error('Native patch provenance mismatch'); // Load the installed native closure, never a global addon or user database. const require = createRequire(import.meta.url); try { @@ -20,7 +17,4 @@ try { const database = new Database(':memory:'); database.close(); } catch { throw new Error('Native database dependency check failed'); } -const tools = JSON.parse(execFileSync(process.execPath, - [fileURLToPath(new URL('dist/worker.mjs', import.meta.url)), process.cwd(), '--describe'], { maxBuffer: 1024 * 1024 })); -if (tools.map(t => t.name).sort().join(',') !== 'bash,edit,glob,grep,read,write') throw new Error('Native tool inventory mismatch'); -process.stdout.write(JSON.stringify({ protocol: 2, native: pin.version, source: pin.revision }) + '\n'); +process.stdout.write(JSON.stringify({ protocol: 3, native: pin.version, source: pin.revision }) + '\n'); diff --git a/packages/mcode-harness/launch.mjs b/packages/mcode-harness/launch.mjs deleted file mode 100644 index 3400808ff..000000000 --- a/packages/mcode-harness/launch.mjs +++ /dev/null @@ -1,47 +0,0 @@ -import { spawn } from 'node:child_process'; -import { readFileSync, mkdirSync } from 'node:fs'; -import { isIP } from 'node:net'; -import { dirname, join, isAbsolute, normalize } from 'node:path'; -import { fileURLToPath } from 'node:url'; -const here = dirname(fileURLToPath(import.meta.url)); -const profile = JSON.parse(readFileSync(process.argv[2], 'utf8')); -if (!isAbsolute(profile.workspace) || normalize(profile.workspace) !== profile.workspace || (process.argv[3] && profile.workspace !== process.argv[3])) - throw new Error('Workspace profile does not match execution binding'); -if (profile.network !== 'enabled') throw new Error('Invalid network policy'); -const domains=profile.allowedDomains??[]; -const hostname=/^(?=.{1,253}$)[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)*$/i; -if (!Array.isArray(domains) || (profile.network==='restricted' - ? domains.length<1 || domains.length>100 || domains.some(host=>typeof host!=='string'||!hostname.test(host)||host.trim()!==host||isIP(host)!==0) - : domains.length!==0)) throw new Error('Invalid network domains'); -const baseEnv = {...process.env}; -if (Object.hasOwn(profile, 'toolEnv')) throw new Error('Inline tool environment is unsupported'); -if (profile.toolEnvFile !== undefined) { - // Resolve values only at tool launch, from the Runtime-owned frozen snapshot. - // Never include parser diagnostics: malformed input can contain credentials. - let values; - try { - const path = profile.toolEnvFile; - if (typeof path !== 'string' || !isAbsolute(path) || normalize(path) !== path) throw new Error(); - const raw = readFileSync(path); - if (raw.length > 1024 * 1024) throw new Error(); - values = JSON.parse(raw.toString('utf8')); - if (!values || typeof values !== 'object' || Array.isArray(values) || - Object.entries(values).some(([key, value]) => !key || /[=\x00\r\n]/.test(key) || typeof value !== 'string' || value.includes('\0'))) throw new Error(); - } catch { throw new Error('Runtime tool environment unavailable'); } - Object.assign(baseEnv, values); -} -if (profile.capabilityRoot && (typeof profile.capabilityRoot !== 'string' || !isAbsolute(profile.capabilityRoot) || normalize(profile.capabilityRoot) !== profile.capabilityRoot || profile.capabilityRoot === '/' || /[\\\x00-\x1f\x7f]/.test(profile.capabilityRoot))) throw new Error('Invalid capability root'); -if (profile.skills && !profile.capabilityRoot) throw new Error('Missing capability root'); -let child; -let cancelled=false; -const cancel=()=>{cancelled=true;child?.kill('SIGTERM');}; -process.on('SIGTERM',cancel);process.on('SIGINT',cancel); -try { - mkdirSync(profile.scratch,{recursive:true}); - if (cancelled) throw new Error('Cancelled before workspace tool start'); - child=spawn(process.execPath,[join(here,'dist/worker.mjs'),profile.workspace],{cwd:profile.workspace,env:baseEnv,stdio:['pipe','pipe','pipe']}); - process.stdin.pipe(child.stdin);child.stdout.pipe(process.stdout);child.stderr.pipe(process.stderr); - child.stdin.on('error',()=>cancel()); - const status=await new Promise((resolve,reject)=>{child.on('error',reject);child.on('close',resolve);}); - process.exitCode=cancelled?1:(status??1); -} catch(error) {process.stderr.write(String(error)+'\n');process.exitCode=1;} diff --git a/packages/mcode-harness/launch.test.mjs b/packages/mcode-harness/launch.test.mjs deleted file mode 100644 index a2aa3859c..000000000 --- a/packages/mcode-harness/launch.test.mjs +++ /dev/null @@ -1,28 +0,0 @@ -import assert from 'node:assert/strict'; -import test from 'node:test'; -import {mkdtemp,mkdir,writeFile,copyFile,rm,realpath} from 'node:fs/promises'; -import {tmpdir} from 'node:os'; -import {join} from 'node:path'; -import {ToolExecutor} from './tool-executor.mjs'; - -test('Runtime directly executes host worker with bound cwd and initialized environment',async t=>{ - const root=await realpath(await mkdtemp(join(tmpdir(),'mcode-host-'))); - t.after(()=>rm(root,{recursive:true,force:true})); - await mkdir(join(root,'dist')); - const workspace=join(root,'workspace');await mkdir(workspace); - const launcher=join(root,'launch.mjs');await copyFile(new URL('./launch.mjs',import.meta.url),launcher); - await writeFile(join(root,'dist','worker.mjs'),`import {readFileSync} from 'node:fs';const request=JSON.parse(readFileSync(0,'utf8'));console.log(JSON.stringify({tool_name:request.tool,text:JSON.stringify({cwd:process.cwd(),value:process.env.INITIALIZED}),content:[]}));`); - const profile=join(root,'profile.json'); - const environment=join(root,'runtime-env.json'); - await writeFile(environment,JSON.stringify({INITIALIZED:'ordinary'}),{mode:0o600}); - await writeFile(profile,JSON.stringify({workspace,scratch:join(root,'scratch'),network:'enabled',toolEnvFile:environment})); - const executor=new ToolExecutor(profile,launcher);t.after(()=>executor.close()); - const result=await executor.execute('bash',{}); - assert.deepEqual(JSON.parse(result.text),{cwd:workspace,value:'ordinary'}); - for (const body of ['{\"SECRET\":\"fixture-canary\",', '[]', '{\"KEY\":123}']) { - await writeFile(environment,body); - await assert.rejects(executor.execute('bash',{})); - } - await rm(environment); - await assert.rejects(executor.execute('bash',{})); -}); diff --git a/packages/mcode-harness/native-pi-tools.ts b/packages/mcode-harness/native-pi-tools.ts deleted file mode 100644 index dadd17e4f..000000000 --- a/packages/mcode-harness/native-pi-tools.ts +++ /dev/null @@ -1,5 +0,0 @@ -export { createReadTool } from '@pi/core/tools/read'; -export { createWriteTool } from '@pi/core/tools/write'; -export { createEditTool } from '@pi/core/tools/edit'; -export { createBashTool } from '@pi/core/tools/bash'; -export { getShellConfig } from '@pi/utils/shell'; diff --git a/packages/mcode-harness/native.test.mjs b/packages/mcode-harness/native.test.mjs deleted file mode 100644 index ad34ac463..000000000 --- a/packages/mcode-harness/native.test.mjs +++ /dev/null @@ -1,36 +0,0 @@ -import assert from 'node:assert/strict'; -import test from 'node:test'; -import { readFile } from 'node:fs/promises'; -import { isAbsolute, join, sep } from 'node:path'; -import { pathToFileURL } from 'node:url'; - -const profile = process.env.OAC_TEST_MCODE_NATIVE_PROFILE; -const artifact = process.env.OAC_TEST_MCODE_NATIVE_ARTIFACT; - -test('packaged native tools use writable scratch and retain large output', { - skip: !profile || !artifact, - timeout: 30_000, -}, async t => { - assert.ok(isAbsolute(profile) && isAbsolute(artifact)); - const config = JSON.parse(await readFile(profile, 'utf8')); - const { ToolExecutor } = await import(pathToFileURL(join(artifact, 'tool-executor.mjs'))); - const executor = new ToolExecutor(profile); - t.after(() => executor.close()); - const temporary = await executor.execute('bash', { - command: 'node -p "require(\'os\').tmpdir()"; f=$(mktemp) && printf TEMP_OK > "$f" && cat "$f" && rm "$f"', - }); - assert.notEqual(temporary.isError, true); - assert.ok(temporary.text.includes(config.scratch)); - assert.ok(temporary.text.includes('TEMP_OK')); - const output = await executor.execute('bash', { - command: 'python3 -c "print((\'native-spill-line\' + chr(10)) * 16000, end=\'\')"', - }); - assert.notEqual(output.isError, true); - const full = output.details?.fullOutputPath; - assert.equal(typeof full, 'string'); - assert.ok(full.startsWith(config.scratch + sep)); - assert.equal(await readFile(full, 'utf8'), 'native-spill-line\n'.repeat(16000)); - const read = await executor.execute('read', { path: full, offset: 1, limit: 1 }); - assert.notEqual(read.isError, true); - assert.ok(read.text.includes('native-spill-line')); -}); diff --git a/packages/mcode-harness/package-lock.json b/packages/mcode-harness/package-lock.json deleted file mode 100644 index 24741c0f3..000000000 --- a/packages/mcode-harness/package-lock.json +++ /dev/null @@ -1,1841 +0,0 @@ -{ - "name": "@oac/mcode-harness", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "@oac/mcode-harness", - "dependencies": { - "@modelcontextprotocol/sdk": "1.30.0", - "@pondwader/socks5-server": "1.0.10", - "@silvia-odwyer/photon-node": "0.3.4", - "@sinclair/typebox": "0.34.52", - "chalk": "5.6.2", - "cross-spawn": "7.0.6", - "diff": "8.0.4", - "get-east-asian-width": "1.6.0", - "highlight.js": "10.7.3", - "marked": "18.0.12", - "minimatch": "10.2.6", - "node-forge": "1.4.0", - "typebox": "1.1.38", - "unpdf": "1.8.1", - "zod": "3.25.76" - }, - "devDependencies": { - "esbuild": "0.28.2" - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.2.tgz", - "integrity": "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.2.tgz", - "integrity": "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.2.tgz", - "integrity": "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.2.tgz", - "integrity": "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.2.tgz", - "integrity": "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.2.tgz", - "integrity": "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.2.tgz", - "integrity": "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.2.tgz", - "integrity": "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.2.tgz", - "integrity": "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.2.tgz", - "integrity": "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.2.tgz", - "integrity": "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.2.tgz", - "integrity": "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.2.tgz", - "integrity": "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.2.tgz", - "integrity": "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.2.tgz", - "integrity": "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.2.tgz", - "integrity": "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.2.tgz", - "integrity": "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.2.tgz", - "integrity": "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.2.tgz", - "integrity": "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.2.tgz", - "integrity": "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.2.tgz", - "integrity": "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.2.tgz", - "integrity": "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.2.tgz", - "integrity": "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.2.tgz", - "integrity": "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.2.tgz", - "integrity": "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.2.tgz", - "integrity": "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@hono/node-server": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-2.1.1.tgz", - "integrity": "sha512-ELuehkj5VCBdgEw9zs+ivkKwyzzUCSQuE96YmiPvn1ECBoZCczbFXJLeEGMTYjphP6gydh4pHMqEYPVMYUVgQg==", - "license": "MIT", - "engines": { - "node": ">=20" - }, - "peerDependencies": { - "hono": "^4" - } - }, - "node_modules/@modelcontextprotocol/sdk": { - "version": "1.30.0", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.30.0.tgz", - "integrity": "sha512-xKd8OIzlqNzcqcNumGAa6g+PW2kjD5vrpcKOnfldAUPP3j7lnqMPwlTXQm8gF+UwH72z0lqaRbjr9hqGz0eITA==", - "license": "MIT", - "dependencies": { - "@hono/node-server": "^1.19.9 || ^2.0.5", - "ajv": "^8.17.1", - "ajv-formats": "^3.0.1", - "content-type": "^1.0.5", - "cors": "^2.8.5", - "cross-spawn": "^7.0.5", - "eventsource": "^3.0.2", - "eventsource-parser": "^3.0.0", - "express": "^5.2.1", - "express-rate-limit": "^8.2.1", - "hono": "^4.11.4", - "jose": "^6.1.3", - "json-schema-typed": "^8.0.2", - "pkce-challenge": "^5.0.0", - "raw-body": "^3.0.0", - "zod": "^3.25 || ^4.0", - "zod-to-json-schema": "^3.25.1" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@cfworker/json-schema": "^4.1.1", - "zod": "^3.25 || ^4.0" - }, - "peerDependenciesMeta": { - "@cfworker/json-schema": { - "optional": true - }, - "zod": { - "optional": false - } - } - }, - "node_modules/@pondwader/socks5-server": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/@pondwader/socks5-server/-/socks5-server-1.0.10.tgz", - "integrity": "sha512-bQY06wzzR8D2+vVCUoBsr5QS2U6UgPUQRmErNwtsuI6vLcyRKkafjkr3KxbtGFf9aBBIV2mcvlsKD1UYaIV+sg==", - "license": "MIT" - }, - "node_modules/@silvia-odwyer/photon-node": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/@silvia-odwyer/photon-node/-/photon-node-0.3.4.tgz", - "integrity": "sha512-bnly4BKB3KDTFxrUIcgCLbaeVVS8lrAkri1pEzskpmxu9MdfGQTy8b8EgcD83ywD3RPMsIulY8xJH5Awa+t9fA==", - "license": "Apache-2.0" - }, - "node_modules/@sinclair/typebox": { - "version": "0.34.52", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.52.tgz", - "integrity": "sha512-XiMQh7qqVlxZzcVD+kkGMNGMzcTrDMLWI7S4x7z1MkCkbDPrekpZXEUK0eZqZFMuHQg2a2DZOcDIh9o5v3Gonw==", - "license": "MIT" - }, - "node_modules/accepts": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", - "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", - "license": "MIT", - "dependencies": { - "mime-types": "^3.0.0", - "negotiator": "^1.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/ajv": { - "version": "8.20.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", - "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ajv-formats": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", - "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", - "license": "MIT", - "dependencies": { - "ajv": "^8.0.0" - }, - "peerDependencies": { - "ajv": "^8.0.0" - }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } - } - }, - "node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", - "license": "MIT", - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/body-parser": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz", - "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==", - "license": "MIT", - "dependencies": { - "bytes": "^3.1.2", - "content-type": "^2.0.0", - "debug": "^4.4.3", - "http-errors": "^2.0.1", - "iconv-lite": "^0.7.2", - "on-finished": "^2.4.1", - "qs": "^6.15.2", - "raw-body": "^3.0.2", - "type-is": "^2.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/body-parser/node_modules/content-type": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", - "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/brace-expansion": { - "version": "5.0.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz", - "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==", - "license": "MIT", - "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/call-bound": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/chalk": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", - "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/content-disposition": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", - "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", - "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie-signature": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", - "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", - "license": "MIT", - "engines": { - "node": ">=6.6.0" - } - }, - "node_modules/cors": { - "version": "2.8.6", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", - "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", - "license": "MIT", - "dependencies": { - "object-assign": "^4", - "vary": "^1" - }, - "engines": { - "node": ">= 0.10" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/diff": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.4.tgz", - "integrity": "sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", - "license": "MIT" - }, - "node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-object-atoms": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", - "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/esbuild": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.2.tgz", - "integrity": "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.28.2", - "@esbuild/android-arm": "0.28.2", - "@esbuild/android-arm64": "0.28.2", - "@esbuild/android-x64": "0.28.2", - "@esbuild/darwin-arm64": "0.28.2", - "@esbuild/darwin-x64": "0.28.2", - "@esbuild/freebsd-arm64": "0.28.2", - "@esbuild/freebsd-x64": "0.28.2", - "@esbuild/linux-arm": "0.28.2", - "@esbuild/linux-arm64": "0.28.2", - "@esbuild/linux-ia32": "0.28.2", - "@esbuild/linux-loong64": "0.28.2", - "@esbuild/linux-mips64el": "0.28.2", - "@esbuild/linux-ppc64": "0.28.2", - "@esbuild/linux-riscv64": "0.28.2", - "@esbuild/linux-s390x": "0.28.2", - "@esbuild/linux-x64": "0.28.2", - "@esbuild/netbsd-arm64": "0.28.2", - "@esbuild/netbsd-x64": "0.28.2", - "@esbuild/openbsd-arm64": "0.28.2", - "@esbuild/openbsd-x64": "0.28.2", - "@esbuild/openharmony-arm64": "0.28.2", - "@esbuild/sunos-x64": "0.28.2", - "@esbuild/win32-arm64": "0.28.2", - "@esbuild/win32-ia32": "0.28.2", - "@esbuild/win32-x64": "0.28.2" - } - }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "license": "MIT" - }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/eventsource": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", - "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", - "license": "MIT", - "dependencies": { - "eventsource-parser": "^3.0.1" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/eventsource-parser": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.1.1.tgz", - "integrity": "sha512-EKN1vKAMcZ8MlYMpaNuxN6R9yakzH6uajHcHVTqWJzvu5pWw9DyhbP35HH8MVBQ+dZjAfDxk+A8NiR9KWaXiyQ==", - "license": "MIT", - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/express": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", - "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", - "license": "MIT", - "dependencies": { - "accepts": "^2.0.0", - "body-parser": "^2.2.1", - "content-disposition": "^1.0.0", - "content-type": "^1.0.5", - "cookie": "^0.7.1", - "cookie-signature": "^1.2.1", - "debug": "^4.4.0", - "depd": "^2.0.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "finalhandler": "^2.1.0", - "fresh": "^2.0.0", - "http-errors": "^2.0.0", - "merge-descriptors": "^2.0.0", - "mime-types": "^3.0.0", - "on-finished": "^2.4.1", - "once": "^1.4.0", - "parseurl": "^1.3.3", - "proxy-addr": "^2.0.7", - "qs": "^6.14.0", - "range-parser": "^1.2.1", - "router": "^2.2.0", - "send": "^1.1.0", - "serve-static": "^2.2.0", - "statuses": "^2.0.1", - "type-is": "^2.0.1", - "vary": "^1.1.2" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/express-rate-limit": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.7.0.tgz", - "integrity": "sha512-hOwV7WOxXfjRpAM1DSJWZDXx3GhplwD8IfwuwvogD8i1Qnkgosw/H45s4ZnFAUHDAhPjlY9hLBvJhKmGMyY26g==", - "license": "MIT", - "dependencies": { - "debug": "^4.4.3", - "ip-address": "^10.2.0" - }, - "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://github.com/sponsors/express-rate-limit" - }, - "peerDependencies": { - "express": ">= 4.11" - } - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "license": "MIT" - }, - "node_modules/fast-uri": { - "version": "3.1.8", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.8.tgz", - "integrity": "sha512-GZMtZUTNRpOVIECoXwLNZS5xUGE+mVNbTB8h/7Rwh2TFWcBQiPzTgyZi05BF9UMZKkLJv8XBRJTlU7zg8+ZfMg==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "BSD-3-Clause" - }, - "node_modules/finalhandler": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", - "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", - "license": "MIT", - "dependencies": { - "debug": "^4.4.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "on-finished": "^2.4.1", - "parseurl": "^1.3.3", - "statuses": "^2.0.1" - }, - "engines": { - "node": ">= 18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fresh": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", - "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-east-asian-width": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", - "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hasown": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", - "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/highlight.js": { - "version": "10.7.3", - "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", - "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", - "license": "BSD-3-Clause", - "engines": { - "node": "*" - } - }, - "node_modules/hono": { - "version": "4.13.8", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.13.8.tgz", - "integrity": "sha512-/Gng7NfoykZl2pjukW5Z6+8Yxm3BPRf86GTbQnt0SbySkvax4fyL4H3HhY1cCpBGmiW9XDRFzRV+CXK2W8QudQ==", - "license": "MIT", - "engines": { - "node": ">=16.9.0" - } - }, - "node_modules/http-errors": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", - "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", - "license": "MIT", - "dependencies": { - "depd": "~2.0.0", - "inherits": "~2.0.4", - "setprototypeof": "~1.2.0", - "statuses": "~2.0.2", - "toidentifier": "~1.0.1" - }, - "engines": { - "node": ">= 0.8" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/iconv-lite": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz", - "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "license": "ISC" - }, - "node_modules/ip-address": { - "version": "10.7.2", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.7.2.tgz", - "integrity": "sha512-7H/2gFSIitxc0hG3nOI1glS8QLo/EHBFFLk8vEUjXY/xu0AdL8jZ9U1IzO2PUm0d2D/ofQcAifb0g6OBkt8U7w==", - "license": "MIT", - "engines": { - "node": ">= 12" - } - }, - "node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/is-promise": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", - "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", - "license": "MIT" - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "license": "ISC" - }, - "node_modules/jose": { - "version": "6.2.12", - "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.12.tgz", - "integrity": "sha512-9NiFmJEex0sy2Dk58j2UGBSHgUs2ypF9eZSu4L6vjOX3Dp96Sw1F3uL+H+D1sx02jZZdzUT0HgvCy59CuvXcWw==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/panva" - } - }, - "node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "license": "MIT" - }, - "node_modules/json-schema-typed": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz", - "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==", - "license": "BSD-2-Clause" - }, - "node_modules/marked": { - "version": "18.0.12", - "resolved": "https://registry.npmjs.org/marked/-/marked-18.0.12.tgz", - "integrity": "sha512-LEm4ga2YeI2T3GVHj9b0BaDPPk93LLTHMFeMyQbNIzPxc8vCI0y/scy0ZA6z6lXKyT9j9Nhl/OC6ZYKYGuFScA==", - "license": "MIT", - "bin": { - "marked": "bin/marked.js" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/media-typer": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.1.tgz", - "integrity": "sha512-yz3xRaG20c6/BOzvYoDaGtPmGscs7YivItZEEqe6GbwNfHuxu9YNmvnEkMzKldAGY4/80pRcQRZSEnhquk9XuQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/merge-descriptors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", - "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mime-db": { - "version": "1.54.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", - "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", - "license": "MIT", - "dependencies": { - "mime-db": "^1.54.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/minimatch": { - "version": "10.2.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", - "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", - "license": "BlueOak-1.0.0", - "dependencies": { - "brace-expansion": "^5.0.8" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, - "node_modules/negotiator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.1.0.tgz", - "integrity": "sha512-NMPBRMJgiQHjbd8phG3Vebdx4kZ1H121rbl5IkMqeOsahptB9BKo/d7oJ3zTXqTgagn2bWlNSXkh0QUGM31RYg==", - "license": "MIT", - "dependencies": { - "content-type": "^2.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/negotiator/node_modules/content-type": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", - "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/node-forge": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.4.0.tgz", - "integrity": "sha512-LarFH0+6VfriEhqMMcLX2F7SwSXeWwnEAJEsYm5QKWchiVYVvJyV9v7UDvUv+w5HO23ZpQTXDv/GxdDdMyOuoQ==", - "license": "(BSD-3-Clause OR GPL-2.0)", - "engines": { - "node": ">= 6.13.0" - } - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-inspect": { - "version": "1.13.4", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", - "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "license": "MIT", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-to-regexp": { - "version": "8.4.2", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", - "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/pkce-challenge": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz", - "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==", - "license": "MIT", - "engines": { - "node": ">=16.20.0" - } - }, - "node_modules/proxy-addr": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.8.tgz", - "integrity": "sha512-5nnx0yGyVUcY6t9RnWcARWtwT9F1D8O9rt08htPvnd49W1IgZtmLkhu9WfMzQj1cFxjHIO6connUNVW5k7AVyQ==", - "license": "MIT", - "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/qs": { - "version": "6.16.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.16.0.tgz", - "integrity": "sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==", - "license": "BSD-3-Clause", - "dependencies": { - "es-define-property": "^1.0.1", - "side-channel": "^1.1.1" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/range-parser": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.3.0.tgz", - "integrity": "sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/raw-body": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", - "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", - "license": "MIT", - "dependencies": { - "bytes": "~3.1.2", - "http-errors": "~2.0.1", - "iconv-lite": "~0.7.0", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/router": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", - "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", - "license": "MIT", - "dependencies": { - "debug": "^4.4.0", - "depd": "^2.0.0", - "is-promise": "^4.0.0", - "parseurl": "^1.3.3", - "path-to-regexp": "^8.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "license": "MIT" - }, - "node_modules/send": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", - "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", - "license": "MIT", - "dependencies": { - "debug": "^4.4.3", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "fresh": "^2.0.0", - "http-errors": "^2.0.1", - "mime-types": "^3.0.2", - "ms": "^2.1.3", - "on-finished": "^2.4.1", - "range-parser": "^1.2.1", - "statuses": "^2.0.2" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/serve-static": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", - "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", - "license": "MIT", - "dependencies": { - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "parseurl": "^1.3.3", - "send": "^1.2.0" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "license": "ISC" - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/side-channel": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", - "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.4", - "side-channel-list": "^1.0.1", - "side-channel-map": "^1.0.1", - "side-channel-weakmap": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-list": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", - "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-map": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", - "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-weakmap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", - "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3", - "side-channel-map": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/statuses": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", - "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "license": "MIT", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/type-is": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz", - "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==", - "license": "MIT", - "dependencies": { - "content-type": "^2.0.0", - "media-typer": "^1.1.0", - "mime-types": "^3.0.0" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/type-is/node_modules/content-type": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", - "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/typebox": { - "version": "1.1.38", - "resolved": "https://registry.npmjs.org/typebox/-/typebox-1.1.38.tgz", - "integrity": "sha512-pZ0aQPmMmXoUvSbeuWf/Hzsc+avNw/Zd6VeE8CFgkVGWyuHPJvqeJJDeJqLve+K70LvjYIoleGcoJHPT17cWoA==", - "license": "MIT" - }, - "node_modules/unpdf": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/unpdf/-/unpdf-1.8.1.tgz", - "integrity": "sha512-xkURhy2SoGpOIH0a1gLHNkASPIQYonadDJs2AQwPEfUakafeD9EA1WTWWsaR++gfTCXJpV27W7tU1nXuk82UKQ==", - "license": "MIT", - "engines": { - "node": ">=22" - }, - "peerDependencies": { - "@napi-rs/canvas": "^0.1.69 || ^1.0.0" - }, - "peerDependenciesMeta": { - "@napi-rs/canvas": { - "optional": true - } - } - }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "license": "ISC" - }, - "node_modules/zod": { - "version": "3.25.76", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", - "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - }, - "node_modules/zod-to-json-schema": { - "version": "3.25.2", - "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz", - "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==", - "license": "ISC", - "peerDependencies": { - "zod": "^3.25.28 || ^4" - } - } - } -} diff --git a/packages/mcode-harness/package.json b/packages/mcode-harness/package.json deleted file mode 100644 index c509c7d02..000000000 --- a/packages/mcode-harness/package.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "name": "@oac/mcode-harness", - "private": true, - "type": "module", - "devDependencies": { - "esbuild": "0.28.2" - }, - "dependencies": { - "@pondwader/socks5-server": "1.0.10", - "@silvia-odwyer/photon-node": "0.3.4", - "@sinclair/typebox": "0.34.52", - "chalk": "5.6.2", - "cross-spawn": "7.0.6", - "diff": "8.0.4", - "get-east-asian-width": "1.6.0", - "highlight.js": "10.7.3", - "marked": "18.0.12", - "minimatch": "10.2.6", - "node-forge": "1.4.0", - "typebox": "1.1.38", - "unpdf": "1.8.1", - "zod": "3.25.76", - "@modelcontextprotocol/sdk": "1.30.0" - }, - "scripts": { - "test": "node --test *.test.mjs" - } -} diff --git a/packages/mcode-harness/patch-native.mjs b/packages/mcode-harness/patch-native.mjs index 13700c1bc..7d03ab0b2 100644 --- a/packages/mcode-harness/patch-native.mjs +++ b/packages/mcode-harness/patch-native.mjs @@ -22,22 +22,16 @@ const acpPath = join(root, 'packages/tui/src/acp/agent.ts'); const acp = readFileSync(acpPath, 'utf8'); const marker = " _meta: {\n 'minimax-code/extensions': {"; if (acp.split(marker).length !== 2) throw new Error('Pinned native ACP initialization changed'); -writeFileSync(acpPath, acp.replace(marker, " _meta: {\n 'oac/subagents': { version: 1, workspaceTools: process.env.OAC_RUNTIME_MCODE_TOOL_POLICY ?? null, maxConcurrent: Number(process.env.OAC_RUNTIME_MCODE_MAX_SUBAGENTS ?? 0) },\n 'minimax-code/extensions': {")); -const catalogPath = join(root, 'packages/local-runtime-v2/src/service/turn-system/agent-host/assembly/local-turn-tool-catalog.ts'); -const catalog = readFileSync(catalogPath, 'utf8'); -const gate = " if (!isFeatureEnabled(ceiling, toolName)) return false;"; -if (catalog.split(gate).length !== 2) throw new Error('Pinned native tool capability gate changed'); -const selectorGate = " if (!input.selector.allowsTool(input.tool.def.name, input.options.selectorAlias)) return false;\n if (!isMcpServerAllowed(input.selector, input.options)) return false;"; -if (catalog.split(selectorGate).length !== 2) throw new Error('Pinned native selector gate changed'); -const withWorkspace = catalog.replace(selectorGate, ` const protectedWorkspace = process.env.OAC_RUNTIME_MCODE_TOOL_POLICY === 'protected-mcp-v1' && - input.source === 'configured' && input.options.mcp === true && input.options.serverName === 'oac_workspace'; - if (!protectedWorkspace && !input.selector.allowsTool(input.tool.def.name, input.options.selectorAlias)) return false; - if (!protectedWorkspace && !isMcpServerAllowed(input.selector, input.options)) return false;`); -writeFileSync(catalogPath, withWorkspace.replace(gate, ` if (process.env.OAC_RUNTIME_MCODE_TOOL_POLICY === 'protected-mcp-v1') { - if (source === 'builtin' && toolName !== 'skill' && !DELEGATION_TOOL_NAMES.has(toolName) && !TASK_CONTROL_TOOL_NAMES.has(toolName)) return false; - if (source === 'builtin-matrix') return false; - } -${gate}`)); +writeFileSync(acpPath, acp.replace(marker, " _meta: {\n 'oac/subagents': { version: 2, backgroundBash: process.env.OAC_RUNTIME_MCODE_BACKGROUND_BASH !== 'off', maxConcurrent: Number(process.env.OAC_RUNTIME_MCODE_MAX_SUBAGENTS ?? 0) },\n 'minimax-code/extensions': {")); +// Background bash outlives the Turn. With the adapter's switch set, bash runs in the foreground only; +// task_output stays admitted, so Subagent results remain readable. +const executorPath = join(root, 'packages/local-runtime-v2/src/service/turn-system/agent-host/execution/executor.ts'); +const executor = readFileSync(executorPath, 'utf8'); +const context = " const toolContext = {\n ...toolResolution.context,"; +const promotion = " allowBashAutoPromotion: canConsumeBackgroundBashOutput,\n canConsumeBackgroundBashOutput,\n"; +if (executor.split(context).length !== 2 || executor.split(promotion).length !== 2) throw new Error('Pinned native background bash gate changed'); +writeFileSync(executorPath, executor.replace(context, " const backgroundBash =\n canConsumeBackgroundBashOutput && process.env.OAC_RUNTIME_MCODE_BACKGROUND_BASH !== 'off';\n" + context) + .replace(promotion, " allowBashAutoPromotion: backgroundBash,\n canConsumeBackgroundBashOutput: backgroundBash,\n")); copyFileSync(join(here, 'native-subagent-admission.mjs'), join(root, 'packages/local-runtime/src/background-task/oac-subagent-admission.mjs')); const digest = name => createHash('sha256').update(readFileSync(join(here, name))).digest('hex'); writeFileSync(join(root, '.oac-native-patch.json'), JSON.stringify({ revision: pin.revision, diff --git a/packages/mcode-harness/snapshot.test.mjs b/packages/mcode-harness/snapshot.test.mjs deleted file mode 100644 index e5463f13d..000000000 --- a/packages/mcode-harness/snapshot.test.mjs +++ /dev/null @@ -1,74 +0,0 @@ -import assert from 'node:assert/strict'; -import test from 'node:test'; -import { copyFile, mkdir, mkdtemp, readFile, rm, stat, symlink, writeFile } from 'node:fs/promises'; -import { join } from 'node:path'; -import { tmpdir } from 'node:os'; -import { spawnSync } from 'node:child_process'; - -// This opt-in test requires an isolated Linux Runtime containing the built -// sandbox/worker artifact. It runs only native tools; no model is contacted. -const artifact = process.env.OAC_TEST_MCODE_NATIVE_ARTIFACT; -const isolated = process.env.OAC_TEST_MCODE_SNAPSHOT_ISOLATED === '1'; -test('capability read exceptions preserve denied ancestors and immutable snapshots', { - skip: !artifact || !isolated, timeout: 60_000, -}, async t => { - const fixture = await mkdtemp(join(tmpdir(), 'oac-mcode-snapshot-')); - t.after(() => rm(fixture, { recursive: true, force: true })); - await copyFile(new URL('./launch.mjs', import.meta.url), join(fixture, 'launch.mjs')); - await symlink(join(artifact, 'dist'), join(fixture, 'dist')); - const quote = value => "'" + value.replaceAll("'", "'\\''") + "'"; - for (const [name, ancestor, root] of [ - ['standard', '/environment', '/environment/initialization/capabilities'], - ['custom', join(fixture, 'private-layout'), join(fixture, 'private-layout', 'frozen', 'skills')], - ]) { - await t.test(name, async t => { - const scratch = join(fixture, name + '-scratch'); - const secret = join(ancestor, 'private', 'snapshot-test-secret'); - await mkdir(root, { recursive: true }); - await mkdir(join(ancestor, 'private'), { recursive: true }); - await mkdir(scratch); - const script = join(root, 'proof.sh'), attachment = join(root, 'attachment.txt'); - await writeFile(script, 'printf SNAPSHOT_SCRIPT_READABLE', { mode: 0o700 }); - await writeFile(attachment, 'SNAPSHOT_ATTACHMENT_READABLE', { mode: 0o600 }); - await writeFile(secret, 'PRIVATE_SNAPSHOT_CANARY', { mode: 0o600 }); - const profile = join(fixture, name + '.json'); - const config = { workspace: '/workspace', scratch, network: 'disabled', - allowedDomains: [], protectedDirs: [ancestor], capabilityRoot: root, skills: true }; - await writeFile(profile, JSON.stringify(config)); - const executor = { execute(tool, input) { - const child = spawnSync(process.execPath, [join(fixture, 'launch.mjs'), profile, '/workspace'], { - input: JSON.stringify({ tool, input }), encoding: 'utf8', timeout: 15_000, - env: { PATH: '/usr/local/bin:/usr/bin:/bin', HOME: '/tmp', LANG: 'C.UTF-8' }, - }); - assert.ok(child.stdout, child.stderr || String(child.error)); - return JSON.parse(child.stdout); - } }; - const read = await executor.execute('bash', { - command: 'sh ' + quote(script) + '; cat ' + quote(attachment), - }); - assert.notEqual(read.isError, true, read.text); - assert.ok(read.text.includes('SNAPSHOT_SCRIPT_READABLE')); - assert.ok(read.text.includes('SNAPSHOT_ATTACHMENT_READABLE')); - const protectedResult = await executor.execute('bash', { - command: [ - 'if printf changed >> ' + quote(attachment) + '; then exit 41; fi', - 'if chmod 0777 ' + quote(script) + '; then exit 42; fi', - 'if touch ' + quote(join(root, 'new-sibling')) + '; then exit 43; fi', - 'if cat ' + quote(secret) + '; then exit 44; fi', - 'printf SNAPSHOT_PROTECTION_OK', - ].join('; '), - }); - assert.notEqual(protectedResult.isError, true); - assert.ok(protectedResult.text.includes('SNAPSHOT_PROTECTION_OK')); - assert.ok(!protectedResult.text.includes('PRIVATE_SNAPSHOT_CANARY')); - assert.equal(await readFile(attachment, 'utf8'), 'SNAPSHOT_ATTACHMENT_READABLE'); - assert.equal((await stat(script)).mode & 0o777, 0o700); - await assert.rejects(stat(join(root, 'new-sibling')), { code: 'ENOENT' }); - // Negative control proves that the ancestor deny really masks this root. - await writeFile(profile, JSON.stringify({ ...config, capabilityRoot: undefined, skills: false })); - const hidden = await executor.execute('bash', { command: 'cat ' + quote(attachment) }); - assert.equal(hidden.isError, true); - assert.ok(!hidden.text.includes('SNAPSHOT_ATTACHMENT_READABLE')); - }); - } -}); diff --git a/packages/mcode-harness/tool-executor.mjs b/packages/mcode-harness/tool-executor.mjs deleted file mode 100644 index 16de8ea1f..000000000 --- a/packages/mcode-harness/tool-executor.mjs +++ /dev/null @@ -1,65 +0,0 @@ -import { spawn } from 'node:child_process'; -import { fileURLToPath } from 'node:url'; - -const limit = 16 * 1024 * 1024; -const launcher = fileURLToPath(new URL('./launch.mjs', import.meta.url)); - -// One bridge owns every launcher until its stdio and native tools have settled. -export class ToolExecutor { - #calls = new Set(); - #closed = false; - - constructor(profile, entrypoint = launcher) { - this.profile = profile; - this.entrypoint = entrypoint; - } - - async execute(tool, input, signal) { - if (this.#closed) throw new Error('Workspace transport is closed'); - signal?.throwIfAborted(); - const request = JSON.stringify({ tool, input }); - if (Buffer.byteLength(request) > limit) throw new Error('Workspace tool input exceeds limit'); - const child = spawn(process.execPath, [this.entrypoint, this.profile], { - env: process.env, - stdio: ['pipe', 'pipe', 'pipe'], - }); - const call = { stop: () => child.kill('SIGTERM') }; - this.#calls.add(call); - let output = Buffer.alloc(0); - let failure; - signal?.addEventListener('abort', call.stop, { once: true }); - if (signal?.aborted) call.stop(); - child.on('error', error => { failure = error; }); - child.stdin.on('error', error => { failure = error; call.stop(); }); - child.stderr.resume(); - child.stdout.on('data', chunk => { - if (output.length + chunk.length > limit) { - failure = new Error('Workspace tool output exceeds limit'); - call.stop(); - } else output = Buffer.concat([output, chunk]); - }); - call.settled = new Promise(resolve => child.on('close', resolve)); - child.stdin.end(request); - try { - const code = await call.settled; - if (failure) throw failure; - signal?.throwIfAborted(); - if (this.#closed) throw new Error('Workspace transport is closed'); - const result = JSON.parse(output.toString('utf8')); - if (result.tool_name !== tool || typeof result.text !== 'string' || !Array.isArray(result.content)) - throw new Error('Invalid native workspace tool result'); - if (code !== 0 && result.isError !== true) throw new Error('Workspace tool execution failed'); - return result; - } finally { - signal?.removeEventListener('abort', call.stop); - this.#calls.delete(call); - } - } - - async close() { - this.#closed = true; - const calls = [...this.#calls]; - for (const call of calls) call.stop(); - await Promise.all(calls.map(call => call.settled)); - } -} diff --git a/packages/mcode-harness/tool-executor.test.mjs b/packages/mcode-harness/tool-executor.test.mjs deleted file mode 100644 index 955c6854e..000000000 --- a/packages/mcode-harness/tool-executor.test.mjs +++ /dev/null @@ -1,64 +0,0 @@ -import assert from 'node:assert/strict'; -import test from 'node:test'; -import { mkdtemp, mkdir, writeFile, rm, access } from 'node:fs/promises'; -import { homedir } from 'node:os'; -import { join } from 'node:path'; -import { setTimeout as delay } from 'node:timers/promises'; -import { ToolExecutor } from './tool-executor.mjs'; - -async function fixture(t, body) { - const root = join(homedir(), '.oac', 'tests'); - await mkdir(root, { recursive: true }); - const dir = await mkdtemp(join(root, 'mcode-worker-')); - t.after(() => rm(dir, { recursive: true, force: true })); - const script = join(dir, 'launcher.mjs'); - await writeFile(script, body); - const executor = new ToolExecutor(dir, script); - t.after(() => executor.close()); - return { dir, executor }; -} - -test('returns the original native result, including a native tool error', async t => { - const { executor } = await fixture(t, ` - process.stdin.resume(); process.stdin.on('end', () => { - console.log(JSON.stringify({tool_name:'read',text:'denied',content:[],isError:true})); - process.exitCode=1; - });`); - assert.deepEqual(await executor.execute('read', { path: 'private' }), { - tool_name: 'read', text: 'denied', content: [], isError: true, - }); -}); - -test('rejects a result attributed to a different native tool', async t => { - const { executor } = await fixture(t, ` - process.stdin.resume(); process.stdin.on('end', () => - console.log(JSON.stringify({tool_name:'write',text:'wrong',content:[]})));`); - await assert.rejects(executor.execute('read', {}), /Invalid native/); -}); - -for (const shutdown of ['cancel', 'transport close']) { - test(`${shutdown} waits for worker exit and prevents late effects`, async t => { - const { dir, executor } = await fixture(t, ` - import { writeFileSync } from 'node:fs'; - process.stdin.resume(); - process.stdin.on('end', () => { - writeFileSync(process.argv[2]+'/ready','1'); - setTimeout(() => writeFileSync(process.argv[2]+'/late','1'),500); - });`); - const controller = new AbortController(); - const failed = assert.rejects(executor.execute('bash', {}, controller.signal)); - let ready = false; - for (let i = 0; i < 100; i++) { - try { await access(join(dir, 'ready')); ready = true; break; } - catch { await delay(10); } - } - assert.equal(ready, true, 'fixture worker started'); - if (shutdown === 'cancel') controller.abort(); - else await executor.close(); - await failed; - await delay(550); - await assert.rejects(access(join(dir, 'late')), { code: 'ENOENT' }); - await executor.close(); - await assert.rejects(executor.execute('read', {}), /transport is closed/); - }); -} diff --git a/packages/mcode-harness/worker.ts b/packages/mcode-harness/worker.ts deleted file mode 100644 index f034b1ef6..000000000 --- a/packages/mcode-harness/worker.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { LocalReadTool, LocalWriteTool, LocalEditTool, LocalBashTool } from '@native/local-pi-tools'; -import { LocalGrepTool } from '@native/local-grep'; -import { LocalGlobTool } from '@native/local-glob'; -import { toRuntimeTool, isRuntimeToolInputValid } from '@mavis/agent-core/tools'; -import { readFileSync } from 'node:fs'; -import { killTrackedDetachedChildren } from '@pi/utils/shell'; -import { isAbsolute } from 'node:path'; - -const root = process.argv[2]; -if (!root || !isAbsolute(root)) throw new Error('absolute workspace argument required'); -const tools = [new LocalReadTool(root), new LocalWriteTool(root), new LocalEditTool(root), - new LocalBashTool(root, undefined, { mode: 'off' }), new LocalGrepTool(root), new LocalGlobTool(root)] - .map(toRuntimeTool); -if (process.argv[3] === '--describe') { - process.stdout.write(JSON.stringify(tools.map(t => ({ name: t.def.name, - description: t.def.description, inputSchema: t.def.schema }))) + '\n'); - process.exit(0); -} -const abort = new AbortController(); -const cancel = () => { - abort.abort(); - // Native Bash owns a detached process group outside the daemon's group. - // Keep the worker alive to settle its tool after stopping that owned group. - killTrackedDetachedChildren(); -}; -process.on('SIGTERM', cancel); -process.on('SIGINT', cancel); -const request = JSON.parse(readFileSync(0, 'utf8')); -const tool = tools.find(value => value.def.name === request.tool); -if (!tool || !request.input || typeof request.input !== 'object' || Array.isArray(request.input) || - !isRuntimeToolInputValid(tools, request.tool, request.input)) - throw new Error('invalid tool request'); -const context = { sessionId: 'worker', turnId: 'call', allowBashAutoPromotion: false, - canConsumeBackgroundBashOutput: false }; -try { - const result = await tool.impl.execute(context, request.input, abort.signal); - process.stdout.write(JSON.stringify(result) + '\n'); -} catch (error) { - process.stdout.write(JSON.stringify({ tool_name: request.tool, isError: true, - text: error instanceof Error ? error.message : String(error), content: [] }) + '\n'); - process.exitCode = 1; -} finally { - process.removeListener('SIGTERM', cancel); - process.removeListener('SIGINT', cancel); -} diff --git a/scripts/build-mcode-harness.sh b/scripts/build-mcode-harness.sh index d08b0bccb..1df969b58 100644 --- a/scripts/build-mcode-harness.sh +++ b/scripts/build-mcode-harness.sh @@ -32,27 +32,16 @@ if [[ -f "$source/.cache/artifacts/code-0.3.11.tgz" ]]; then cp "$source/.cache/artifacts/code-0.3.11.tgz" "$context/upstream/.cache/artifacts/" fi printf '%s\n' "$revision" > "$context/upstream/.oac-source-revision" -cp "$package/"*.mjs "$package/"*.ts "$package/"*.json "$context/" test "$(node "$native/cli.js" --version)" = 0.4.12 ( - cd "$context" - MCODE_SOURCE="$context/upstream" node patch-native.mjs - cd upstream + MCODE_SOURCE="$context/upstream" node "$package/patch-native.mjs" + cd "$context/upstream" corepack pnpm install --frozen-lockfile node scripts/build.mjs ) -( - cd "$context" - npm ci --no-audit --no-fund - MCODE_SOURCE="$context/upstream" node build.mjs - node dist/worker.mjs /workspace --describe > dist/tools.json - npm prune --omit=dev --no-audit --no-fund -) artifact="$context/artifact" mkdir "$artifact" -cp -R "$context/dist" "$context/node_modules" "$artifact/" -cp "$package/launch.mjs" "$package/bridge.mjs" "$package/check.mjs" "$package/tool-executor.mjs" "$package/source.json" "$artifact/" -cp "$package/subagent-snapshot.mjs" "$artifact/" +cp "$package/check.mjs" "$package/subagent-snapshot.mjs" "$package/source.json" "$artifact/" mkdir "$artifact/native" cp -R "$context/upstream/dist/." "$artifact/native/" cp -R "$native/node_modules" "$artifact/native/" diff --git a/scripts/build-native-installer.mjs b/scripts/build-native-installer.mjs index e8cc4a250..2060f7b66 100644 --- a/scripts/build-native-installer.mjs +++ b/scripts/build-native-installer.mjs @@ -131,10 +131,10 @@ export async function validateComponents(root, names, daemonVersion) { const upstream = await json(join(component, 'source.json')); const expected = await json(new URL('../packages/mcode-harness/source.json', import.meta.url)); if (upstream.version !== pins.minimax || upstream.revision !== expected.revision) throw new Error('MiniMax source pin mismatch'); - for (const entry of ['launch.mjs', 'dist/worker.mjs', 'provenance.json', 'native-patch.json']) await requireFile(join(component, entry)); + for (const entry of ['check.mjs', 'subagent-snapshot.mjs', 'provenance.json', 'native-patch.json']) await requireFile(join(component, entry)); if (probe(node, [join(component, 'native/cli.js'), '--version'], component, env) !== pins.minimax) throw new Error('MiniMax native pin mismatch'); const info = JSON.parse(probe(node, [join(component, 'check.mjs')], component, env)); - if (info.protocol !== 2 || info.native !== pins.minimax || info.source !== expected.revision) throw new Error('MiniMax companion compatibility failed'); + if (info.protocol !== 3 || info.native !== pins.minimax || info.source !== expected.revision) throw new Error('MiniMax companion compatibility failed'); } } } diff --git a/scripts/core-distribution-manifest.py b/scripts/core-distribution-manifest.py index c94015351..3c2822d5c 100644 --- a/scripts/core-distribution-manifest.py +++ b/scripts/core-distribution-manifest.py @@ -198,7 +198,7 @@ def verify_runtime(image, daemon, source): files = {"/usr/local/bin/oac-daemon": pathlib.Path(daemon)} environment = dict(value.split("=", 1) for value in details["Config"]["Env"] if "=" in value) if "OAC_RUNTIME_MCODE_BIN" in environment: - for name in ("launch.mjs", "bridge.mjs", "check.mjs", "tool-executor.mjs", "subagent-snapshot.mjs", "source.json"): + for name in ("check.mjs", "subagent-snapshot.mjs", "source.json"): files["/opt/mcode-harness/" + name] = source / "packages/mcode-harness" / name output = subprocess.check_output( ["docker", "run", "--rm", "--network", "none", "--entrypoint", "sha256sum", image, *files], text=True diff --git a/services/core/deploy/mcode/Dockerfile b/services/core/deploy/mcode/Dockerfile index aa43dbbb4..2887b20fd 100644 --- a/services/core/deploy/mcode/Dockerfile +++ b/services/core/deploy/mcode/Dockerfile @@ -12,7 +12,7 @@ COPY mcode-harness /opt/mcode-harness ENV HOME=/home/runtime OAC_RUNTIME_HOME=/home/runtime/.oac \ OAC_RUNTIME_MCODE_NODE=/usr/local/bin/node \ OAC_RUNTIME_MCODE_BIN=/opt/mcode-harness/native/cli.js \ - OAC_RUNTIME_MCODE_WORKSPACE_BRIDGE=/opt/mcode-harness/bridge.mjs \ + OAC_RUNTIME_MCODE_COMPANION=/opt/mcode-harness \ OAC_RUNTIME_WORKSPACE=/environment/workspace \ OAC_RUNTIME_INITIALIZATION_DIRECTORY=/environment/initialization \ OAC_RUNTIME_PACKAGE_DIRECTORY=/environment/packages \ diff --git a/services/core/deploy/mcode/README.md b/services/core/deploy/mcode/README.md index ab9e9690f..172e6663e 100644 --- a/services/core/deploy/mcode/README.md +++ b/services/core/deploy/mcode/README.md @@ -1,14 +1,14 @@ # MiniMax Code Runtime -The MiniMax Code adapter ([`agent/mcode`](../../../../apps/daemon/internal/agent/mcode)) runs the native MiniMax Code CLI over ACP inside the daemon. MiniMax Code keeps its own ACP Session, model loop and history. The companion package [`packages/mcode-harness`](../../../../packages/mcode-harness/README.md) owns the workspace tool bridge, the native patch and the Subagent history reader. This page holds the Runtime-level adapter rules and the MiniMax Code Runtime image. [Harness protocol](../../../../contracts/agents-api/harness-onboarding.md) owns the obligations shared by all adapters. +The MiniMax Code adapter ([`agent/mcode`](../../../../apps/daemon/internal/agent/mcode)) runs the native MiniMax Code CLI over ACP inside the daemon. MiniMax Code keeps its own ACP Session, model loop and history. The companion package [`packages/mcode-harness`](../../../../packages/mcode-harness/README.md) owns the native patch and the Subagent history reader. This page holds the Runtime-level adapter rules and the MiniMax Code Runtime image. [Harness protocol](../../../../contracts/agents-api/harness-onboarding.md) owns the obligations shared by all adapters. -Native tools run with the daemon user's permissions; the outer sandbox provides isolation ([Runtime and outer isolation](../../../../docs/concepts.md#runtime-and-outer-isolation)). +Native tools run with the daemon user's permissions; the outer sandbox provides isolation ([Runtime and outer isolation](../../../../docs/concepts.md#runtime-and-outer-isolation)). The native data directory, including the generated configuration with the provider credential, is readable by those tools, as for [Claude Code](../../../../contracts/agents-api/model-execution.md#session-override). ## Native pin and readiness The adapter accepts only `@minimax-ai/code` `0.4.12` ([`version.go`](../../../../apps/daemon/internal/agent/mcode/version.go)), built from the upstream source revision in [`source.json`](../../../../packages/mcode-harness/source.json) and run with Node.js 22. MiniMax Code runs on Linux and macOS. -Workspace execution also requires the companion's readiness report: private protocol 2, the pinned native version and the pinned source revision ([`workspace_readiness.go`](../../../../apps/daemon/internal/agent/mcode/workspace_readiness.go)). An older companion is rejected even when the upstream version matches. +Workspace execution also requires the companion's readiness report: private protocol 3, the pinned native version and the pinned source revision ([`workspace_readiness.go`](../../../../apps/daemon/internal/agent/mcode/workspace_readiness.go)). An older companion is rejected even when the upstream version matches. ## Model provider @@ -21,11 +21,11 @@ Each Session has its own native data directory, `$OAC_RUNTIME_HOME/runtime/mcode | Profile | Where it runs | Native configuration | | --- | --- | --- | | Text (`environment: none`) | A private working directory inside the Session's data directory | No native tools, Skills, web search, browser tools, `mcode-tools`, thread goals or user questions. With Subagents enabled, the default agent gets only the task tools (`task`, `task_append`, `task_query`, `task_output`, `task_stop`) and delegation | -| Workspace | The Environment's workspace directory, for the native process, the ACP Session and the workspace tools | The text configuration plus permission mode `bypassPermissions`, the selected workspace Skills and the `oac_workspace` MCP bridge for file and shell tools | +| Workspace | The Environment's workspace directory, for the native process, the ACP Session and the native tools | The native `read`, `write`, `edit`, `bash`, `grep`, `glob`, `todowrite` and `web_fetch` tools, permission mode `bypassPermissions`, the selected workspace Skills and the frozen tool environment in the native process environment | The text profile rejects workspace, function tools and MCP servers. Workspace execution requires Linux or macOS and an unrestricted Environment network policy ([`workspace.go`](../../../../apps/daemon/internal/agent/mcode/workspace.go)); the adapter enforces no network restriction. -In the workspace profile, the frozen installation's Skills are linked into the Session's `skills` directory under their exact names and loaded through the native `skill` tool; external Skill discovery stays off. Public MCP servers use the Environment origin only, as the [MCP origin contract](../../../../contracts/agents-api/environments.md#public-mcp-connection-origin) describes. With Subagents enabled, the Session's native `mcp.json` holds only the `oac_workspace` server, so child agents get the same workspace tools. +In the workspace profile, the frozen installation's Skills are linked into the Session's `skills` directory under their exact names and loaded through the native `skill` tool; external Skill discovery stays off. Public MCP servers use the Environment origin only, as the [MCP origin contract](../../../../contracts/agents-api/environments.md#public-mcp-connection-origin) describes. With Subagents enabled, child agents use the same native tools. In both profiles the adapter turns off what needs a MiniMax account or outlives a Turn or asks the user: `askUser`, `beta.mcodeTools`, `beta.threadGoal`, `features.mavis`, `features.webSearch`, the Matrix builtin tools and, on the text profile, the bundled Skills, and it leaves `website_deploy` out of `tools`. `web_search: disabled` also removes `web_fetch`. Background Bash is off: the patched CLI gates it on `OAC_RUNTIME_MCODE_BACKGROUND_BASH=off`, and the adapter rejects a CLI that does not report it off. ## Turns, cancellation and continuation @@ -35,7 +35,7 @@ In the workspace profile, the frozen installation's Skills are linked into the S - A Turn that ends with an error, a cancellation, an unknown input outcome, an unfinished tool call or an unanswered permission or question makes the Executor non-reusable. The adapter stops the native process group and waits for it to exit before the Turn settles; later work loads the same native history in a new Executor. - Continuation requires the exact recorded native Session ID and the same working directory. Recovery without a recorded ID is rejected. - MiniMax Code reports no usage: ACP context occupancy and cumulative cost are not per-Turn usage. -- Workspace `workspace_bash` calls become command observations with the command text, output text and status; exit code and duration stay unknown. Native task and Skill utilities produce no public Items. +- Native `bash` calls become command observations with the command, the Session's workspace as the working directory, the output and status; the exit code is reported only when the native result carries one, and duration stays unknown. Native `web_fetch` calls become `web_search` observations with an `open_page` action. Native task and Skill utilities produce no public Items. - The adapter attaches no [native failure classification](../../../../docs/runtime-protocol.md#native-failure-classification) to its errors: MiniMax Code `0.4.12` reports a model provider rejection over ACP as JSON-RPC internal error `-32603` whose only detail is prose, so these failures stay unclassified. - MiniMax Code `0.4.12` retries a failed model request up to 5 times and stops retrying after 120 seconds, so a model host that does not resolve fails the Turn, unclassified, in about two minutes. Its retries stay inside MiniMax Code and never reach ACP. @@ -46,9 +46,9 @@ In the workspace profile, the frozen installation's Skills are linked into the S | Item | Value | | --- | --- | | Base | Digest-pinned `node:22.23.1-bookworm-slim` with `ca-certificates`, `bash`, `git`, `python3`, `python3-pip` and `ripgrep` | -| Programs | `/usr/local/bin/oac-daemon` and the companion at `/opt/mcode-harness` (native CLI at `native/cli.js`, bridge at `bridge.mjs`) | +| Programs | `/usr/local/bin/oac-daemon` and the companion at `/opt/mcode-harness` (native CLI at `native/cli.js`) | | User | UID/GID 1000 with `HOME=/home/runtime` | -| Environment | `OAC_RUNTIME_HOME=/home/runtime/.oac`, `OAC_RUNTIME_MCODE_NODE`, `OAC_RUNTIME_MCODE_BIN`, `OAC_RUNTIME_MCODE_WORKSPACE_BRIDGE`, `OAC_RUNTIME_WORKSPACE=/environment/workspace`, `OAC_RUNTIME_INITIALIZATION_DIRECTORY=/environment/initialization`, `OAC_RUNTIME_PACKAGE_DIRECTORY=/environment/packages`, `OAC_RUNTIME_STATE_DIRECTORY=/environment/runtime-state` | +| Environment | `OAC_RUNTIME_HOME=/home/runtime/.oac`, `OAC_RUNTIME_MCODE_NODE`, `OAC_RUNTIME_MCODE_BIN`, `OAC_RUNTIME_MCODE_COMPANION`, `OAC_RUNTIME_WORKSPACE=/environment/workspace`, `OAC_RUNTIME_INITIALIZATION_DIRECTORY=/environment/initialization`, `OAC_RUNTIME_PACKAGE_DIRECTORY=/environment/packages`, `OAC_RUNTIME_STATE_DIRECTORY=/environment/runtime-state` | | Entry point | `oac-daemon connect --profile default`, working directory `/environment/workspace` | The build runs the companion's `check.mjs` and the native `--version`. The combined Runtime image uses this image as its base. Sandboxes run it with the [Docker sandbox settings](../../../../docs/sandbox-provider.md#docker-adapter).