Problem Statement
Three strands, all surfaced or sharpened by this session's recon of the Traycer codebase (agent-harness orchestrator; clone examined at protocol + client level, host closed-source).
-
Bridge logs are trapped in the serve terminal. wisp serve emits the Bridge's operational record — route resolutions, cooldown verdicts, retry/failure classifications, prompt-cache diagnoses — only to the stdout of whichever terminal ran it. When serve runs in another window, or the user wants to know what the Bridge said after something went wrong, there is nothing to read. The extension face has its OutputChannel; the headless face has scrollback or nothing.
-
The statusline can present an expired quota window as a current alarm. Meters are pruned by reading age (the 24h ledger rule) and age-stamped, but a meter's own resetAt is never compared to now. A 5h window read at 87% whose reset has since passed still renders an 87% alarm-colored bar — honest about when it was read, wrong about what it means: the window refilled and the account is fine. Traycer's rate-limit semantics module encodes the missing rule: an expired window is not a current reading.
-
Quota knowledge is passive-only, and that has a blind spot the Traycer comparison exposed. Wisp learns quota exclusively from response headers on bridged turns (the settled side-channel design). Before the first turn of a session, and after any idle period, the bars are absent or stale. Traycer instead actively probes the account usage endpoint (for Anthropic, the OAuth usage endpoint the Claude CLI itself calls; for Codex, its equivalent) on demand and on a 15-minute cadence, with a cooldown after usage-endpoint 429s. Whether those endpoints answer Wisp's stored OAuth credentials is unverified.
Also examined and closed this session: whether Traycer's Anthropic integration has a better Advisor implementation (it has none — Traycer never emulates the Anthropic wire; its "advisory" vocabulary is provider-CLI version notices, a term collision), and whether its Cursor provider offers a template for a Wisp Cursor Provider (it does not — see Out of Scope).
Solution
-
wisp log — a headless CLI command, sibling of wisp routing/wisp snapshot. wisp serve additionally appends every Bridge log line, timestamped, to a log file in the Wisp home (bridge.log), rotating one generation on serve start. wisp log prints the file with a staleness-visible header; wisp log -f follows it live. Logs survive the terminal and are readable from any other terminal while serve runs or after it dies.
-
Expired-meter semantics in the statusline — a meter whose resetAt is in the past renders as a dimmed "refilled" row (reset glyph, no alarm bar/percent) instead of a stale alarm. Applies to active-route meters and remembered-Provider rows alike. Ships as a wisp-slot plugin bump.
-
Usage-endpoint recon spike — throwaway probes (gitignored, never committed) drive the Anthropic OAuth usage endpoint with Wisp's stored OAuth token, and the Codex equivalent if evidence supports one, to answer: does the endpoint answer our token, what shape is the payload, does it carry anything the turn headers don't (Opus-scoped weekly window, extra-usage credits, authoritative limit-reached type)? Outcome is a build/no-build decision on an active quota probe, recorded on this spec — not an implementation.
User Stories
- As a wisp user running
wisp serve in a background terminal, I want to read the Bridge's logs from any other terminal, so that I don't have to hunt for the serve window to see what the Bridge did.
- As a wisp user whose bridged turn just failed, I want to read the Bridge's failure classification after the fact, so that I can tell a Provider outage from a routing mistake without reproducing the failure.
- As a wisp user, I want
wisp log -f to stream new lines as turns happen, so that I can watch route resolutions and cooldowns live while using Claude Code in another window.
- As a wisp user restarting serve after a crash, I want the previous run's log preserved one generation back, so that the evidence isn't destroyed by the restart.
- As a wisp user, I want each log line timestamped, so that I can correlate a log entry with a Claude Code turn or an error I saw at a known time.
- As a wisp user reading
wisp log while the extension face hosts the Bridge, I want the output to make its own staleness obvious, so that I never mistake an old serve run's log for the live host's activity.
- As a wisp user, I want
wisp log to work without starting the TUI renderer, so that it is fast and usable over SSH or in scripts.
- As a Claude Code user on a bridged session, I want a quota bar whose window has already reset to stop showing the old alarm percentage, so that I don't avoid working under a false near-limit warning.
- As a Claude Code user, I want a refilled window to be visibly marked as refilled rather than silently dropped, so that I know the wire reported recently and the window rolled — not that the reading vanished.
- As a Claude Code user with several remembered Providers in the statusline, I want expired windows on those rows to get the same refilled treatment, so that dimmed history rows don't cry wolf either.
- As the maintainer, I want the statusline's new expiry logic pinned by caller-shaped fixtures with the pre-fix file as a failing control, so that the check suite proves the fix rather than agreeing with it.
- As the maintainer, I want to know whether the Anthropic OAuth usage endpoint answers Wisp's stored token before designing any active probe, so that no feature is built on an unverified wire.
- As the maintainer, I want the usage-endpoint probes kept out of the repo and their outputs redacted, so that account-identifying values never land in a public repo.
- As the maintainer, I want the recon outcome recorded as a build/no-build decision with the payload evidence attached, so that the next session can act on it without re-probing.
- As a wisp user, I want quota bars to be available early in a session (should the probe prove viable and get built later), so that I can see account state before spending the first turn.
Implementation Decisions
wisp log lives entirely in the TUI face. No core seam: the logic (append, rotate, print, follow) is too thin to earn a pure decision layer, unlike the routing CLI. The writer wraps serve's existing single log callback — the one funnel every Bridge line already passes through. The extension face is untouched; its OutputChannel already serves that face.
- The log file is regenerable telemetry, exactly like the statusline snapshot. It lives in the Wisp home beside status.json, is never protected from overwrite (the no-overwrite rule guards stores the user cannot regenerate; a log regenerates every run), and is invisible to the home-store directory watcher by the existing rule that ignores non-
.json names — no watcher change.
- Rotation is one-deep, on serve start: current file renamed to a
.1 sibling, new run starts fresh. No size caps, no time-based rotation, no level filtering — the volume is a few short lines per turn.
- Line format: ISO timestamp prefix + the log line verbatim. The Bridge's own
[bridge] prefixes stay; the file is the terminal record, timestamped.
wisp log is a renderer-free dispatch branch like routing/snapshot/providers: lazily imported, never touching the TUI renderer. Default prints the whole current file preceded by one header line naming the file and its last-write time (staleness signal). -f/--follow prints the header + existing tail, then streams appended lines until Ctrl+C. Missing file → one-line message naming what to run (wisp serve), exit 0 for the plain read (nothing-to-show is not an error), consistent with sibling commands' exit-code discipline.
- Statusline expiry rule: at render time, a meter with a numeric
resetAt at-or-before now is expired — rendered as a dimmed row keeping its label and the reset glyph with a "refilled" marker, dropping the bar and alarm-colored percent. Never silently dropped. The 24h ledger prune is unchanged and orthogonal (reading age vs window validity). The duplicated-resolver landmine is untouched — this changes rendering only, not route resolution.
- wisp-slot ships the statusline change with its two-file version bump (plugin manifest + marketplace manifest), independent of the npm cut.
- Usage-endpoint recon is a bounded spike, not a feature. Probes are throwaway scripts under the gitignored scratch output directory, driven per the settled prove-a-wire-by-driving-it pattern; account-identifying values in payloads are redacted on the value (the usage payload may embed account identifiers under unpredictable keys, as the Antigravity recon proved). One probe per wire, 2–3 reads, respect the endpoint's known multi-minute 429 penalty by not hammering. Deliverable: a comment on this spec with payload shape + build/no-build verdict; a follow-up ticket exists only if the verdict is build.
- The passive header side-channel stays the source of truth for 2.0.43. Nothing in this release changes how quota reaches status.json; the probe recon only gathers evidence for a possible future active lane.
Testing Decisions
- Good tests here observe external behavior at the highest existing seam. For
wisp log that seam is the compiled entry's argv dispatch: run serve and log through the real entry points under a sandboxed home (the scoped tui verify skill), asserting on files and stdout — not on internals. Prior art: the routing and snapshot CLI verifications.
- The statusline change is gated solely by its own assertion suite (the check script), extended with expiry cases. Two hard local rules apply: fixtures must use the shape the caller actually sends (picker-prefixed model ids, epoch-second resets), and the pre-fix script must be run as a failing control — assertions that pass both ways are labelled pins, not proof.
- No package gate runs for the plugin-only ticket; the tui ticket runs the tui typecheck/compile gates plus the workspace test suite.
- The recon spike has no test gate — its verification is the probe transcript itself, redacted, attached to the spec comment.
Out of Scope
- A Cursor Provider — settled no-build for this cycle. The Traycer clone was examined precisely for this. Verdict: Traycer's Cursor "provider" is a GUI-only agent harness driven through
@cursor/sdk with a user API key; there is no Cursor model wire, no Cursor OAuth, not even the binary's name in the open repo. Cursor exposes no chat-completions-shaped API on user keys, and an agent SDK is the wrong shape to sit behind the Bridge's turn model (an agent loop inside a model call). Nothing exists to route through. Revisit only if Cursor ships a raw model API. Recorded as a decision note in the project context.
- Extension-face log capture (OutputChannel already covers it).
- Building the active usage probe (the spike decides; any build is its own future spec).
- Log levels, filtering, size-based rotation, multi-generation retention.
- Any change to quota parsing, the ledger, or
mergeStatus eviction semantics.
Further Notes
- Traycer recon caveats: its host binary is closed-source; the passive half of its rate-limit gauge (whether live turns parse headers) is unprovable from the clone. The adoptable ideas were taken from its protocol schemas and semantics tests, which are open and pinned by fixtures.
- Term collision for future readers: Wisp's Advisor is Claude Code's advisor server tool that the Anthropic door emulates; Traycer's "advisory" is a provider-CLI version notice. They are unrelated, and Traycer has no Advisor implementation to compare against.
- Release surfaces expected: the
wisp log ticket touches the tui package → npm 2.0.43 cut; the statusline ticket touches plugins only → wisp-slot bump; the recon ticket touches nothing shipped.
Problem Statement
Three strands, all surfaced or sharpened by this session's recon of the Traycer codebase (agent-harness orchestrator; clone examined at protocol + client level, host closed-source).
Bridge logs are trapped in the serve terminal.
wisp serveemits the Bridge's operational record — route resolutions, cooldown verdicts, retry/failure classifications, prompt-cache diagnoses — only to the stdout of whichever terminal ran it. When serve runs in another window, or the user wants to know what the Bridge said after something went wrong, there is nothing to read. The extension face has its OutputChannel; the headless face has scrollback or nothing.The statusline can present an expired quota window as a current alarm. Meters are pruned by reading age (the 24h ledger rule) and age-stamped, but a meter's own
resetAtis never compared to now. A 5h window read at 87% whose reset has since passed still renders an 87% alarm-colored bar — honest about when it was read, wrong about what it means: the window refilled and the account is fine. Traycer's rate-limit semantics module encodes the missing rule: an expired window is not a current reading.Quota knowledge is passive-only, and that has a blind spot the Traycer comparison exposed. Wisp learns quota exclusively from response headers on bridged turns (the settled side-channel design). Before the first turn of a session, and after any idle period, the bars are absent or stale. Traycer instead actively probes the account usage endpoint (for Anthropic, the OAuth usage endpoint the Claude CLI itself calls; for Codex, its equivalent) on demand and on a 15-minute cadence, with a cooldown after usage-endpoint 429s. Whether those endpoints answer Wisp's stored OAuth credentials is unverified.
Also examined and closed this session: whether Traycer's Anthropic integration has a better Advisor implementation (it has none — Traycer never emulates the Anthropic wire; its "advisory" vocabulary is provider-CLI version notices, a term collision), and whether its Cursor provider offers a template for a Wisp Cursor Provider (it does not — see Out of Scope).
Solution
wisp log— a headless CLI command, sibling ofwisp routing/wisp snapshot.wisp serveadditionally appends every Bridge log line, timestamped, to a log file in the Wisp home (bridge.log), rotating one generation on serve start.wisp logprints the file with a staleness-visible header;wisp log -ffollows it live. Logs survive the terminal and are readable from any other terminal while serve runs or after it dies.Expired-meter semantics in the statusline — a meter whose
resetAtis in the past renders as a dimmed "refilled" row (reset glyph, no alarm bar/percent) instead of a stale alarm. Applies to active-route meters and remembered-Provider rows alike. Ships as a wisp-slot plugin bump.Usage-endpoint recon spike — throwaway probes (gitignored, never committed) drive the Anthropic OAuth usage endpoint with Wisp's stored OAuth token, and the Codex equivalent if evidence supports one, to answer: does the endpoint answer our token, what shape is the payload, does it carry anything the turn headers don't (Opus-scoped weekly window, extra-usage credits, authoritative limit-reached type)? Outcome is a build/no-build decision on an active quota probe, recorded on this spec — not an implementation.
User Stories
wisp servein a background terminal, I want to read the Bridge's logs from any other terminal, so that I don't have to hunt for the serve window to see what the Bridge did.wisp log -fto stream new lines as turns happen, so that I can watch route resolutions and cooldowns live while using Claude Code in another window.wisp logwhile the extension face hosts the Bridge, I want the output to make its own staleness obvious, so that I never mistake an old serve run's log for the live host's activity.wisp logto work without starting the TUI renderer, so that it is fast and usable over SSH or in scripts.Implementation Decisions
wisp loglives entirely in the TUI face. No core seam: the logic (append, rotate, print, follow) is too thin to earn a pure decision layer, unlike the routing CLI. The writer wraps serve's existing single log callback — the one funnel every Bridge line already passes through. The extension face is untouched; its OutputChannel already serves that face..jsonnames — no watcher change..1sibling, new run starts fresh. No size caps, no time-based rotation, no level filtering — the volume is a few short lines per turn.[bridge]prefixes stay; the file is the terminal record, timestamped.wisp logis a renderer-free dispatch branch likerouting/snapshot/providers: lazily imported, never touching the TUI renderer. Default prints the whole current file preceded by one header line naming the file and its last-write time (staleness signal).-f/--followprints the header + existing tail, then streams appended lines until Ctrl+C. Missing file → one-line message naming what to run (wisp serve), exit 0 for the plain read (nothing-to-show is not an error), consistent with sibling commands' exit-code discipline.resetAtat-or-before now is expired — rendered as a dimmed row keeping its label and the reset glyph with a "refilled" marker, dropping the bar and alarm-colored percent. Never silently dropped. The 24h ledger prune is unchanged and orthogonal (reading age vs window validity). The duplicated-resolver landmine is untouched — this changes rendering only, not route resolution.Testing Decisions
wisp logthat seam is the compiled entry's argv dispatch: run serve and log through the real entry points under a sandboxed home (the scoped tui verify skill), asserting on files and stdout — not on internals. Prior art: the routing and snapshot CLI verifications.Out of Scope
@cursor/sdkwith a user API key; there is no Cursor model wire, no Cursor OAuth, not even the binary's name in the open repo. Cursor exposes no chat-completions-shaped API on user keys, and an agent SDK is the wrong shape to sit behind the Bridge's turn model (an agent loop inside a model call). Nothing exists to route through. Revisit only if Cursor ships a raw model API. Recorded as a decision note in the project context.mergeStatuseviction semantics.Further Notes
wisp logticket touches the tui package → npm 2.0.43 cut; the statusline ticket touches plugins only → wisp-slot bump; the recon ticket touches nothing shipped.