refactor(web): remove --dev; mount the reload chain unconditionally
The client-hmr row joins the web bundle as an ordinary always-on roster row: without a rebuild watcher rewriting client bundles it polls unchanged files and stays idle. This deletes the --dev flag, the web runtime's mode config, the mode-forked prompt contract, the DSH_WEB_MODE bash variable, and the post-settlement row-creation machinery the conditional row required. dsh web + pnpm run dev:web remains the development loop.
This commit is contained in:
@@ -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: aa488e1df087722c072d98c67d47cdf63a42f6b8
|
||||
2026-07-28-web-gui-feedback-loop.zh.md: 9b6954092b737920ed18bc412037e917512e40dc
|
||||
2026-07-28-web-gui-feedback-loop.md: fa7fcee80dc91ad7ec4a9a994927daa2cc293baa
|
||||
2026-07-28-web-gui-feedback-loop.zh.md: ea83441efa83ad0c93e8cb1f0daf71ecfeabb69e
|
||||
|
||||
@@ -12,9 +12,9 @@ The [incident post-mortem](../../../../docs/postmortem/0003-web-agent-gui-feedba
|
||||
|
||||
## Decision
|
||||
|
||||
The ordinary `dsh web` composition mounts the Web bundle's `web-runtime` plugin, 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 sets the row's `surfaceContext` to false and receives neither the prompt section nor the managed variables; the Web launcher uses the same setting to suppress its source-checkout prompt section.
|
||||
The ordinary `dsh web` composition mounts the Web bundle's `web-runtime` plugin, which publishes one canonical loopback URL as both model-visible orientation and a managed shell fact. The `app:web-surface` prompt section says that unqualified references identify this GUI and names the URL; `DSH_WEB_URL` carries the same fact 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 sets the row's `surfaceContext` to false and receives neither the prompt section nor the managed variable; the Web launcher uses the same setting to suppress its source-checkout prompt section.
|
||||
|
||||
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.
|
||||
The prompt makes the agent, rather than the user, own the hidden startup contract. The client-plugin HMR receiver is always mounted, but 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 rebuilding the affected artifacts and refreshing the existing URL. The agent does not launch a replacement GUI unless asked.
|
||||
|
||||
The `apps/web` development script and Vite configuration reject serve mode before opening a port. Their diagnostics identify `apps/web` as a build-only shell, explain that only `dsh web` injects `window.__DSH_BOOT__`, and name the production and HMR entry paths. Vite build mode remains unchanged.
|
||||
|
||||
@@ -22,7 +22,7 @@ No server restart or replacement is required merely because static artifacts cha
|
||||
|
||||
## Verification
|
||||
|
||||
The keyless fresh-round-trip browser scenario boots the shipped production Web composition, drives a real replayed session, snapshots the URL/mode-bearing system-prompt prefix, and invokes the assembled bash tool to prove `$DSH_WEB_URL` and `$DSH_WEB_MODE` match the actual bound runtime. The real CLI smoke launches `dsh web --dev` and captures the provider request, pinning the complete two-command development contract. The `dev:web` watcher test rebuilds an isolated client bundle after a source change; the browser HMR scenario launches `dsh web --dev`, changes an initial production-roster bundle, and observes the new DOM under the same page identity. A real Vite subprocess test requires serve mode to exit naturally with the full-host correction and instruments `Server.listen()` to prove it was never called. The real-Loader webserver test rewrites a static asset after the process binds and proves the same port returns the new bytes. These assertions inspect prompt state, process exit, shell output, DOM identity, and HTTP bytes rather than an agent's success statement.
|
||||
The keyless fresh-round-trip browser scenario boots the shipped Web composition, drives a real replayed session, snapshots the URL-bearing system-prompt prefix, and invokes the assembled bash tool to prove `$DSH_WEB_URL` matches the actual bound runtime. The real CLI smoke launches `dsh web` and captures the provider request, pinning the complete two-command development contract. The `dev:web` watcher test rebuilds an isolated client bundle after a source change; the browser HMR scenario launches `dsh web`, changes an initial roster bundle, and observes the new DOM under the same page identity. A real Vite subprocess test requires serve mode to exit naturally with the full-host correction and instruments `Server.listen()` to prove it was never called. The real-Loader webserver test rewrites a static asset after the process binds and proves the same port returns the new bytes. These assertions inspect prompt state, process exit, shell output, DOM identity, and HTTP bytes rather than an agent's success statement.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
@@ -30,10 +30,10 @@ The keyless fresh-round-trip browser scenario boots the shipped production Web c
|
||||
|
||||
**Remove the `apps/web` development script without guarding Vite.** Rejected because `npx vite`, the exact incident command, bypasses package scripts. Serve mode itself must fail.
|
||||
|
||||
**Automatically restart or replace the current Web process after every edit.** Rejected because the static server already reads current artifacts per request, a restart would interrupt the session that requested the edit, and plugin HMR has a separate explicit `dsh web --dev` composition.
|
||||
**Automatically restart or replace the current Web process after every edit.** Rejected because the static server already reads current artifacts per request, a restart would interrupt the session that requested the edit, and client-plugin reload is owned by the always-mounted HMR chain plus the `pnpm run dev:web` watcher.
|
||||
|
||||
**Send DOM, route, or screenshots with each request.** Deferred to a separate logged-input design. Stable URL identity closes this feedback loop without claiming browser state the host does not receive.
|
||||
|
||||
## Consequences
|
||||
|
||||
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 `surfaceContext` also give up this feedback-loop guidance and shell context.
|
||||
Ordinary Web prompts gain a dynamic URL paragraph, so provider prefix reuse now varies by bound port. Their Bash processes gain one non-secret managed environment variable. 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 contract guides the agent away from replacement ports; it does not prohibit arbitrary shell commands from starting one. Profiles that disable `surfaceContext` also give up this feedback-loop guidance and shell context.
|
||||
|
||||
@@ -12,9 +12,9 @@ Web agent(智能体)既无法识别承载当前会话的 GUI,也不知道
|
||||
|
||||
## 决策
|
||||
|
||||
常规 `dsh web` 组合会挂载 Web 组合包的 `web-runtime` 插件,由它发布一个规范的回环 URL 及其实际运行时模式,同时将二者作为模型可见的界面定位信息和受管 shell 事实。`app:web-surface` 提示词段说明:未加限定的指代指向此 GUI,并给出 URL;`DSH_WEB_URL` 和 `DSH_WEB_MODE=production|development` 会把同样的事实传入每次前台或受管后台 bash 调用。该段保留「不会隐式获得 DOM、路由或截图」这一边界,也不声称局域网别名等于浏览器中的实际地址。拥有完整提示词的 profile 会把该配置行的 `surfaceContext` 设为 false,并且不会收到该提示词段和这些受管变量中的任何一个;Web 启动器也会使用同一项设置来抑制其源码 checkout 提示词段。
|
||||
常规 `dsh web` 组合会挂载 Web 组合包的 `web-runtime` 插件,由它发布一个规范的回环 URL,同时将其作为模型可见的界面定位信息和受管 shell 事实。`app:web-surface` 提示词段说明:未加限定的指代指向此 GUI,并给出 URL;`DSH_WEB_URL` 会把同样的事实传入每次前台或受管后台 bash 调用。该段保留「不会隐式获得 DOM、路由或截图」这一边界,也不声称局域网别名等于浏览器中的实际地址。拥有完整提示词的 profile 会把该配置行的 `surfaceContext` 设为 false,并且不会收到该提示词段和该受管变量;Web 启动器也会使用同一项设置来抑制其源码 checkout 提示词段。
|
||||
|
||||
按模式区分的提示词让 agent 而非用户负责隐藏的启动约定。生产模式将验收定义为重新构建受影响的产物并刷新现有 URL。开发模式说明,`dsh web --dev` 只会启用 HMR(热模块替换)接收端:客户端插件要自动重新加载,还需要在同一检出中运行 `pnpm run dev:web` 监听进程,agent 会在承诺无需刷新即可更新前验证这一点。外壳和其他普通包的变更仍然需要重新构建并刷新。生产模式下的 agent 会在用户要求无需刷新即可更新时说明这两个命令;除非用户要求,否则不会启动替代 GUI。
|
||||
提示词让 agent 而非用户负责隐藏的启动约定。HMR(热模块替换)接收端始终挂载,但客户端插件要自动重新加载,还需要在同一检出中运行 `pnpm run dev:web` 监听进程,agent 会在承诺无需刷新即可更新前验证这一点。外壳和其他普通包的变更仍然需要重新构建受影响的产物并刷新现有 URL。除非用户要求,agent 不会启动替代 GUI。
|
||||
|
||||
`apps/web` 开发脚本和 Vite 配置都会在打开端口前拒绝服务模式。诊断信息会指出 `apps/web` 只是一个仅供构建的外壳,说明只有 `dsh web` 才会注入 `window.__DSH_BOOT__`,并给出生产入口与 HMR 入口路径。Vite 构建模式保持不变。
|
||||
|
||||
@@ -22,7 +22,7 @@ Web agent(智能体)既无法识别承载当前会话的 GUI,也不知道
|
||||
|
||||
## 验证
|
||||
|
||||
无密钥的 fresh-round-trip 浏览器场景会启动已交付的生产 Web 组合,驱动真实的回放会话,对包含 URL 和模式的系统提示词前缀生成快照,并调用组装后的 bash 工具,证明 `$DSH_WEB_URL` 和 `$DSH_WEB_MODE` 与实际绑定的运行时一致。真实 CLI 冒烟测试会启动 `dsh web --dev` 并捕获模型提供方请求,从而固定完整的双命令开发约定。`dev:web` watcher 测试会在源码发生变化后重新构建隔离的客户端 bundle;浏览器 HMR 场景会启动 `dsh web --dev`,修改生产初始 roster 中的 bundle,并在页面 identity 不变的情况下观察新 DOM。真实 Vite 子进程测试要求服务模式在给出改用完整宿主的纠正信息后自然退出,并通过插桩 `Server.listen()` 证明它从未被调用。真实 loader Web 服务器测试会在进程完成绑定后改写静态资源,并证明同一端口返回新的字节。这些断言检查提示词状态、进程退出状态、shell 输出、DOM identity 和 HTTP 字节,而不是 agent 的成功声明。
|
||||
无密钥的 fresh-round-trip 浏览器场景会启动已交付的 Web 组合,驱动真实的回放会话,对包含 URL 的系统提示词前缀生成快照,并调用组装后的 bash 工具,证明 `$DSH_WEB_URL` 与实际绑定的运行时一致。真实 CLI 冒烟测试会启动 `dsh web` 并捕获模型提供方请求,从而固定完整的双命令开发约定。`dev:web` watcher 测试会在源码发生变化后重新构建隔离的客户端 bundle;浏览器 HMR 场景会启动 `dsh web`,修改初始 roster 中的 bundle,并在页面 identity 不变的情况下观察新 DOM。真实 Vite 子进程测试要求服务模式在给出改用完整宿主的纠正信息后自然退出,并通过插桩 `Server.listen()` 证明它从未被调用。真实 loader Web 服务器测试会在进程完成绑定后改写静态资源,并证明同一端口返回新的字节。这些断言检查提示词状态、进程退出状态、shell 输出、DOM identity 和 HTTP 字节,而不是 agent 的成功声明。
|
||||
|
||||
## 考虑过的替代方案
|
||||
|
||||
@@ -30,10 +30,10 @@ Web agent(智能体)既无法识别承载当前会话的 GUI,也不知道
|
||||
|
||||
**删除 `apps/web` 开发脚本,但不为 Vite 添加防护。** 不予采纳,因为事故中实际使用的命令 `npx vite` 会绕过包脚本。服务模式本身必须失败。
|
||||
|
||||
**每次编辑后自动重启或替换当前 Web 进程。** 不予采纳,因为静态服务器本就会在每次请求时读取当前产物,重启还会中断发起编辑请求的会话,而插件 HMR 已有独立且显式的 `dsh web --dev` 组合。
|
||||
**每次编辑后自动重启或替换当前 Web 进程。** 不予采纳,因为静态服务器本就会在每次请求时读取当前产物,重启还会中断发起编辑请求的会话,而客户端插件重载由始终挂载的 HMR 链路加 `pnpm run dev:web` watcher 负责。
|
||||
|
||||
**每次请求都发送 DOM、路由或截图。** 推迟到另行设计的已记录输入机制。稳定的 URL 身份足以闭合本次反馈循环,同时不会声称宿主掌握其未接收的浏览器状态。
|
||||
|
||||
## 影响
|
||||
|
||||
常规 Web 提示词会增加一个动态 URL 和模式段落,因此模型提供方的前缀复用会随绑定端口和模式变化。相应的 Bash 进程会增加两个非敏感的受管环境变量。裸 Vite 不再能用作只依赖 shell 的视觉沙箱;开发者应改用完整宿主或构建模式。作为交换,GUI 工作有了一个可由机制观察的唯一目标,agent 可以向用户说明实际承载其会话的进程究竟如何更新,不受支持的启动路径也会在出现白屏前失败。URL/模式约定会引导 agent 避免使用替代端口,但不会禁止任意 shell 命令启动替代服务。禁用 `surfaceContext` 的 profile 也会放弃这项反馈闭环指引与 shell 上下文。
|
||||
常规 Web 提示词会增加一个动态 URL 段落,因此模型提供方的前缀复用会随绑定端口变化。相应的 Bash 进程会增加一个非敏感的受管环境变量。裸 Vite 不再能用作只依赖 shell 的视觉沙箱;开发者应改用完整宿主或构建模式。作为交换,GUI 工作有了一个可由机制观察的唯一目标,agent 可以向用户说明实际承载其会话的进程究竟如何更新,不受支持的启动路径也会在出现白屏前失败。URL 约定会引导 agent 避免使用替代端口,但不会禁止任意 shell 命令启动替代服务。禁用 `surfaceContext` 的 profile 也会放弃这项反馈闭环指引与 shell 上下文。
|
||||
|
||||
Reference in New Issue
Block a user