diff --git a/.agents/notes/implemented/bug-fix/2026-07-28-web-agent-runtime-context.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-07-28-web-agent-runtime-context.i18n.yaml index 483bfd9e86..8f186c0689 100644 --- a/.agents/notes/implemented/bug-fix/2026-07-28-web-agent-runtime-context.i18n.yaml +++ b/.agents/notes/implemented/bug-fix/2026-07-28-web-agent-runtime-context.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-07-28-web-agent-runtime-context.md -2026-07-28-web-agent-runtime-context.md: 449c9d4ba2b144d02dee4b98ae80c86815aec5c1 -2026-07-28-web-agent-runtime-context.zh.md: def1674be5f193739bfb214a24f34590ee075d5f +2026-07-28-web-agent-runtime-context.md: c0e01ab60f2c2eef8e4a021c274e6a8fe9b8ed6f +2026-07-28-web-agent-runtime-context.zh.md: 1f4deb9cf4f4b4fd135cf907323765cf4869a714 diff --git a/.agents/notes/implemented/bug-fix/2026-07-28-web-agent-runtime-context.md b/.agents/notes/implemented/bug-fix/2026-07-28-web-agent-runtime-context.md index 449c9d4ba2..c0e01ab60f 100644 --- a/.agents/notes/implemented/bug-fix/2026-07-28-web-agent-runtime-context.md +++ b/.agents/notes/implemented/bug-fix/2026-07-28-web-agent-runtime-context.md @@ -10,13 +10,13 @@ The shared CLI base configured an empty deployment persona, the Web overlay did ## Decision -The shared Web/headless overlay (`apps/cli/config/web.cordis.yml`) supplies a concise coding-agent persona containing the resolved `{{model}}` and session `{{cwd}}`. `dsh web` additionally resolves the harness checkout from the launcher's module URL, installs the existing `harness:source` section, and adds an `app:web-surface` section before serving requests. The launcher registers that setup before mounting the config tree; its `systemPrompt` injection therefore installs both sections before later prompt consumers such as the agent loop can activate and emit a request header. The [source-checkout/workdir decision](2026-07-30-source-checkout-workdir-distinction.md) owns the source section's wording and its warning not to infer one path from the other. +The shared Web/headless overlay (`apps/cli/config/web.cordis.yml`) supplies a concise coding-agent persona containing the resolved `{{model}}` and session `{{cwd}}`. Before mounting that tree, `dsh web` registers a launcher-provided `cordis:web-runtime-context` builtin; the ordinary Web overlay mounts it to resolve the harness checkout from the launcher's module URL, install the existing `harness:source` section, and add an `app:web-surface` section. A profile that owns its complete prompt can disable the builtin row, while every mounted prompt contribution still activates before later consumers such as the agent loop can emit a request header. The [source-checkout/workdir decision](2026-07-30-source-checkout-workdir-distinction.md) owns the source section's wording and its warning not to infer one path from the other. The Web section treats unqualified references to “this page,” “this GUI,” or “this app” as references to the DeepSeek Harness Web GUI. It also states that the browser provides no implicit DOM, route, or screenshot context, so the model can identify the product without claiming visual state it did not receive. The assembled text is logged in `request/header`, preserving the model-visible/logged invariant. ## Verification -The focused startup-order test registers a later `systemPrompt` consumer and proves that it observes both launcher sections on its first activation. The keyless fresh-round-trip Web scenario boots the shipped base plus Web overlay, registers the same launcher context as `dsh web`, runs a real session through the HTTP/SSE application, and snapshots the first four system-prompt sections with source and working-directory paths normalized. The snapshot pins the harness identity, source checkout, Web orientation, and resolved coding-agent persona in request order. +The focused startup-order test mounts the launcher builtin, registers a later `systemPrompt` consumer, and proves that it observes both launcher sections on its first activation. The keyless fresh-round-trip Web scenario boots the shipped base plus Web overlay, registers the same launcher context as `dsh web`, runs a real session through the HTTP/SSE application, and snapshots the first four system-prompt sections with source and working-directory paths normalized. The snapshot pins the harness identity, source checkout, Web orientation, and resolved coding-agent persona in request order. The Core Web snapshot disables the builtin and pins its complete RL system prompt. ## Alternatives considered @@ -30,4 +30,4 @@ The focused startup-order test registers a later `systemPrompt` consumer and pro ## Consequences -Web requests gain a short stable prompt prefix and may invalidate provider prefix caches once when this change is deployed. Agents can distinguish the GUI source checkout from the selected Workspace and resolve ordinary references to the current app without a clarification round trip. References to a specific visual state remain bounded by the explicit no-DOM/no-route/no-screenshot statement and may still require a path, description, or attachment. +Ordinary Web requests gain a short stable prompt prefix and may invalidate provider prefix caches once when this change is deployed. Agents can distinguish the GUI source checkout from the selected Workspace and resolve ordinary references to the current app without a clarification round trip. References to a specific visual state remain bounded by the explicit no-DOM/no-route/no-screenshot statement and may still require a path, description, or attachment. Complete-prompt profiles can opt out without a launcher path check. diff --git a/.agents/notes/implemented/bug-fix/2026-07-28-web-agent-runtime-context.zh.md b/.agents/notes/implemented/bug-fix/2026-07-28-web-agent-runtime-context.zh.md index def1674be5..1f4deb9cf4 100644 --- a/.agents/notes/implemented/bug-fix/2026-07-28-web-agent-runtime-context.zh.md +++ b/.agents/notes/implemented/bug-fix/2026-07-28-web-agent-runtime-context.zh.md @@ -10,13 +10,13 @@ CLI 共享 base 配置了空的部署 persona,Web overlay 没有替换它, ## 决策 -`apps/cli/config/web.cordis.yml` 这份 Web/无头共享 overlay 提供一段简洁的编码 agent persona,其中包含解析后的 `{{model}}` 与会话 `{{cwd}}`。`dsh web` 还会根据启动器模块的 URL 解析 harness checkout,安装现有的 `harness:source` 提示词段,并在对外提供请求服务前添加 `app:web-surface` 提示词段。启动器会在挂载配置树前注册这项设置;因此,它的 `systemPrompt` 注入会在 agent loop(智能体循环)等后续提示词消费方激活并发出 request header 之前安装这两个提示词段。源码提示词段的措辞,以及其中不得从一条路径推断另一条路径的警告,均由另行记录的[源码 checkout 与工作目录区分决策](2026-07-30-source-checkout-workdir-distinction.md)负责。 +`apps/cli/config/web.cordis.yml` 这份 Web/无头共享 overlay 提供一段简洁的编码 agent persona,其中包含解析后的 `{{model}}` 与会话 `{{cwd}}`。挂载该配置树前,`dsh web` 会注册一个由启动器提供的 `cordis:web-runtime-context` builtin;常规 Web overlay 会挂载它,以根据启动器模块的 URL 解析 harness checkout、安装现有的 `harness:source` 提示词段并添加 `app:web-surface` 提示词段。拥有完整提示词的 profile 可以禁用该 builtin 配置行,而每项已挂载的提示词贡献仍会在 agent loop(智能体循环)等后续消费方发出 request header 前激活。源码提示词段的措辞,以及其中不得从一条路径推断另一条路径的警告,均由另行记录的[源码 checkout 与工作目录区分决策](2026-07-30-source-checkout-workdir-distinction.md)负责。 Web 提示词段把未限定的「这个页面」「这个 GUI」或「这个应用」解释为 DeepSeek Harness Web GUI。同时,它会明确说明浏览器不会隐式提供 DOM、路由或截图上下文,使模型能够识别产品,但不会声称掌握未收到的视觉状态。组装后的文本会记录在 `request/header` 中,从而保持「模型可见内容必须有日志记录」这一不变量。 ## 验证 -聚焦启动顺序的测试会注册一个后续的 `systemPrompt` 消费方,并证明该消费方首次激活时就能观察到启动器的两个提示词段。无密钥的 Web fresh-round-trip 场景会启动已交付的 base 与 Web overlay,注册与 `dsh web` 相同的启动器上下文,并通过 HTTP/SSE 应用运行一个真实会话。测试会把源码路径和工作目录规范化,然后对系统提示词的前四个段落生成快照。该快照按请求顺序固定 harness 身份、源码 checkout、Web 界面定位,以及解析后的编码 agent persona。 +聚焦启动顺序的测试会挂载启动器 builtin,注册一个后续的 `systemPrompt` 消费方,并证明该消费方首次激活时就能观察到启动器的两个提示词段。无密钥的 Web fresh-round-trip 场景会启动已交付的 base 与 Web overlay,注册与 `dsh web` 相同的启动器上下文,并通过 HTTP/SSE 应用运行一个真实会话。测试会把源码路径和工作目录规范化,然后对系统提示词的前四个段落生成快照。该快照按请求顺序固定 harness 身份、源码 checkout、Web 界面定位,以及解析后的编码 agent persona。Core Web 快照会禁用该 builtin,并固定其完整的 RL 系统提示词。 ## 考虑过的替代方案 @@ -30,4 +30,4 @@ Web 提示词段把未限定的「这个页面」「这个 GUI」或「这个应 ## 影响 -Web 请求会增加一段较短且稳定的提示词前缀;部署此变更时,模型提供方的前缀缓存可能失效一次。agent 可以区分 GUI 源码 checkout 与所选 Workspace,并且无需再经过一轮澄清即可解析对当前应用的一般指代。对特定视觉状态的指代仍受「无 DOM/无路由/无截图」这一显式边界约束,必要时仍需用户提供路径、描述或附件。 +常规 Web 请求会增加一段较短且稳定的提示词前缀;部署此变更时,模型提供方的前缀缓存可能失效一次。agent 可以区分 GUI 源码 checkout 与所选 Workspace,并且无需再经过一轮澄清即可解析对当前应用的一般指代。对特定视觉状态的指代仍受「无 DOM/无路由/无截图」这一显式边界约束,必要时仍需用户提供路径、描述或附件。拥有完整提示词的 profile 无需检查启动器路径即可选择退出。 diff --git a/.agents/notes/implemented/bug-fix/2026-07-28-web-gui-feedback-loop.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-07-28-web-gui-feedback-loop.i18n.yaml index 6feeb0e71f..685a36b3f5 100644 --- a/.agents/notes/implemented/bug-fix/2026-07-28-web-gui-feedback-loop.i18n.yaml +++ b/.agents/notes/implemented/bug-fix/2026-07-28-web-gui-feedback-loop.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-07-28-web-gui-feedback-loop.md -2026-07-28-web-gui-feedback-loop.md: 039d2aebeeef903d10838a46b48e5172f0195126 -2026-07-28-web-gui-feedback-loop.zh.md: 34b6b26d4ce7c7e194e641536fffc503c013188b +2026-07-28-web-gui-feedback-loop.md: 04f0e9383db03b84e1998212782342034ffb9d96 +2026-07-28-web-gui-feedback-loop.zh.md: 4ce0144bc6aba8d1b2b99d072541bf7fc24057b2 diff --git a/.agents/notes/implemented/bug-fix/2026-07-28-web-gui-feedback-loop.md b/.agents/notes/implemented/bug-fix/2026-07-28-web-gui-feedback-loop.md index 039d2aebee..04f0e9383d 100644 --- a/.agents/notes/implemented/bug-fix/2026-07-28-web-gui-feedback-loop.md +++ b/.agents/notes/implemented/bug-fix/2026-07-28-web-gui-feedback-loop.md @@ -12,7 +12,7 @@ The [incident post-mortem](../../../../docs/postmortem/0003-web-agent-gui-feedba ## Decision -`dsh web` publishes one canonical loopback URL and its actual runtime mode as both model-visible orientation and managed shell facts. The `app:web-surface` prompt section says that unqualified references identify this GUI and names the URL; `DSH_WEB_URL` and `DSH_WEB_MODE=production|development` carry the same facts into every foreground or managed background bash call. The section preserves the no-implicit-DOM, route, or screenshot boundary and does not claim that a LAN alias equals the browser's literal address. +The ordinary `dsh web` composition mounts the launcher-provided `cordis:web-runtime-context` builtin, which publishes one canonical loopback URL and its actual runtime mode as both model-visible orientation and managed shell facts. The `app:web-surface` prompt section says that unqualified references identify this GUI and names the URL; `DSH_WEB_URL` and `DSH_WEB_MODE=production|development` carry the same facts into every foreground or managed background bash call. The section preserves the no-implicit-DOM, route, or screenshot boundary and does not claim that a LAN alias equals the browser's literal address. A complete-prompt profile can disable the row and receives neither the prompt section nor the managed variables. The mode-specific prompt makes the agent, rather than the user, own the hidden startup contract. Production mode defines acceptance as rebuilding the affected artifacts and refreshing the existing URL. Development mode states that `dsh web --dev` activates only the HMR receiver: automatic client-plugin reload additionally requires a same-checkout `pnpm run dev:web` watcher, which the agent verifies before promising no-refresh updates. Shell and other plain-package changes still require rebuild plus refresh. An agent in production mode explains both commands when a user requests no-refresh updates; it does not launch a replacement GUI unless asked. @@ -36,4 +36,4 @@ The keyless fresh-round-trip browser scenario boots the shipped production Web c ## Consequences -Web prompts gain a dynamic URL-and-mode paragraph, so provider prefix reuse now varies by bound port and mode. Bash processes gain two non-secret managed environment variables. Bare Vite can no longer be used as a shell-only visual sandbox; developers use the full host or build mode instead. In exchange, GUI work has one mechanically observable target, the agent can teach the user the exact update behavior of the process actually serving their session, and the unsupported startup path fails before a white screen. The URL/mode contract guides the agent away from replacement ports; it does not prohibit arbitrary shell commands from starting one. +Ordinary Web prompts gain a dynamic URL-and-mode paragraph, so provider prefix reuse now varies by bound port and mode. Their Bash processes gain two non-secret managed environment variables. Bare Vite can no longer be used as a shell-only visual sandbox; developers use the full host or build mode instead. In exchange, GUI work has one mechanically observable target, the agent can teach the user the exact update behavior of the process actually serving their session, and the unsupported startup path fails before a white screen. The URL/mode contract guides the agent away from replacement ports; it does not prohibit arbitrary shell commands from starting one. Profiles that disable the runtime-context builtin also give up this feedback-loop guidance and shell context. diff --git a/.agents/notes/implemented/bug-fix/2026-07-28-web-gui-feedback-loop.zh.md b/.agents/notes/implemented/bug-fix/2026-07-28-web-gui-feedback-loop.zh.md index 34b6b26d4c..4ce0144bc6 100644 --- a/.agents/notes/implemented/bug-fix/2026-07-28-web-gui-feedback-loop.zh.md +++ b/.agents/notes/implemented/bug-fix/2026-07-28-web-gui-feedback-loop.zh.md @@ -12,7 +12,7 @@ Web agent(智能体)既无法识别承载当前会话的 GUI,也不知道 ## 决策 -`dsh web` 发布一个规范的回环 URL 及其实际运行时模式,同时将二者作为模型可见的界面定位信息和受管 shell 事实。`app:web-surface` 提示词段说明:未加限定的指代指向此 GUI,并给出 URL;`DSH_WEB_URL` 和 `DSH_WEB_MODE=production|development` 会把同样的事实传入每次前台或受管后台 bash 调用。该段保留「不会隐式获得 DOM、路由或截图」这一边界,也不声称局域网别名等于浏览器中的实际地址。 +常规 `dsh web` 组合会挂载启动器提供的 `cordis:web-runtime-context` builtin,由它发布一个规范的回环 URL 及其实际运行时模式,同时将二者作为模型可见的界面定位信息和受管 shell 事实。`app:web-surface` 提示词段说明:未加限定的指代指向此 GUI,并给出 URL;`DSH_WEB_URL` 和 `DSH_WEB_MODE=production|development` 会把同样的事实传入每次前台或受管后台 bash 调用。该段保留「不会隐式获得 DOM、路由或截图」这一边界,也不声称局域网别名等于浏览器中的实际地址。拥有完整提示词的 profile 可以禁用该配置行,并且不会收到该提示词段和这些受管变量中的任何一个。 按模式区分的提示词让 agent 而非用户负责隐藏的启动契约。生产模式将验收定义为重新构建受影响的产物并刷新现有 URL。开发模式说明,`dsh web --dev` 只会启用 HMR(热模块替换)接收端:客户端插件要自动重新加载,还需要在同一检出中运行 `pnpm run dev:web` 监听进程,agent 会在承诺无需刷新即可更新前验证这一点。外壳和其他普通包的变更仍然需要重新构建并刷新。生产模式下的 agent 会在用户要求无需刷新即可更新时说明这两个命令;除非用户要求,否则不会启动替代 GUI。 @@ -36,4 +36,4 @@ Web agent(智能体)既无法识别承载当前会话的 GUI,也不知道 ## 影响 -Web 提示词会增加一个动态 URL 和模式段落,因此模型提供方的前缀复用会随绑定端口和模式变化。Bash 进程会增加两个非敏感的受管环境变量。裸 Vite 不再能用作只依赖 shell 的视觉沙箱;开发者应改用完整宿主或构建模式。作为交换,GUI 工作有了一个可由机制观察的唯一目标,agent 可以向用户说明实际承载其会话的进程究竟如何更新,不受支持的启动路径也会在出现白屏前失败。URL/模式契约会引导 agent 避免使用替代端口,但不会禁止任意 shell 命令启动替代服务。 +常规 Web 提示词会增加一个动态 URL 和模式段落,因此模型提供方的前缀复用会随绑定端口和模式变化。相应的 Bash 进程会增加两个非敏感的受管环境变量。裸 Vite 不再能用作只依赖 shell 的视觉沙箱;开发者应改用完整宿主或构建模式。作为交换,GUI 工作有了一个可由机制观察的唯一目标,agent 可以向用户说明实际承载其会话的进程究竟如何更新,不受支持的启动路径也会在出现白屏前失败。URL/模式契约会引导 agent 避免使用替代端口,但不会禁止任意 shell 命令启动替代服务。禁用 runtime-context builtin 的 profile 也会放弃这项反馈闭环指引与 shell 上下文。 diff --git a/.agents/notes/implemented/feature/2026-07-29-persistent-bash-str-replace-editor.i18n.yaml b/.agents/notes/implemented/feature/2026-07-29-persistent-bash-str-replace-editor.i18n.yaml index e35daaaf42..d5dbd53881 100644 --- a/.agents/notes/implemented/feature/2026-07-29-persistent-bash-str-replace-editor.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-07-29-persistent-bash-str-replace-editor.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-29-persistent-bash-str-replace-editor.md -2026-07-29-persistent-bash-str-replace-editor.md: 22851078c1cc8fa9d5716afa41c8a2e2b7e7725c -2026-07-29-persistent-bash-str-replace-editor.zh.md: 23f80d1a5911f4f3820d526c2221d3002507d774 +2026-07-29-persistent-bash-str-replace-editor.md: c4750e30370bfd253064c39cb1adc0f5b2baa60d +2026-07-29-persistent-bash-str-replace-editor.zh.md: 62073571ce2164d88897d4959caafd9cb1dbdd93 diff --git a/.agents/notes/implemented/feature/2026-07-29-persistent-bash-str-replace-editor.md b/.agents/notes/implemented/feature/2026-07-29-persistent-bash-str-replace-editor.md index 22851078c1..c4750e3037 100644 --- a/.agents/notes/implemented/feature/2026-07-29-persistent-bash-str-replace-editor.md +++ b/.agents/notes/implemented/feature/2026-07-29-persistent-bash-str-replace-editor.md @@ -18,7 +18,7 @@ Some deployments need a one-call Bash schema whose shell state survives across m Both plugins are included in the Python runtime closure. The persistent Bash closure also includes the PTY service/local backend and the sandbox services required by that backend. Because `node-pty` executes a native `spawn-helper` on macOS, each packaged macOS runtime executable ships with a `-spawn-helper` sibling; Linux uses `forkpty` directly. A pinned `node-pty` patch checks `DSH_NODE_PTY_SPAWN_HELPER` first, so it remains a true override for a current external consumer that supplies a non-sibling helper. When the override is unset, the patch resolves the packaged executable sibling if present and otherwise preserves upstream lookup in ordinary Node runs. The macOS builders fail before publication when the helper is absent or not executable. -The shipped [`core-web.cordis.yml`](../../../../apps/cli/config/core-web.cordis.yml) overlay composes both plugins over the ordinary Web surface, disables its other model-facing consumers, and leaves the Web host, browser, Workspace, persistence, sandbox, and permission stack in place. The local PTY backend resolves the effective session sandbox mode when it creates the shell. While that owner has an open shell or a spawn in progress, a different permission mode is rejected before its session event commits; the editor continues through the Web filesystem sandbox. +The shipped [`core-web.cordis.yml`](../../../../apps/cli/config/core-web.cordis.yml) overlay composes both plugins over the ordinary Web surface for the Claude SWE-compatible RL contract. It pins native tool mode and makes the complete system prompt `DSH_SYSTEM_PROMPT` when set or `You are a helpful software engineer assistant.` otherwise, with no harness identity, source-checkout section, Web orientation, Workspace instructions, or tool-mode guidance. It disables every other model-facing consumer, so the model receives exactly the persistent `bash` and `str_replace_editor` schemas, while the Web host, browser, Workspace, persistence, sandbox, and permission stack remains in place. The local PTY backend resolves the effective session sandbox mode when it creates the shell. While that owner has an open shell or a spawn in progress, a different permission mode is rejected before its session event commits; the editor continues through the Web filesystem sandbox. ## Alternatives considered diff --git a/.agents/notes/implemented/feature/2026-07-29-persistent-bash-str-replace-editor.zh.md b/.agents/notes/implemented/feature/2026-07-29-persistent-bash-str-replace-editor.zh.md index 23f80d1a59..62073571ce 100644 --- a/.agents/notes/implemented/feature/2026-07-29-persistent-bash-str-replace-editor.zh.md +++ b/.agents/notes/implemented/feature/2026-07-29-persistent-bash-str-replace-editor.zh.md @@ -18,7 +18,7 @@ Status: implemented 两个插件都进入 Python runtime 闭包。持久 Bash 的闭包还包含 PTY 服务/本地后端,以及该后端要求的沙箱服务。由于 `node-pty` 在 macOS 上会执行原生 `spawn-helper`,每个打包后的 macOS 运行时可执行文件都会携带一个 `-spawn-helper` 伴随文件;Linux 直接使用 `forkpty`。固定版本的 `node-pty` 补丁会先检查 `DSH_NODE_PTY_SPAWN_HELPER`,因此对当前提供非伴随 helper 的外部消费方而言,该变量仍是真正的覆盖项。未设置该覆盖时,补丁会在打包可执行文件的伴随文件存在时解析它,否则在普通 Node 运行中保留上游查找方式。若 helper 缺失或不可执行,macOS 构建器会在发布前失败。 -已交付的 [`core-web.cordis.yml`](../../../../apps/cli/config/core-web.cordis.yml) 覆盖层在常规 Web 界面之上组合这两个插件,禁用该界面的其他面向模型的消费方,并保留 Web 宿主、浏览器、Workspace、持久化、沙箱与权限栈。本地 PTY 后端会在创建 shell 时解析会话的有效沙箱模式。只要该所有者仍有打开的 shell 或仍在进行中的 spawn,另一种权限模式就会在对应的会话事件提交前遭到拒绝;编辑器则继续经由 Web 文件系统沙箱运行。 +已交付的 [`core-web.cordis.yml`](../../../../apps/cli/config/core-web.cordis.yml) overlay 会在常规 Web 界面之上组合这两个插件,以满足与 Claude SWE 兼容的 RL 契约。它固定使用原生工具模式;完整的系统提示词在设置 `DSH_SYSTEM_PROMPT` 时采用其值,否则采用 `You are a helpful software engineer assistant.`,且不包含 harness 身份、源码 checkout 提示词段、Web 界面定位、Workspace 指令或工具模式指引。它会禁用其他所有面向模型的消费方,使模型恰好只收到持久 `bash` 和 `str_replace_editor` 两个 schema,同时保留 Web 宿主、浏览器、Workspace、持久化、沙箱与权限栈。本地 PTY 后端会在创建 shell 时解析会话的有效沙箱模式。只要该所有者仍有打开的 shell 或仍在进行中的 spawn,另一种权限模式就会在对应的会话事件提交前遭到拒绝;编辑器则继续经由 Web 文件系统沙箱运行。 ## 考虑过的替代方案 diff --git a/apps/cli/config/core-web.cordis.yml b/apps/cli/config/core-web.cordis.yml index 6b31c8f424..e86c452a61 100644 --- a/apps/cli/config/core-web.cordis.yml +++ b/apps/cli/config/core-web.cordis.yml @@ -1,6 +1,26 @@ -# Opt-in two-tool profile over the shipped Web composition. The default native -# model surface is exactly persistent `bash` plus `str_replace_editor`; the -# Web host, browser shell, workspace, persistence, and permission stack remain. +# Opt-in Web shell for the RL core agent contract. The model receives exactly +# the configured persona plus the native `bash` and `str_replace_editor` +# schemas; the Web host, browser shell, persistence, and permission stack stay. + +# Match the Claude SWE-compatible RL core prompt. The launcher-owned Web +# orientation is a separate plugin so disabling it removes both its prompt +# sections without a path-specific launcher branch. Workspace instructions are +# model-visible user context rather than a system section, but RL core disables +# them as part of the same prompt contract. +- id: system-prompt + config: + includeHarnessIdentity: false + persona: !!js process.env.DSH_SYSTEM_PROMPT ?? 'You are a helpful software engineer assistant.' + +- id: web-runtime-context + disabled: true + +- id: workspace-context + disabled: true + +- id: tools + config: + mode: native # Disable every model-facing consumer in the base/Web tree. plan-mode owns the # always-registered exit_plan_mode tool even while the session is not planning. diff --git a/apps/cli/config/web.cordis.yml b/apps/cli/config/web.cordis.yml index daf597916e..2bf9a9d89d 100644 --- a/apps/cli/config/web.cordis.yml +++ b/apps/cli/config/web.cordis.yml @@ -46,6 +46,12 @@ # `dshClient` rows are the browser roster the modules node half scans into # window.__DSH_BOOT__; the modules row is simultaneously a host row. - insert: + # Launcher-provided builtin: owns Web GUI orientation in the model prompt + # and the matching managed Bash variables. Profiles with a complete prompt + # contract disable this row instead of relying on launcher special cases. + - id: web-runtime-context + name: cordis:web-runtime-context + - id: session-projection name: '@deepseek-ai/dsh-session-projection' diff --git a/apps/cli/reference/README.i18n.yaml b/apps/cli/reference/README.i18n.yaml index 7e5b8e5c58..161d6a95f6 100644 --- a/apps/cli/reference/README.i18n.yaml +++ b/apps/cli/reference/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write apps/cli/reference/README.md -README.md: b37ec9ed61ea4e9899a51316065d4188f30997ad -README.zh.md: ca29808a6c8e670f0d0b82c59b1a2c1fa0e13565 +README.md: 0b6c4d0ad854fb6fa0428f6a1cb2db8d91017f27 +README.zh.md: ebd055c949827eb58696e90aea94a111a543586e diff --git a/apps/cli/reference/README.md b/apps/cli/reference/README.md index b37ec9ed61..0b6c4d0ad8 100644 --- a/apps/cli/reference/README.md +++ b/apps/cli/reference/README.md @@ -55,7 +55,7 @@ Both modes treat the invoking directory as the default workspace root, load appl New sessions default to the `workspace-write` permission preset. Bash and filesystem mutations are restricted to the session workspace and platform temporary roots; reads, network access, and process visibility are not confined. `DSH_PERMISSION_MODE` changes the process fallback. Stored General-settings permissions affect later Web sessions, not an already-open one. -`DSH_TOOLS_MODE` selects `native`, `code`, or `both` for the Web/headless process; another value fails at boot. [`config/core-web.cordis.yml`](../config/core-web.cordis.yml) is an optional Web overlay that reduces the native model surface to persistent `bash` and `str_replace_editor` while retaining the shipped host, browser, workspace, persistence, and permission composition. +`DSH_TOOLS_MODE` selects `native`, `code`, or `both` for the ordinary Web/headless process; another value fails at boot. [`config/core-web.cordis.yml`](../config/core-web.cordis.yml) is an optional RL-compatible Web overlay that pins native mode, renders only `DSH_SYSTEM_PROMPT` or `You are a helpful software engineer assistant.` as the system prompt, disables Workspace instructions and Web runtime prompt context, and exposes only persistent `bash` and `str_replace_editor` while retaining the shipped host, browser, workspace, persistence, and permission composition. ## Shared deployment behavior diff --git a/apps/cli/reference/README.zh.md b/apps/cli/reference/README.zh.md index ca29808a6c..ebd055c949 100644 --- a/apps/cli/reference/README.zh.md +++ b/apps/cli/reference/README.zh.md @@ -55,7 +55,7 @@ Web 和无头进程关闭时会给插件树最多 5 秒完成 dispose。第一 新会话默认使用 `workspace-write` 权限预设。Bash 和文件系统修改仅限于会话 workspace 与平台临时根目录;读取、网络访问和进程可见性不受限制。`DSH_PERMISSION_MODE` 更改进程后备值。General settings 中存储的权限影响后续 Web 会话,不改变已打开的会话。 -`DSH_TOOLS_MODE` 为 Web/无头进程选择 `native`、`code` 或 `both`;其他值会导致启动失败。[`config/core-web.cordis.yml`](../config/core-web.cordis.yml) 是可选 Web overlay:它在保留随附宿主、浏览器、workspace、持久化和权限组合的同时,把原生模型 surface 缩减为持久 `bash` 和 `str_replace_editor`。 +`DSH_TOOLS_MODE` 为常规 Web/无头进程选择 `native`、`code` 或 `both`;其他值会导致启动失败。[`config/core-web.cordis.yml`](../config/core-web.cordis.yml) 是可选的 RL 兼容 Web overlay:它固定使用原生模式,仅将 `DSH_SYSTEM_PROMPT` 或 `You are a helpful software engineer assistant.` 渲染为系统提示词,禁用 Workspace 指令与 Web 运行时提示词上下文,并且在保留随附宿主、浏览器、workspace、持久化和权限组合的同时,仅暴露持久 `bash` 和 `str_replace_editor`。 ## 共享部署行为 diff --git a/apps/cli/src/web.ts b/apps/cli/src/web.ts index d2186e097a..f1fcb72fc6 100644 --- a/apps/cli/src/web.ts +++ b/apps/cli/src/web.ts @@ -22,6 +22,7 @@ const SOURCE_ROOT = fileURLToPath(new URL('../../..', import.meta.url)) const DSH_WEB_URL = 'DSH_WEB_URL' as const const DSH_WEB_MODE = 'DSH_WEB_MODE' as const +const WEB_RUNTIME_CONTEXT_BUILTIN = 'web-runtime-context' as const type WebMode = 'production' | 'development' @@ -53,16 +54,8 @@ function localWebUrl(ctx: Context): string { return `http://${LOOPBACK_HOST}:${String(port)}` } -/** - * Register the launcher-owned prompt and shell runtime context before the - * shared config tree mounts. The earlier injections install the prompt - * sections and managed Bash contributor when their owning services activate; - * dynamic values read the bound server only when consumed. - * @param ctx - Web root context with Loader installed but no config tree mounted. - * @param sourceRoot - absolute checkout root resolved from the launcher module. - * @param mode - whether this process mounted the client-plugin HMR receiver. - */ -export function prepareWebRuntimeContext(ctx: Context, sourceRoot: string, mode: WebMode): void { +/** Register the model-facing Web orientation and its matching shell variables. */ +function applyWebRuntimeContext(ctx: Context, sourceRoot: string, mode: WebMode): void { ctx.inject(['systemPrompt'], (promptCtx) => { addHarnessSourceSection(promptCtx, sourceRoot) promptCtx.systemPrompt.section({ @@ -83,6 +76,31 @@ export function prepareWebRuntimeContext(ctx: Context, sourceRoot: string, mode: }) } +/** + * Register the launcher-owned runtime-context builtin before the config tree + * mounts. The Web overlay decides whether to mount it, so profiles that own + * their complete model prompt can disable the contribution without changing + * launcher control flow. Dynamic values read the bound server only when used. + * @param ctx - Web root context with Loader installed but no config tree mounted. + * @param sourceRoot - absolute checkout root resolved from the launcher module. + * @param mode - whether this process mounted the client-plugin HMR receiver. + */ +export function prepareWebRuntimeContext(ctx: Context, sourceRoot: string, mode: WebMode): void { + const { builtins } = ctx.loader + if (builtins[WEB_RUNTIME_CONTEXT_BUILTIN] !== undefined) { + throw new Error(`dsh web: Loader builtin "${WEB_RUNTIME_CONTEXT_BUILTIN}" is already registered`) + } + const plugin = (runtimeCtx: Context): void => { + applyWebRuntimeContext(runtimeCtx, sourceRoot, mode) + } + builtins[WEB_RUNTIME_CONTEXT_BUILTIN] = plugin + ctx.effect(() => () => { + if (builtins[WEB_RUNTIME_CONTEXT_BUILTIN] === plugin) { + Reflect.deleteProperty(builtins, WEB_RUNTIME_CONTEXT_BUILTIN) + } + }, 'dsh web runtime-context builtin') +} + /** * Serve the browser UI from the shipped config tree. `host`/`port` are passed * through only when the flag was given; absent, the shipped Web overlay value stands. diff --git a/apps/cli/tests/web-prompt-context.spec.ts b/apps/cli/tests/web-prompt-context.spec.ts index 64280bda47..91bfbb368b 100644 --- a/apps/cli/tests/web-prompt-context.spec.ts +++ b/apps/cli/tests/web-prompt-context.spec.ts @@ -1,18 +1,26 @@ import { sep } from 'node:path' import { Context } from 'cordis' import { describe, expect, it } from 'vitest' +import Loader from '@cordisjs/plugin-loader' import SystemPrompt from '@deepseek-ai/dsh-system-prompt' import { HARNESS_SOURCE_SECTION } from '@deepseek-ai/dsh-app-boot' import type {} from '@deepseek-ai/dsh-host-webserver' import { prepareWebRuntimeContext } from '../src/web.ts' describe('prepareWebRuntimeContext', () => { - it('installs both sections before a later systemPrompt consumer activates', async () => { + it('registers the config-tree builtin that installs both prompt sections', async () => { const ctx = new Context() const sourceRoot = `${sep}opt${sep}harness-src` let observedSections: { name: string; text: string }[] | undefined try { + await ctx.plugin(Loader) prepareWebRuntimeContext(ctx, sourceRoot, 'production') + expect(() => { + prepareWebRuntimeContext(ctx, sourceRoot, 'production') + }).toThrow( + 'Loader builtin "web-runtime-context" is already registered', + ) + await ctx.loader.create({ name: 'cordis:web-runtime-context' }) ctx.provide('httpServer', { port: 3080 } as Context['httpServer']) const consumer = ctx.inject(['systemPrompt'], async (promptCtx) => { const assembly = await promptCtx.systemPrompt.assemble() diff --git a/apps/web/tests/core-web-profile.snapshot.ts b/apps/web/tests/core-web-profile.snapshot.ts index 58f2a34858..6b048cd120 100644 --- a/apps/web/tests/core-web-profile.snapshot.ts +++ b/apps/web/tests/core-web-profile.snapshot.ts @@ -5,6 +5,7 @@ import { afterAll, beforeAll, describe, expect, it } from 'vitest' import type { AgentHandle } from '@deepseek-ai/dsh-agent' import { CallId } from '@deepseek-ai/dsh-llm' import { SessionId } from '@deepseek-ai/dsh-session' +import { renderPrompt } from '@deepseek-ai/dsh-system-prompt' import { launchWebScaffold, type WebScaffold } from './scaffold.ts' const CORE_WEB_OVERLAY = fileURLToPath(new URL('../../cli/config/core-web.cordis.yml', import.meta.url)) @@ -14,10 +15,13 @@ describe('core Web profile', () => { let agentHandle: AgentHandle beforeAll(async () => { - scaffold = await launchWebScaffold({ - extraOverlayPath: CORE_WEB_OVERLAY, - toolsMode: 'native', - }) + const systemPrompt = process.env.DSH_SYSTEM_PROMPT + Reflect.deleteProperty(process.env, 'DSH_SYSTEM_PROMPT') + try { + scaffold = await launchWebScaffold({ extraOverlayPath: CORE_WEB_OVERLAY }) + } finally { + if (systemPrompt !== undefined) process.env.DSH_SYSTEM_PROMPT = systemPrompt + } agentHandle = await scaffold.ctx.agents.create({ sessionId: SessionId('core-web-profile-smoke'), meta: { cwd: scaffold.workspaceCwd }, @@ -59,7 +63,9 @@ describe('core Web profile', () => { .replaceAll(scaffold.workspaceCwd, '{{cwd}}') .trimEnd() + const prompt = renderPrompt(await scaffold.ctx.systemPrompt.assemble()) expect({ + prompt, tools: scaffold.ctx.tools.schemas().map(tool => tool.name), bash: text(bash), editor: text(editor), @@ -69,6 +75,7 @@ describe('core Web profile', () => { "editor": "Here's the content of {{cwd}}/profile-smoke.txt with line numbers (which has a total of 2 lines): 1 CORE_WEB_EDITOR_OK 2", + "prompt": "You are a helpful software engineer assistant.", "tools": [ "bash", "str_replace_editor", @@ -80,5 +87,24 @@ describe('core Web profile', () => { expect(entries.find(entry => entry.options.id === 'persistent-bash')?.fiber).toBeDefined() expect(entries.find(entry => entry.options.id === 'pty-local')?.fiber).toBeDefined() expect(entries.find(entry => entry.options.id === 'str-replace-editor')?.fiber).toBeDefined() + expect(entries.find(entry => entry.options.id === 'web-runtime-context')?.fiber).toBeUndefined() + expect(entries.find(entry => entry.options.id === 'workspace-context')?.fiber).toBeUndefined() + }) + + it('uses DSH_SYSTEM_PROMPT as the complete prompt when configured', async () => { + const previous = process.env.DSH_SYSTEM_PROMPT + process.env.DSH_SYSTEM_PROMPT = 'RL prompt override' + let overrideScaffold: WebScaffold | undefined + try { + overrideScaffold = await launchWebScaffold({ extraOverlayPath: CORE_WEB_OVERLAY }) + expect(renderPrompt(await overrideScaffold.ctx.systemPrompt.assemble())).toBe('RL prompt override') + } finally { + try { + await overrideScaffold?.close() + } finally { + if (previous === undefined) Reflect.deleteProperty(process.env, 'DSH_SYSTEM_PROMPT') + else process.env.DSH_SYSTEM_PROMPT = previous + } + } }) })