diff --git a/apps/cli/config/agent-presets/core-web/agent.cordis.yml b/apps/cli/config/agent-presets/core-web/agent.cordis.yml index 9a251461c5..e9003aa975 100644 --- a/apps/cli/config/agent-presets/core-web/agent.cordis.yml +++ b/apps/cli/config/agent-presets/core-web/agent.cordis.yml @@ -15,20 +15,12 @@ text: >- You are a coding agent powered by the {{model}} model. Your working directory is {{cwd}}. -# `tool-bash` provides the `bashEnv` service, so it needs a realm even alone. -- id: shell - name: cordis:group - group: true - isolate: - bashEnv: true - config: - # The registry and its consumer share the realm: a consumer left outside - # would resolve the host's `bashEnv`, which this plane no longer provides. - - id: bash-env - name: '@deepseek-ai/dsh-bash-env' - - - id: tool-bash - name: '@deepseek-ai/dsh-tool-bash' +# `bash-env` stays in the HOST composition: `apps/cli/src/web.ts` injects it to +# publish `DSH_WEB_URL`/`DSH_WEB_MODE`, and a host row that injects a service is +# the criterion for host-plane ownership. `tool-bash` consumes that host +# registry from here. +- id: tool-bash + name: '@deepseek-ai/dsh-tool-bash' - id: tool-str-replace-editor name: '@deepseek-ai/dsh-tool-str-replace-editor' diff --git a/apps/cli/config/agent-presets/standard/agent.cordis.yml b/apps/cli/config/agent-presets/standard/agent.cordis.yml index 407e4d7d27..0a211bc97f 100644 --- a/apps/cli/config/agent-presets/standard/agent.cordis.yml +++ b/apps/cli/config/agent-presets/standard/agent.cordis.yml @@ -31,22 +31,15 @@ # ── shell ─────────────────────────────────────────────────────────────────── -# `tool-bash` reads as a tool but provides the `bashEnv` service, so it needs a -# realm like any other provider. The executor behind it (`bash-sandbox`) stays -# in the host composition, where the sandbox policy owns it. -- id: shell - name: cordis:group - group: true - isolate: - bashEnv: true - config: - # The registry and its consumer share the realm: a consumer left outside - # would resolve the host's `bashEnv`, which this plane no longer provides. - - id: bash-env - name: '@deepseek-ai/dsh-bash-env' - - - id: tool-bash - name: '@deepseek-ai/dsh-tool-bash' +# `bash-env` stays in the HOST composition: `apps/cli/src/web.ts` injects it to +# publish `DSH_WEB_URL`/`DSH_WEB_MODE`, and a host row that injects a service is +# the criterion for host-plane ownership — injection resolves before any session +# exists, so there is no agent to key by. Behind a preset realm those variables +# never reached the model's shell at all. `tool-bash` consumes the host registry +# from here; the executor behind it (`bash-sandbox`) is host-plane too, where the +# sandbox policy owns it. +- id: tool-bash + name: '@deepseek-ai/dsh-tool-bash' # ── filesystem ────────────────────────────────────────────────────────────── @@ -172,29 +165,21 @@ # ── delegation and workflows ──────────────────────────────────────────────── -# Every backend and every tool that reaches `subagents` or `workflows` shares -# one realm: a consumer left outside it would resolve the host's registry -# instead, which this preset does not populate. +# The `subagents` registry and its spawn/fork backends live in the HOST +# composition: the registry is a process singleton whose cross-session queries +# the api-proxy serves to the browser, and a provider name may only be +# registered once. This preset contributes the delegation TOOLS, which resolve +# that host registry. +# +# `workflows` is different — nothing outside an agent reads it — so every row +# that reaches it shares one entry-local realm here, and a consumer left +# outside would resolve a host registry this preset does not populate. - id: delegation name: cordis:group group: true isolate: - subagents: true workflows: true config: - - id: subagent - name: '@deepseek-ai/dsh-subagent' - - - id: subagent-spawn - name: '@deepseek-ai/dsh-subagent-spawn' - config: - providerName: spawn - - - id: subagent-fork - name: '@deepseek-ai/dsh-subagent-fork' - config: - providerName: fork - - id: tool-subagent-control name: '@deepseek-ai/dsh-tool-subagent-control' @@ -215,8 +200,11 @@ toolName: subagent_fork backgroundMode: continuable - - id: tool-subagent-report - name: '@deepseek-ai/dsh-tool-subagent-report' + # `tool-subagent-report` is host-plane for the same reason as the registry, + # not because a preset may not want it: it registers a CONTINUABLE SETUP on + # that singleton rather than a tool this agent calls, and the setup list is + # not scope-aware — one copy per mounted preset means every child gets + # `report` registered once per live session, which throws on the second. - id: workflow-workerthread name: '@deepseek-ai/dsh-workflow-workerthread' diff --git a/apps/web/tests/scaffold.ts b/apps/web/tests/scaffold.ts index 8d8f866740..b1c6d66bf9 100644 --- a/apps/web/tests/scaffold.ts +++ b/apps/web/tests/scaffold.ts @@ -240,6 +240,31 @@ export async function launchWebScaffold(options: LaunchOptions = {}): Promise [key, process.env[key]]), + ) + let skillRootEnvironmentRestored = false + const restoreSkillRootEnvironment = (): void => { + if (skillRootEnvironmentRestored) return + skillRootEnvironmentRestored = true + for (const [key, value] of Object.entries(originalSkillRootEnvironment)) { + if (value === undefined) Reflect.deleteProperty(process.env, key) + else process.env[key] = value + } + } + Object.assign(process.env, skillRootEnvironment) let persistenceRoot: string try { persistenceRoot = await mkdtemp(join(tmpdir(), 'dsh-web-e2e-sessions-')) @@ -415,6 +440,7 @@ export async function launchWebScaffold(options: LaunchOptions = {}): Promise 0) { throw new AggregateError([error, ...cleanupFailures], 'web scaffold setup failed and cleanup was incomplete') } @@ -462,6 +488,7 @@ export async function launchWebScaffold(options: LaunchOptions = {}): Promise 0) throw new AggregateError(failures, 'web scaffold teardown failed') }, diff --git a/packages/bundle/web-app/cordis.patch.yml b/packages/bundle/web-app/cordis.patch.yml index 356d0a95a7..2fe73e7dd5 100644 --- a/packages/bundle/web-app/cordis.patch.yml +++ b/packages/bundle/web-app/cordis.patch.yml @@ -204,8 +204,11 @@ # absent from a surface overlay would silently reappear the day someone reorders # the composition. -- id: bash-env - disabled: true +# `bash-env` STAYS in the host plane: `apps/cli/src/web.ts` injects it to +# publish `DSH_WEB_URL`/`DSH_WEB_MODE`, and a host row that injects a service is +# the criterion for host-plane ownership — injection resolves before any session +# exists, so there is no agent to key by. Behind a preset realm those variables +# would never reach the model's shell at all. - id: tool-bash disabled: true @@ -261,14 +264,12 @@ - id: tool-result-prune disabled: true -- id: subagent - disabled: true - -- id: subagent-spawn - disabled: true - -- id: subagent-fork - disabled: true +# The subagent registry and its backends STAY in the host plane. `subagents` is +# a process singleton with a cross-session query surface (`listChildren`, +# `followup`) that the host api-proxy serves to the browser, and a provider +# registers under a globally unique name, so a per-session copy would both +# starve that host row and collide on the second session. What a preset +# chooses is which delegation TOOLS its agent sees, below. - id: tool-subagent-control disabled: true @@ -282,8 +283,11 @@ - id: tool-subagent-fork disabled: true -- id: tool-subagent-report - disabled: true +# `tool-subagent-report` is host-plane for the same reason as the registry, not +# because a preset may not want it: it registers a CONTINUABLE SETUP on that +# singleton rather than a tool this agent calls, and the setup list is not +# scope-aware — one copy per mounted preset means every child gets `report` +# registered once per live session, which throws on the second. - id: workflow-workerthread disabled: true