refactor(web): project usage and snapshot request context

This commit is contained in:
Hypatia May
2026-07-29 15:27:59 +08:00
parent e37cb23336
commit bf618dabf9
78 changed files with 748 additions and 934 deletions

View File

@@ -22,9 +22,9 @@ Directory picking delegates to the composed `ctx.directoryPicker` backend ([the
`host.openPath` opens a filesystem path with the operating system's default application (`open` on macOS, `Invoke-Item` on Windows, `xdg-open` on Linux). The opener is injectable for tests. The browser carrier applies the same loopback, same-origin restriction as `host.pickDirectory`.
`session.history` pages on message boundaries. Its tail page (no `beforeSeq`) carries the generic `projections` baseline, including the `todos` whole-list value when that unit is mounted, plus separate `metrics`: full-log usage deduplicated by `(turn, step)` and current token-meter pressure. Older pages omit both session-level carriers. Live `session/metrics` mux frames carry monotonic log/projection revisions, so clients reject stale frames and preserve the counters while prepending older pages. Cache reads and writes remain disjoint buckets; the cache-hit denominator is uncached input plus cache reads.
`session.history` pages on message boundaries. Its tail page (no `beforeSeq`) carries only the generic `projections` baseline for registered units; older pages omit it. When token-meter is composed with the projection registry, full-log provider billing rides the ordinary `tokenUsage` key. Its usage chunks and final messages are deduplicated by `(turn, step)`, while cache reads and writes remain disjoint buckets. ApiProxy owns no token-specific history field, mux frame, projector, revision counter, or refresh queue.
Context capacity uses a distinct transient `session/model-request` mux frame emitted from the contained Agent notification after an observed request attempt returns its outer stream handle. This boundary does not prove provider I/O began. The frame carries turn, step, final provider/model, and optional capacity only to mux connections already open at that instant. `session.history`, mux subscription baselines, reconnects, and session restore never query or replay prior capacity; a frame without capacity explicitly clears the earlier connection-local value.
Request context uses a distinct transient `session/model-request` mux frame emitted from the contained Agent notification after an observed request attempt returns its outer stream handle. This boundary does not prove provider I/O began. In the same synchronous event boundary, ApiProxy optionally reads `tokenMeter.measure(session).totalTokens` once and combines it with capacity from that exact prepared call. The atomic frame carries turn, step, final provider/model, and optional `contextTokens`/`contextWindow` only to mux connections already open at that instant. Measurement failure omits only the numerator. `session.history`, mux subscription baselines, reconnects, and session restore never query or replay an earlier snapshot, and missing fields in a later frame replace rather than inherit prior values.
The `command.*` and `skill.*` domains expose the host command registry and skill catalog to clients. Every method addresses one session's agent by `sessionId` (a served session always has an Agent; `command.*` resumes cold sessions through the same path as `session.*`, while `skill.list` resolves the project root from the session header without touching the Agent registry). `command.execute` runs a slash-command line host-side with pure admission semantics: the response reports whether the line resolved to a handler plus the minted lifecycle `commandId` when it did (correlating the acknowledgment with the flow node), while the outcome rides the durably logged `command/run`/`command/done` lifecycle pair broadcast on the mux stream; the carrier's request signal cancels the running handler. `host/commands-changed` is the catalog invalidation frame: clients refetch `command.list` instead of diffing.