fix(web): verify current GUI updates end to end

This commit is contained in:
NI0317
2026-07-29 11:22:48 +08:00
parent d8004e9956
commit cd88a339fa
27 changed files with 438 additions and 89 deletions

View File

@@ -8,11 +8,13 @@ English | [中文](2026-07-28-web-gui-feedback-loop.zh.md)
The Web agent could identify neither the GUI hosting its session nor the URL the user was viewing. The [runtime-context decision](2026-07-28-web-agent-runtime-context.md) supplies the first fact, but a GUI edit still had no executable acceptance target: source edits, artifact builds, a listening process, and the user's existing page were unrelated observations. Repository affordances made a wrong substitute look valid because `apps/web/package.json` exposed `vite` as its `dev` script and bare Vite returned HTTP 200 even though it could not inject `window.__DSH_BOOT__`.
The incident session recorded three consecutive failures. After changing the theme, turn 2 delegated acceptance to the user with `pnpm run demo:tui` or an unspecified browser application and ran no assembled Web check. Turn 3 read the frontend package script, launched bare Vite on port 5173, treated HTTP 200 as readiness, and reported success; the user instead received the expected missing-`__DSH_BOOT__` white screen. Turn 4 found `dsh web`, rebuilt the shell, started an unmanaged shell-background process on port 3334, and checked only that the new page returned 200 with a boot manifest. It never probed the existing port 3081. In fact, the port-3081 process predated the build, and its static host read the rebuilt dist on the next request, so refreshing the original page already showed the change. Only after the user reported that fact did turn 5 inspect port 3081 and remove the redundant server.
The [incident post-mortem](../../../../docs/postmortem/0003-web-agent-gui-feedback-loop.md) owns the event-log timeline and why the original checks accepted the wrong page, process, and port.
## Decision
`dsh web` 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, names the URL, and defines acceptance as rebuilding the affected Web artifacts and verifying that existing URL after refresh. `DSH_WEB_URL` carries the same value into every foreground or managed background bash call, so the agent can query the target without parsing prose or process listings. 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.
`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 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 `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.
@@ -20,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 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` equals the scaffold's actual bound URL. A real Vite subprocess test requires serve mode to exit nonzero with the full-host correction. 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, and HTTP bytes rather than an agent's success statement.
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.
## Alternatives considered
@@ -34,4 +36,4 @@ The keyless fresh-round-trip browser scenario boots the shipped Web composition,
## Consequences
Web prompts gain a dynamic URL paragraph, so provider prefix reuse now varies by bound port. 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 unsupported startup path fails before a white screen, and a second port can no longer masquerade as proof that the user's current page changed.
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.