PART 1 — one introspectable command registry + one dispatcher (decomplected):
- src/services/app-commands.ts is the single source of truth: AppHost capability
port + 16 typed commands (name, JSON-schema params, human description, run()) +
commandManifest() + the ONE dispatch(): gate (sign-in) → validate → execute →
visible action log. Full control now covers panels (show/hide/move/resize),
map (layers, 2D/3D, fly-to, region, time range), variant, theme, search,
reset layout, custom feeds, and org switch — each driving existing App APIs
through the port (no reaching into internals).
- analyst-actions.ts reduced to a type shim (AnalystHost=AppHost). AnalystChat
dispatches through the registry and renders per-command ✓/✗ entries.
- Go tool contract DERIVES from the registry: the client sends its manifest with
every request; handlers_analyst.go builds the action-contract prompt from it
(renderCommandContract) and passes actions through generically, gated by the
manifest's type set — the backend no longer duplicates the schema. Embedded
mirror data/analyst_commands.json (generated by scripts/gen-analyst-commands.mjs,
drift-guarded by TestAnalystCommandManifest) covers callers that omit a manifest.
Model dropdown + backend plane (CTO directive):
- Chat header carries a monochrome model select (Zen family first, then upstream
/v1/models + agents); choice persists and rides every request. New same-origin
/v1/world/models proxy; model passes through to inference (chatMessagesModel).
"agent:<ref>" ids route to the agents run plane (invoke an agent in-chat).
P0 — errors always visible: the chat never shows a blank. An empty/degraded/errored
backend reply (e.g. prod's aud=hanzo-world 401) now surfaces the reason inline.
PART 2 — transport seam: src/services/analyst-transport.ts isolates the wire; HTTP
is the real impl, registerTransport() is the ZAP seam. Verdict: real browser ZAP
(@zap-proto/web) exists and is production-wired in hanzo/platform, but world has no
ZAP dep and no Go WS-ZAP server, and ZAP has no streaming primitive today — so HTTP
stays default, ZAP wiring documented in-file (no fantasy transport).
Gates: tsc + vite build + go build + go test ./internal/world/ all pass; Playwright
(e2e/analyst-command.spec.ts) proves signed-out sign-in prompt and a signed-in
command round-trip that visibly moves a panel + logs the action (screens/).
Claude-Session: https://claude.ai/code/session_013jh8aka8q8RvhhVQ1psMeW