From 4cdf54164930c2baa0c3fdb9214198585a3802e7 Mon Sep 17 00:00:00 2001 From: saladday <1203511142@qq.com> Date: Wed, 30 Sep 2026 01:27:05 +0800 Subject: [PATCH 1/2] fix(mcode): execute in the declared environment workspace --- CONTRIBUTING.md | 3 ++- .../agent/mcode/environment_mcp_test.go | 6 +++++ .../internal/agent/mcode/preparation_test.go | 4 ++-- .../internal/agent/mcode/session_test.go | 4 ++++ .../internal/agent/mcode/workspace.go | 5 +++-- services/agents-api/deploy/mcode/README.md | 22 +++++++++---------- 6 files changed, 28 insertions(+), 16 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 15b0dc872..680474ba4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -119,7 +119,8 @@ databases, credentials and migrations. The product uses Core exclusively; it has Core handlers, storage or scheduling. Qualify public workflows through the same shared chain; direct native probes establish feasibility only. - Keep engine-specific types, process management and protocol translation inside - execution adapters. The public API and persistence/application core must not + execution adapters. Native workspace execution must use the declared Environment + directory; a separate native history/configuration directory is not a workspace. The public API and persistence/application core must not interpret Parsar product payloads or depend on one engine's native item types. Prefer maintained upstream SDKs and native execution protocols over a second hand-written model/tool loop or a general-purpose compatibility framework. diff --git a/apps/parsar-daemon/internal/agent/mcode/environment_mcp_test.go b/apps/parsar-daemon/internal/agent/mcode/environment_mcp_test.go index 3b22c5517..455e5ec3c 100644 --- a/apps/parsar-daemon/internal/agent/mcode/environment_mcp_test.go +++ b/apps/parsar-daemon/internal/agent/mcode/environment_mcp_test.go @@ -43,6 +43,7 @@ func TestEnvironmentMCPUsesFixedLauncherForNewAndLoadedSessions(t *testing.T) { } var params struct { SessionID string `json:"sessionId"` + Cwd string `json:"cwd"` MCP []struct { Name, Command string Args []string @@ -52,6 +53,11 @@ func TestEnvironmentMCPUsesFixedLauncherForNewAndLoadedSessions(t *testing.T) { if json.Unmarshal(raw, ¶ms) != nil || len(params.MCP) != 2 || params.MCP[0].Name != "oac_workspace" { t.Fatal("environment MCP displaced workspace tools") } + cwd, err := os.ReadFile(record + ".cwd") + workspace, pathErr := filepath.EvalSymlinks(req.WorkDir) + if err != nil || pathErr != nil || string(cwd) != workspace || params.Cwd != req.WorkDir { + t.Fatalf("native process and ACP Session must use the declared workspace: process=%q ACP=%q", cwd, params.Cwd) + } server := params.MCP[1] executable, _ := os.Executable() if server.Name != "proof.server" || server.Command != executable || server.Env == nil || len(server.Env) != 0 || diff --git a/apps/parsar-daemon/internal/agent/mcode/preparation_test.go b/apps/parsar-daemon/internal/agent/mcode/preparation_test.go index 4fe282689..f6f3d6f9b 100644 --- a/apps/parsar-daemon/internal/agent/mcode/preparation_test.go +++ b/apps/parsar-daemon/internal/agent/mcode/preparation_test.go @@ -48,8 +48,8 @@ func TestPreparedWorkspaceHasOneInputAndOutputOwner(t *testing.T) { if err != nil || strings.Contains(string(raw), "session/prompt") { t.Fatalf("preparation consumed input: %q %v", raw, err) } - if p.session.opts.Dir == r.WorkDir || !strings.HasPrefix(p.session.opts.Dir, p.session.opts.DataDir+string(filepath.Separator)) { - t.Fatal("native cwd is not private") + if p.session.opts.Dir != r.WorkDir || p.session.opts.DataDir == r.WorkDir { + t.Fatal("native cwd must use the workspace without moving Session state") } out := make(chan proto.Envelope) var wg sync.WaitGroup diff --git a/apps/parsar-daemon/internal/agent/mcode/session_test.go b/apps/parsar-daemon/internal/agent/mcode/session_test.go index a59610226..22fddfdeb 100644 --- a/apps/parsar-daemon/internal/agent/mcode/session_test.go +++ b/apps/parsar-daemon/internal/agent/mcode/session_test.go @@ -222,6 +222,10 @@ func TestMCodeProcess(t *testing.T) { _, _ = f.WriteString(frame.Method + "\n") _ = f.Close() if frame.Method == "session/new" || frame.Method == "session/load" { + cwd, err := os.Getwd() + if err != nil || os.WriteFile(record+".cwd", []byte(cwd), 0600) != nil { + os.Exit(11) + } if os.WriteFile(record+".session", frame.Params, 0600) != nil { os.Exit(11) } diff --git a/apps/parsar-daemon/internal/agent/mcode/workspace.go b/apps/parsar-daemon/internal/agent/mcode/workspace.go index 77b976b00..d61d5dfb2 100644 --- a/apps/parsar-daemon/internal/agent/mcode/workspace.go +++ b/apps/parsar-daemon/internal/agent/mcode/workspace.go @@ -61,14 +61,15 @@ func prepareWorkspaceOptions(ctx context.Context, c WorkspaceConfig, req proto.P if err != nil { return launchOptions{}, err } - // Reuse public option validation and private Session state provisioning. Native - // cwd remains private; only the internal MCP worker receives the public workspace. + // Reuse public option validation and private Session state provisioning. + // The native process, ACP Session and workspace tools share the declared cwd. private := req private.LocalEnvironment, private.WorkDir, private.DisableExecutionEnvironment = nil, "", true opts, err := prepareOptionsWithSkills(ctx, private, false) if err != nil { return opts, err } + opts.Dir = c.Directory if len(req.LocalEnvironment.Skills) > 0 { root := filepath.Join(opts.DataDir, "skills") if err := os.MkdirAll(root, 0700); err != nil { diff --git a/services/agents-api/deploy/mcode/README.md b/services/agents-api/deploy/mcode/README.md index d22db89a3..8a8b2a0f4 100644 --- a/services/agents-api/deploy/mcode/README.md +++ b/services/agents-api/deploy/mcode/README.md @@ -48,12 +48,12 @@ enrollment and the public protocol remain shared. The image supplies the private `OAC_RUNTIME_MCODE_WORKSPACE=managed` and companion paths; caller Agent options cannot change them. Public Files and Artifacts use the common bound workspace helpers. -The native process and ACP Session use a private control directory, while six -original native tools execute against `/workspace` through one trusted MCP bridge +The native process, ACP Session and six original native tools use the declared +Environment workspace (`/workspace` in the Docker image). The tools connect through one trusted MCP bridge with the daemon user's ordinary permissions and no inner sandbox. MCP is internal -transport here; its presence alone does not enable caller-supplied public MCP servers. Project instructions must be read through the -workspace tools. Native automatic project configuration and diff/undo capture do -not apply to this adapter tool path. Exact native-ID continuation remains +transport here; its presence alone does not enable caller-supplied public MCP servers. +Native project discovery uses that same workspace; adapter-owned configuration and +history remain in the private Session data directory. Exact native-ID continuation remains required; recovery without a recorded ID fails closed. Native Bash observations become public `command_execution` items after command arguments arrive. Their text output and status are retained; absent native exit code/duration stay unknown. @@ -87,7 +87,8 @@ mapping; only actual Bash calls become `command_execution`. ## Execution boundaries -Each API Session uses a separate native state directory and cwd. The execution +Each API Session uses a separate native state directory. Workspace execution uses +the declared Environment directory; text-only execution uses a private cwd. The execution child inherits only process and model-network essentials; it does not inherit Core/daemon tokens or arbitrary Node startup configuration. The adapter owns its native config, instructions and home and disables external skills, delegated work, @@ -116,7 +117,7 @@ Cancellation waits for process-group exit and output settlement. Public slash text remains a model message instead of invoking native ACP operator commands. Cold continuation requires the exact persisted native Session ID and matching -private cwd. Missing/foreign history fails; recovery by guessing an ID from native +workspace cwd (or private cwd for text-only execution). Missing/foreign history fails; recovery by guessing an ID from native session listings is not qualified. Public usage breakdown is unavailable because native ACP context occupancy and cumulative cost are not per-Turn usage. @@ -144,11 +145,10 @@ later work recovers the same native history in a new owner without replay. The MiniMax workspace profile builds one CLI from the fixed upstream source and lockfile through the existing companion packaging path, and connects native workspace tools through its standard MCP client. The process and native Session -share one private control directory; public workspace files cannot configure that -process or become privileged project instructions. A trusted adapter-owned bridge +share the declared workspace directory, including for cold continuation. A trusted adapter-owned bridge runs the original six tool implementations with the launching user's ordinary -permissions. It adds no inner sandbox on any platform. Keep native history bound -to the control directory and Files/Artifacts bound to the public workspace. Core and shared file helpers remain engine +permissions. It adds no inner sandbox on any platform. Keep native history in the Session data directory and native execution and +Files/Artifacts bound to the same Environment workspace. Core and shared file helpers remain engine neutral. This internal MCP transport does not admit public MCP configuration. Record the upstream revision, native admission patch hashes and worker-source provenance. The bounded patch checks the shared descendant-task limit inside the From 1d5f92900b2b09b841b6ce663b3b2502308fe746 Mon Sep 17 00:00:00 2001 From: saladday <1203511142@qq.com> Date: Wed, 30 Sep 2026 01:28:23 +0800 Subject: [PATCH 2/2] docs(mcode): wrap workspace guidance --- CONTRIBUTING.md | 3 +- services/agents-api/deploy/mcode/README.md | 96 ++++++++++++---------- 2 files changed, 53 insertions(+), 46 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 680474ba4..f92ba2f9a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -120,7 +120,8 @@ databases, credentials and migrations. The product uses Core exclusively; it has shared chain; direct native probes establish feasibility only. - Keep engine-specific types, process management and protocol translation inside execution adapters. Native workspace execution must use the declared Environment - directory; a separate native history/configuration directory is not a workspace. The public API and persistence/application core must not + directory; a separate native history/configuration directory is not a workspace. + The public API and persistence/application core must not interpret Parsar product payloads or depend on one engine's native item types. Prefer maintained upstream SDKs and native execution protocols over a second hand-written model/tool loop or a general-purpose compatibility framework. diff --git a/services/agents-api/deploy/mcode/README.md b/services/agents-api/deploy/mcode/README.md index 8a8b2a0f4..6065804c2 100644 --- a/services/agents-api/deploy/mcode/README.md +++ b/services/agents-api/deploy/mcode/README.md @@ -49,16 +49,17 @@ enrollment and the public protocol remain shared. The image supplies the private change them. Public Files and Artifacts use the common bound workspace helpers. The native process, ACP Session and six original native tools use the declared -Environment workspace (`/workspace` in the Docker image). The tools connect through one trusted MCP bridge -with the daemon user's ordinary permissions and no inner sandbox. MCP is internal -transport here; its presence alone does not enable caller-supplied public MCP servers. -Native project discovery uses that same workspace; adapter-owned configuration and -history remain in the private Session data directory. Exact native-ID continuation remains -required; recovery without a recorded ID fails closed. Native Bash observations -become public `command_execution` items after command arguments arrive. Their -text output and status are retained; absent native exit code/duration stay unknown. -The private MCP server and file/skill/task utilities are not invented public MCP -or function calls. +Environment workspace (`/workspace` in the Docker image). The tools connect +through one trusted MCP bridge with the daemon user's ordinary permissions and +no inner sandbox. MCP is internal transport here; its presence alone does not +enable caller-supplied public MCP servers. Native project discovery uses that +same workspace; adapter-owned configuration and history remain in the private +Session data directory. Exact native-ID continuation remains required; recovery +without a recorded ID fails closed. Native Bash observations become public +`command_execution` items after command arguments arrive. Their text output and +status are retained; absent native exit code/duration stay unknown. The private +MCP server and file/skill/task utilities are not invented public MCP or function +calls. ## Provider configuration @@ -87,15 +88,16 @@ mapping; only actual Bash calls become `command_execution`. ## Execution boundaries -Each API Session uses a separate native state directory. Workspace execution uses -the declared Environment directory; text-only execution uses a private cwd. The execution -child inherits only process and model-network essentials; it does not inherit -Core/daemon tokens or arbitrary Node startup configuration. The adapter owns its -native config, instructions and home and disables external skills, delegated work, -web search, builtin file/shell tools, browser tools, mcode-tools and native goals. -The workspace profile uses its tool bridge without sandbox isolation. Tools can -read any local state available to the launching user. The outer Environment owns -managed isolation; daemon network modes do not add another network boundary. +Each API Session uses a separate native state directory. Workspace execution +uses the declared Environment directory; text-only execution uses a private cwd. +The execution child inherits only process and model-network essentials; it does +not inherit Core/daemon tokens or arbitrary Node startup configuration. The +adapter owns its native config, instructions and home and disables external +skills, delegated work, web search, builtin file/shell tools, browser tools, +mcode-tools and native goals. The workspace profile uses its tool bridge without +sandbox isolation. Tools can read any local state available to the launching +user. The outer Environment owns managed isolation; daemon network modes do not +add another network boundary. Provide system dependencies at image/template build time or on the self-hosted machine before execution. Runtime does not run apt or sudo or elevate daemon @@ -117,9 +119,10 @@ Cancellation waits for process-group exit and output settlement. Public slash text remains a model message instead of invoking native ACP operator commands. Cold continuation requires the exact persisted native Session ID and matching -workspace cwd (or private cwd for text-only execution). Missing/foreign history fails; recovery by guessing an ID from native -session listings is not qualified. Public usage breakdown is unavailable because -native ACP context occupancy and cumulative cost are not per-Turn usage. +workspace cwd (or private cwd for text-only execution). Missing/foreign history +fails; recovery by guessing an ID from native session listings is not qualified. +Public usage breakdown is unavailable because native ACP context occupancy and +cumulative cost are not per-Turn usage. ### Adapter rules @@ -145,31 +148,34 @@ later work recovers the same native history in a new owner without replay. The MiniMax workspace profile builds one CLI from the fixed upstream source and lockfile through the existing companion packaging path, and connects native workspace tools through its standard MCP client. The process and native Session -share the declared workspace directory, including for cold continuation. A trusted adapter-owned bridge -runs the original six tool implementations with the launching user's ordinary -permissions. It adds no inner sandbox on any platform. Keep native history in the Session data directory and native execution and -Files/Artifacts bound to the same Environment workspace. Core and shared file helpers remain engine -neutral. This internal MCP transport does not admit public MCP configuration. -Record the upstream revision, native admission patch hashes and worker-source -provenance. The bounded patch checks the shared descendant-task limit inside the -existing native SQLite admission transaction before start, without another -scheduler. ACP initialization must acknowledge the applied limit before input. -The native tool catalog selects the same admitted workspace tools for every child, -not only the root's configured profile; this is tool selection, not filesystem -isolation. Only the Session's authorized internal +share the declared workspace directory, including for cold continuation. A +trusted adapter-owned bridge runs the original six tool implementations with the +launching user's ordinary permissions. It adds no inner sandbox on any platform. +Keep native history in the Session data directory and native execution and +Files/Artifacts bound to the same Environment workspace. Core and shared file +helpers remain engine neutral. This internal MCP transport does not admit public +MCP configuration. Record the upstream revision, native admission patch hashes +and worker-source provenance. The bounded patch checks the shared +descendant-task limit inside the existing native SQLite admission transaction +before start, without another scheduler. ACP initialization must acknowledge the +applied limit before input. The native tool catalog selects the same admitted +workspace tools for every child, not only the root's configured profile; this is +tool selection, not filesystem isolation. Only the Session's authorized internal workspace MCP entry crosses the native child selector; this does not grant external MCP access. Initialization must acknowledge the admitted tool inventory -before input as well. Subagent reads use the Session's native database; the daemon -does not protect it from other tools running as the same user. Multi-agent -workspace execution installs only the existing authorized workspace MCP entry in -that private native configuration so children inherit the same tools; public MCP -and Environment-origin MCP combinations remain separately qualified. Complete the hosted -[acceptance checklist](../../../../contracts/agents-api/harnesses.md#acceptance-checklist) before enabling -hosted execution. The standalone companion uses its own npm lock; `make check` -runs its lifecycle tests and script checks, while its exact-source Linux build and -native qualification for the actual supported platform and outer deployment -are required when the companion changes. Historical tests of both inner network -policies do not qualify the current bypass implementation. +before input as well. Subagent reads use the Session's native database; the +daemon does not protect it from other tools running as the same user. +Multi-agent workspace execution installs only the existing authorized workspace +MCP entry in that private native configuration so children inherit the same +tools; public MCP and Environment-origin MCP combinations remain separately +qualified. Complete the hosted [acceptance +checklist](../../../../contracts/agents-api/harnesses.md#acceptance-checklist) +before enabling hosted execution. The standalone companion uses its own npm +lock; `make check` runs its lifecycle tests and script checks, while its +exact-source Linux build and native qualification for the actual supported +platform and outer deployment are required when the companion changes. +Historical tests of both inner network policies do not qualify the current +bypass implementation. ## Acceptance