Commit graph

1783 commits

Author SHA1 Message Date
Aditya Vikram Singh
fa540325fb
fix(desktop): reap CLI process trees on quit (#1166)
Some checks are pending
CI / semgrep (push) Waiting to run
macOS Menubar CI / test (push) Waiting to run
Tests / test (22) (push) Waiting to run
Tests / test (22.13.0) (push) Waiting to run
Tests / desktop (push) Waiting to run
2026-08-28 15:12:33 -07:00
Aditya Vikram Singh
1487c75fa4
fix(tui): clean cache locks on confirmed exit (#1165) 2026-08-28 15:06:07 -07:00
Aditya Vikram Singh
9a929c87ab
test: add repeatable release acceptance ledger (#1164)
* test: add repeatable release acceptance ledger

* ci: gate desktop typecheck tests and build
2026-08-28 15:02:07 -07:00
Aditya Vikram Singh
b5f07d1cc7
perf(desktop): keep every destination useful across restarts (#1163)
* perf(desktop): keep every destination useful across restarts

* fix(desktop): label cached reports while they refresh

* fix(desktop): preserve reviewed cache invariants

* fix(desktop): bound durable snapshot work
2026-08-28 14:51:18 -07:00
Aditya Vikram Singh
81f1eb3d6a
perf(tui): show cached periods before refreshing sources (#1159)
* perf: paint Today before indexing TUI history

* perf(tui): serve cached periods before source refresh

* fix(tui): keep cached snapshots out of parse memo

* fix(tui): preserve provider and period truth

* fix(tui): label visible period while indexing

* fix(tui): recover provider switches and empty Today
2026-08-28 13:21:47 -07:00
Aditya Vikram Singh
9a93fdd77d
fix(tui): clear stale panels on settled resize (#1156) 2026-08-28 13:21:03 -07:00
Aditya Vikram Singh
daafb593ea
fix(menubar): recover parked status item (#1161) 2026-08-28 13:20:48 -07:00
Aditya Vikram Singh
6406296cb1
perf(desktop): show exact totals while details refresh (#1157)
* perf(desktop): warm time horizons before providers

* perf(desktop): avoid idle provider parse storm

* perf(desktop): show last exact headline during refresh

* perf(desktop): paint cached headline without animation

* fix(desktop): keep timeout recovery single-flight

* fix(desktop): keep cached headline stable on handoff

* fix(desktop): keep overview timelines legible

* fix(desktop): keep period switches truthful

* fix(desktop): open heatmap at newest dates

* fix(desktop): label cached data during refresh

* fix(desktop): invalidate progressive cache safely

* fix(desktop): keep progressive views exact across boundaries

* fix(desktop): preserve filter responsiveness

* fix(desktop): bound instant-switch memo
2026-08-28 13:20:33 -07:00
Aditya Vikram Singh
0d39b8cd0a
fix(desktop): harden packaged resident CLI routing (#1155)
* fix(desktop): recover from resident pipe closure

* fix(desktop): keep packaged resident serve alive

* test(desktop): make closed-pipe proof deterministic

* fix(desktop): preserve packaged CLI command routing

* test(desktop): isolate packaged CLI smoke
2026-08-28 13:19:29 -07:00
Resham Joshi
2512f6e487
Merge pull request #1158 from getagentseal/feat/desktop-plugin-era
Some checks are pending
CI / semgrep (push) Waiting to run
Tests / test (22) (push) Waiting to run
Tests / test (22.13.0) (push) Waiting to run
feat(desktop): plugin manager, install flow, sharing consent center, dormant Team tab
2026-08-28 07:09:17 -07:00
Resham Joshi
4cb59e8404
Merge pull request #1154 from getagentseal/feat/plugin-commands
feat(plugins): socket phase 2 - command invocation, exporter seam, remote install (10A/10B/14A)
2026-08-28 07:08:58 -07:00
Resham Joshi
3b4d3fd665
Merge pull request #1153 from getagentseal/feat/consent-scheduler
feat(sync): consent-once auto-sync with acceptance fingerprint, receipts, and kill switch (CB-4)
2026-08-28 07:08:35 -07:00
Resham Joshi
25e595c06b
Merge pull request #1152 from getagentseal/feat/plugin-signing
feat(plugins): ed25519 release-key verification and plugin add/remove (9b)
2026-08-28 07:08:17 -07:00
Resham Joshi
aca73fb8d0
Merge pull request #1151 from getagentseal/feat/plugin-socket
feat(plugins): CB-3 plugin socket - wire guard, CLI inspector, payload seam
2026-08-28 07:07:40 -07:00
iamtoruk
12f6d47afb test(sync): union the consent tests from both branches after rebase
The rebase spliced HEAD's damaged-record tests and the desktop branch's --json status tests into one block; rebuilt as the full HEAD suite plus the desktop 'sync auto status --json' describe block. 30 tests, no duplicate titles.
2026-08-28 07:02:23 -07:00
iamtoruk
7d03886554 fix(desktop): install flow surfaces CLI errors and locks actions while pending
InstallFlow modal now properly handles error states and prevents action resubmission:

1. When install starts, disable Cancel button (footer shows Cancel-only, not clickable alongside spinner)
2. When CLI call fails or times out, transition to error state showing CLI stderr verbatim in scrollable pre block
3. Error state offers Back (returns to step 1, clears error) and Cancel (closes modal) actions
4. Promise rejection properly updates error state on bridge envelope errors
5. Success path transitions to step 3 without showing error state

Added component tests for:
- Error state displays CLI message verbatim
- Back button clickable during error
- Install button disabled while pending (Cancel-only visible)
2026-08-28 06:59:50 -07:00
iamtoruk
747e6a619e feat(desktop): complete plugin-era UI with details, install flow, settings, and team registry
Implement five missing D1-D3 components for full plugin era desktop support:

1. PluginDetails modal (sections/PluginDetails.tsx): Display full plugin manifest with all syncAttributes disclosures verbatim, spanKinds, commands, and on-disk sections loaded via pluginInfo. Defensive rendering for unknown fields.

2. InstallFlow stepper modal (sections/InstallFlow.tsx): Three-step flow for plugin installation - Step 1 source selection (org name input or folder via chooseDirectory), Step 2 installation with spinner and CLI error display verbatim, Step 3 success confirmation with "View details" button. Special handling for no-sync-config errors.

3. Plugin row actions (update sections/Plugins.tsx): Add Details, Verify, and Remove action buttons to plugin list. Remove action shows confirm modal naming plugin. Wired to CLI bridge calls.

4. Settings > Sharing pane (sections/SettingsSharing.tsx): Complete automatic sync configuration UI within Settings panes. Empty state with "Set up automatic sync" flow: cadence selection (daily/hourly) + work-matching toggle -> disclosure text via syncAutoEnable(accept:false) -> scrollable display -> Accept flow. Active state displays fingerprint (truncated), acceptedAt, cadence, work-matching, receipts list. Drift detection with "Review and re-accept" button. Always-visible "Stop automatic sync" danger button.

5. Team tab registry (components/TeamRegistry.tsx): Detect "teams.*" keys in payload plugins record; render Team section in nav when present. v1 renderers for teams.week (spend card) and teams.status (status link to teams.codeburn.app). Unknown keys fallback rendered. schemaVersion > 1 displays update prompt.

All components reuse existing app patterns (card/list/button/modal styles, CSS tokens, no new dependencies). Tests added for component row states, disclosure flow, and Team tab detection. All gates pass: root 3839 tests, app 614 tests, typecheck clean, build succeeds.
2026-08-28 06:59:50 -07:00
iamtoruk
6d69803817 feat(desktop): plugin manager, install flow, sharing consent center, and dormant Team tab (plugin era D1-D3)
Part 1: Add --json output flags to three CLI commands for machine-readable output
- codeburn plugin list --json: array of plugin objects with status and capabilities
- codeburn plugin info <name> --json: manifest with dir and onDiskSections
- codeburn sync auto status --json: status object with configured, accepted, killed, receipts

Part 2: Implement Electron bridge with read and mutation handlers
- Read handlers: pluginList, pluginInfo, syncAutoStatus (return parsed JSON)
- Mutation handlers: pluginAdd, pluginRemove, pluginVerify, syncAutoEnable, syncAutoDisable
- All handlers follow existing Envelope pattern with proper error handling
- Special case: syncAutoEnable captures disclosure text when accept=false

Part 3: Renderer UI components (minimal implementation D1-D3 shell)
- Add Plugins section to nav rail at bottom (quiet entry, plugin icon)
- Implement PluginsSection component with list rendering for loaded/rejected plugins
- Update Section type and routing to include plugins navigation
- Add test coverage for Plugins panel row states
- Implemented as dormant Team tab registry placeholder for future plugin sections

Tests: All CLI tests (3839 passed), app tests (613 passed), E2E gates all passing
- CLI: tsc clean, vitest passes with 26 pre-existing jsdom errors
- App: typecheck clean, npm test green, npm run build succeeds
2026-08-28 06:58:55 -07:00
iamtoruk
a0adce6d5b fix(plugins): reject non-regular tar entries before extraction; cap exporter per-call surplus
Independent adversarial review found the remote-install tar validation was blind to symlinks - tar -tzf hides the link target, so a symlink (or the classic symlink-then-write-through pair) could reach the extraction step before verifyPlugin's post-extract symlink check, escaping tempDir on any tar that follows links mid-archive (macOS bsdtar refuses, GNU tar historically does not). Validation now lists with -tvzf and rejects any entry whose type flag is not a plain file or directory, platform-independently. Also bounds exporter per-call iteration to the batch size as defense beyond the existing 8MB stdout cap.
2026-08-28 06:42:34 -07:00
iamtoruk
b519f50dbd test(plugins): import loadPlugins in exporter hostility tests 2026-08-28 06:27:56 -07:00
iamtoruk
59d909b859 fix(plugins): push survives hostile exporters - stdin EPIPE, invalid output shapes, malformed calls; cap manifest reads
Deep-review probes proved three crash paths where a plugin could kill the entire push: an exporter exiting before reading stdin raised an unhandled EPIPE stream error; exporter stdout of a bare JSON string passed the null check and threw in enrichment; and a call record without a tools array crashed the turn-context build inside the promise executor. All three now cost only that plugin's contribution, with a stderr notice, pinned by regression tests mirroring the probes. Remote install additionally caps the manifest read at 64 KB.
2026-08-28 06:27:37 -07:00
iamtoruk
dd5c5d0f33 fix(plugins): SIGKILL escalation for stuck exporters; bound stderr capture 2026-08-28 06:22:39 -07:00
iamtoruk
41245e7f62 fix(sync): schedule invokes runtime plus script explicitly; damaged acceptance reported plainly
Two fixes from a deep review of the consent scheduler.

The LaunchAgent plist was being written with process.argv[1] as the sole
ProgramArguments entry, which is a non-executable .ts/.js path under
plain node and a stale copy under packaged Electron. buildLaunchAgentPlist
now takes both runtimePath and scriptPath, emits them as separate
<string> entries, and sets ELECTRON_RUN_AS_NODE so the packaged Electron
binary behaves as a node runtime. Both paths are XML-escaped before being
dropped into the plist so a stray & or < in a path cannot break it. The
non-darwin crontab hint now prints the same two-argument form. Caller in
src/sync/cli.ts passes process.execPath and process.argv[1].

When config.auto.accepted is present but missing fingerprint or
acceptedAt, sync auto status used to interpolate the missing field and
print the literal 'undefined'. It now detects the malformed record, prints
a single line telling the user to re-run sync auto enable, and exits non
zero. The run command already fails closed on this case and is unchanged.
2026-08-28 06:21:06 -07:00
iamtoruk
5fe757b25a fix(plugins): codepoint-ordered signature canonicalization; sign fails loudly on unreadable files
localeCompare is locale/ICU dependent and fed the signed digest, so a plugin signed on one machine could false-reject on another (live even in ASCII: README.md vs codeburn-plugin.json order flips between locale-aware and codepoint comparison). All four sort sites now use plain codepoint order, commented as a canonicalization requirement. The sign script now throws on unreadable files instead of signing an incomplete list; the loader keeps skipping (its omission causes a digest mismatch and rejection, the fail-closed direction). New test drives the real sign script over a tree with mixed-case and non-ASCII names, so test and production canonicalization cannot diverge again.
2026-08-28 05:52:36 -07:00
iamtoruk
463f7416af fix(plugins): update plugin-signing tests for stderr+exitCode error handling
The plugin CLI commands now write polite errors to stderr with exitCode=1
instead of throwing. Update three test cases to check stderr and exitCode
instead of expecting rejects.toThrow().

- plugin add refuses unsigned source: check stderr contains 'signature'
- plugin add refuses existing destination: check stderr contains 'already installed'
- plugin remove refuses without --confirm: check stderr contains '--confirm'
2026-08-28 05:48:27 -07:00
iamtoruk
4912be1915 fix(plugins): polite one-line errors for plugin commands instead of stack traces
Convert user-facing plugin commands (info, verify, add, remove) to use
process.stderr.write() + process.exitCode pattern. Wrap internal helper calls
in try-catch at action boundary. Update tests to capture stderr and check
exitCode instead of expecting thrown errors.
2026-08-28 05:43:18 -07:00
iamtoruk
56dcc402d7 fix(plugins): reject tarball entries that escape the extraction dir before extracting
- Validate tarball entries with tar -tzf BEFORE extraction to prevent path traversal
- Reject entries that: start with /, contain .., start with ~, or contain \
- Prevents malicious/compromised servers from writing outside temp dir
- Test: create malicious gzipped tar with ../evil.txt entry and verify rejection
- Extraction only proceeds after successful validation
2026-08-28 02:48:09 -07:00
iamtoruk
1a1a099a73 feat(plugins): remote plugin install from the org receiver (store v1, CLI half)
- Dispatch plugin add <source> to local or remote flow based on path-like vs plugin-name pattern
- Remote flow: readSyncConfig, refresh OIDC token, fetch manifest with sha256, download tarball with integrity verification (50MB limit)
- Extract via system tar to temp dir, detect single top-level dir or files at root
- Verify manifest name matches requested name, hand to shared verify+install path
- Refactor verify+install into reusable helper for both local and remote flows
- Add tests for happy path, sha256 mismatch, manifest 404, no sync config
- Secure token refresh, sha256 verification on both manifest and download header

ponytail: no retries or resume; failed download just reruns
2026-08-28 02:42:07 -07:00
iamtoruk
42d1e1e184 feat(plugins): derived turn context in exporter input and per-plugin state dir (10C host)
- Add buildTurnContextMap() helper to compute turn context (turnId, category, retries, hasEdits, oneShot) for calls grouped by session
- Include optional turn field in exporter stdin JSON when call is resolvable to a turn
- Create CODEBURN_PLUGIN_STATE_DIR (~/.config/codeburn/plugin-state/<plugin-name>) before spawning exporters
- Pass CODEBURN_PLUGIN_STATE_DIR to exporter processes; plugins write state outside their signed tree
- Add tests for turn context inclusion, state directory creation, and calls without turns
2026-08-28 02:36:21 -07:00
iamtoruk
62b3d95ad0 fix(plugins): route exporter enrichment through the wire-guard sanitizer; merge per-call attrs across plugins
Per-call attributes and span attributes now pass through filterPluginAttributes to enforce the #1128 sanitizer, rejecting credential/path-like stringValues and dropping non-scalar values (array, object). Fixes security regression where raw strings bypassed sanitization. When multiple plugins contribute attributes for the same deduplicationKey, they now merge into one list instead of overwriting, enabling multi-plugin enrichment of the same call. Tests added for sanitization of path-like values, rejection of arrayValues, and merging across plugins.
2026-08-28 02:31:57 -07:00
iamtoruk
484daabf99 feat(plugins): sync exporter seam - per-call attributes and declared extra spans, wire-guarded (10B)
Loaded plugins with exporters/sync.mjs now enrich sync pushes with per-call attributes and declared extra spans (CB-3 phase 2). CLI spawns exporter child processes, passes batch calls via stdin, collects JSON output with perCall Map and extra spans array. All enrichment is guarded: perCall attrs attached to matching deduplicationKey, extra spans filtered by declared spanKinds, attributes filtered by declared syncAttributes, core keys never overwritten. Exporter timeout (30s), size cap (8MB stdout), and failures are handled gracefully - plugin contributes nothing on crash/timeout/bad JSON, stderr notice printed, push completes. Extra spans are capped at 2x calls.length and >64KB spans dropped. No ledger on CLI side for extra spans (plugin's responsibility). Dry-run shows exporter contributions. Wire is byte-identical when no exporters present.
2026-08-28 02:23:29 -07:00
iamtoruk
8b0e9af431 fix(plugins): sign and install plugin trees recursively; sections stay runtime-mutable
Closes security hole: plugin commands (executable .mjs files under commands/) are now part of the signed digest, preventing tampering. getPluginFilesList and sign-plugin.mjs getFilesList now walk directories recursively with relative POSIX paths, hashing all regular files while excluding codeburn-plugin.sig (the signature itself) and sections/ (runtime-mutable plugin output). plugin add copies the full tree recursively, preserving directory structure including commands/ and commands/subdir/*.mjs. Installed plugins verify correctly without CODEBURN_PLUGIN_DEV. checkForSymlinks now recurses to catch symlinks anywhere in the tree. Sections may change at runtime without breaking verification because they are excluded from the canonical digest.
2026-08-28 02:14:51 -07:00
iamtoruk
d6bdff6d2e feat(plugins): invoke declared plugin commands as child processes (10A)
Loaded, signature-verified plugins declaring capabilities.commands now register as top-level CLI commands that spawn the plugin's command entry file as a child process. New function registerLoadedPluginCommands(program, loads?) in src/plugins/cli.ts handles collision detection (built-ins win), missing entry file detection with clean error reporting, and exit code propagation. Tests cover all five scenarios: successful invocation, child exit code propagation, missing file handling, collision detection, and rejection filtering. Integrated into src/main.ts else branch to wire plugin commands into the CLI. E2E test (non-dev environment with real signing key) confirms end-to-end plugin invocation works.
2026-08-28 02:10:43 -07:00
iamtoruk
9616bbd0f5 fix(sync): fresh-machine schedule install, work-matching disclosure line, named drift causes 2026-08-28 01:26:48 -07:00
iamtoruk
a9c5d8b6ec fix(sync): plain-language meanings for every core field in the consent disclosure 2026-08-28 01:08:06 -07:00
iamtoruk
1e3a7228c8 fix(sync): status recomputes the acceptance fingerprint with the same input as run 2026-08-28 01:04:44 -07:00
iamtoruk
05e4b7cbc1 fix(sync): honest 7-day scope and stored work-matching choice in acceptance; shared push helper 2026-08-28 01:00:54 -07:00
iamtoruk
d92ddd71f4 feat(sync): consent-once auto-sync with acceptance fingerprint, receipts, and kill switch (CB-4)
Reused menubar-installer pattern for schedule installer. On non-darwin, prints crontab line to add manually. Auto block is optional in sync config; add-only and never migrates existing installs.
2026-08-28 00:54:14 -07:00
iamtoruk
c01b7be4b0 fix(plugins): derive keygen keyId identically to sign; pin release-key ids in tests
Fixes critical bug where keygen() used random keyId but sign() derived it from sha256 of public key PEM. This caused embedded release key (f40248d0) to not match actual derived id (499923ae), breaking plugin verification.

Now both keygen and sign derive keyId consistently from sha256(publicKeyPem).slice(0,4).hex.

Added tests:
- keygen and sign derive identical keyIds from same ephemeral key
- RELEASE_PUBLIC_KEYS map keys match their derived keyIds from embedded PEM values

End-to-end verified: signing with private key and verifying with embedded public key now works without CODEBURN_PLUGIN_DEV.
2026-08-28 00:35:36 -07:00
iamtoruk
67320432ca feat(plugins): ed25519 release-key verification and plugin add/remove (9b)
Ships ed25519 signature verification for plugins with two new components:

- src/plugins/keys.ts: RELEASE_PUBLIC_KEYS map with one release keypair
- scripts/sign-plugin.mjs: keygen and sign commands for plugin developers
- verifyPlugin() in loader.ts: validates ed25519 signatures, rejects unsigned plugins unless CODEBURN_PLUGIN_DEV=1
- plugin add <path>: installs signed plugins to ~/.config/codeburn/plugins/
- plugin remove <name> --confirm: removes installed plugins

All three gates pass: tsc clean, vitest zero new failures, smoke test complete.

Design decision: public keys stored as base64-encoded PEM format rather than raw 32-byte keys. Node.js crypto.verify requires PEM/DER format or KeyObject for ed25519; raw bytes alone fail. PEM is standard and portable.

Private key written to: /tmp/codeburn-signing/codeburn-signing-key.pem
2026-08-28 00:30:43 -07:00
iamtoruk
5a72bb35c0 test(plugins): pin deny-by-default rejection of unsigned plugins without CODEBURN_PLUGIN_DEV 2026-08-28 00:16:11 -07:00
iamtoruk
52bde7c4dd feat(plugins): CB-3 plugin socket — wire guard, CLI inspector, payload seam
Teams issue #3 introduces the codeburn plugin socket: an opt-in escape
hatch for third parties to add sync attributes, payload sections, and CLI
commands without touching the codeburn CLI source tree. This commit ships
the first half (CB-3) — the manifest layer, the wire guard, and the user-
facing inspector. Release-key signing (the 9b seam) lands separately.

Why split now:
  - The wire guard (filterPluginAttributes in sync/otlp.ts) is the load-
    bearing piece. With it, no plugin code can run until the user opts
    in by installing one — the default empty socket keeps every byte of
    the existing telemetry wire byte-identical. That is the contract the
    existing four sync test files were already pinning; this commit
    re-pins it explicitly with a new test (tests/plugin-socket.test.ts).
  - The inspector () makes rejections
    visible. Without it, a misconfigured plugin would just vanish
    silently — exactly the foot-gun the socket exists to prevent.

What ships:
  - src/plugins/loader.ts: enumerates ~/.config/codeburn/plugins/<name>/,
    reads + parses each codeburn-plugin.json, applies name/dir match and
    cliCompat check, hands off to verifyPlugin (the 9b seam). Rejected
    plugins contribute nothing to the wire.
  - src/plugins/manifest.ts: parses + validates the manifest shape
    (name, version, cliCompat, capabilities.{commands, syncAttributes,
    payloadSections, spanKinds}). Returns {ok, reason} on any failure.
  - src/plugins/cli.ts: . Read-only;
    never mutates the plugins directory.
  - src/sync/otlp.ts: filterPluginAttributes strips any key not in the
    declared set, called from buildOtlpPayload when pluginAttributes are
    supplied. With no plugin installed, the call path is a no-op.
  - src/sync/push.ts, src/sync/cli.ts: thread pluginAttributeKeys
    through sendBatches -> buildOtlpPayload. The wire is unchanged when
    no plugin declares anything.
  - src/usage-aggregator.ts, src/menubar-json.ts: add the
    MenubarPayload.plugins field and populate it from pluginPayloadSections
    (empty when no plugin installed).
  - src/main.ts: register the plugin subcommand.
  - tests/plugin-socket.test.ts: 12 tests covering the wire guard, the
    byte-identical default, the loader rejection paths, and the CLI.
  - scripts/smoke-plugin-socket.mjs: end-to-end smoke test against the
    built CLI (13 assertions).

Byte-identical guarantee:
  - With no plugin installed: loadPlugins() returns [], pluginPayloadSections
    returns {}, filterPluginAttributes(empty) returns []. The OTLP payload
    and the menubar payload are bit-for-bit the same as before this commit.
  - The dry-run output gains one new line listing the active plugin names
    (empty string when no plugin). User-facing only; not on the wire.

What's intentionally NOT here:
  - Plugin command invocation:  lands when at
    least one community plugin publishes a command (deferred).
  - : shipping this with no signed-plugin
    ecosystem in place would invite supply-chain risk. Deferred.
  - Release-key signing (the verifyPlugin return path). Deferred to 9b.

Refs: teams issue #3, CB-3 sync boundary spec section 9a.
2026-08-27 09:29:15 -07:00
sm0keyyy
b79cf40169
fix(omp): correct OMP period scoping, cost attribution, and Grok 4.6 prompt tiers (#1150)
Some checks failed
Tests / test (22) (push) Has been cancelled
Tests / test (22.13.0) (push) Has been cancelled
CI / semgrep (push) Has been cancelled
* fix(omp): surface nested agent activity

* fix(omp): retain reported agent costs

* refactor(omp): remove unused message field

* fix(status): refresh OMP agent payloads

* Fix Grok 4.6 prompt tier pricing

* fix(omp): retain timestamps and reprice zero costs

* Test aliased Grok tier overrides

* refactor(omp): extract tier/model helpers, pass structured subagent fields

- models.ts: pull the Grok 4.6 high-tier swap into tieredCostsFor() so
  the next tiered model extends one branch instead of copy-pasting the
  inline condition in calculateCost.
- pi.ts: extract the message-model resolution chain into
  resolveMessageModel(); document the zero-reported-cost heuristic
  (OMP writes cost.total = 0 for xai-oauth, so zero acts as absent).
- usage-aggregator.ts: stop formatting a display string into the OMP
  subagent row's name field; the payload carries agentName plus the
  structured fields (model, startedAt, token counts) and the menubar
  formats its own label. Removes the now-unused compactTokenCount.

---------

Co-authored-by: AgentSeal <resham0209@gmail.com>
2026-08-26 22:26:06 -07:00
Resham Joshi
8e61ac76af
feat(sync): additive CB-3 usage-span fields and coverage watermark (Teams boundary spec section 7) (#1135, #1140) (#1147)
Some checks failed
CI / semgrep (push) Has been cancelled
Tests / test (22) (push) Has been cancelled
Tests / test (22.13.0) (push) Has been cancelled
2026-08-25 11:32:45 -07:00
iamtoruk
bee45ad9be docs: move the CB-2 work-unit entry into the 0.9.22 section it ships in 2026-08-25 10:14:54 -07:00
Resham Joshi
d34f32ec87
feat(serve): resident serve child reuses status snapshots across requests (#1135 part 2) (#1146)
* feat(cli): cache the menubar-json base payload on the optimize path (#1135)

The status snapshot keyed on a corpus fingerprint (#999) currently
excludes the default optimize path: persisting scanAndDetect's
findings alongside the base payload would pin a moment-in-time render
of the mutable, non-fingerprinted inputs the corpus fingerprint cannot
observe (settings.json, CLAUDE.md, defined skills/agents/commands,
MCP config) under a key with no way to invalidate them. That is the
exact defect commit 113ebb1c excluded the optimize path to avoid.

This change picks option (b) from the #1135 plan: the snapshot now
covers BOTH optimize=true and optimize=false (the queryKey already
carries optimize as a boolean, so the two are naturally partitioned
without a separate cache namespace). On a hit, the base payload is
served from disk and only the optimize block is re-derived fresh
through a new computeOptimizeBlock helper (re-running scanAndDetect
over a fresh parseAllSessions of the unchanged corpus). On a save,
the optimize block is stripped from the persisted record and
replaced with an empty placeholder, so a mutation in the
non-fingerprinted inputs between two requests at the same fingerprint
is reflected in the second response even though the rest of the
payload is byte-identical to the first. The save gate from #999 is
preserved verbatim (payload.stale !== true, payload.hydration ===
undefined, isSessionHydrationComplete()), the resident codeburn serve
in-memory output memo already keys on the full argv so the two
optimize values land in different memo entries, and the desktop
app's buildOverviewArgs has always sent the menubar-json query
without --no-optimize, so the new hit rate lights up with zero app
changes. (#1135)

* fix(cli): revert optimize-path snapshot reuse, re-validate snapshots on load (#1135)

Post-build review fix round on the optimize-path snapshot change:

1. Snapshot reuse is no longer attempted for one-shot optimize polls.
   One-shot codeburn status invocations behave exactly as on main:
   fresh compute, fresh generated timestamp. The pre-existing
   --no-optimize one-shot save-gate behavior is unchanged.

2. The corpus-change test now holds --provider constant across both
   calls, so the only delta is the appended session line and the test
   proves a corpus change is picked up, not a queryKey change.

3. loadStatusSnapshot now mirrors the save gate: a persisted record
   whose payload carries stale === true or a hydration block is treated
   as a miss and recomputed. Belt-and-braces only; the save gate that
   prevents such records is unchanged.

4. The optimize block cannot be re-derived on a snapshot hit without
   the parsed corpus. scanAndDetect consumes the full ProjectSummary
   trees plus a transcript scan plus ~/.claude state, so re-deriving it
   pays the full fresh parse the snapshot exists to avoid on exactly
   the cold processes the disk snapshot serves, and the resident serve
   child gains nothing because its in-memory output memo already
   dedupes a repeated argv. Deriving from warm caches is therefore not
   honestly achievable, so this takes the plain exit: optimize-carrying
   requests are excluded from reuse (useSnapshot = !queryScope.optimize
   everywhere, the main semantics) and the computeOptimizeBlock helper,
   the save-time optimize stripping, and the optimize-path snapshot
   tests are reverted. Honesty over cleverness.

The CHANGELOG entry this branch added now describes the final
semantics. Full suite green, tsc clean.
2026-08-25 10:13:43 -07:00
Resham Joshi
4da30a5a27
feat(sessions): deterministic work-unit resolver and sessions --by-work-unit (CB-2 slice 1) (#1145)
Some checks failed
CI / semgrep (push) Waiting to run
Tests / test (22) (push) Waiting to run
Tests / test (22.13.0) (push) Waiting to run
macOS Menubar CI / test (push) Has been cancelled
Windows Menubar CI / check (ubuntu-latest) (push) Has been cancelled
Windows Menubar CI / check (windows-latest) (push) Has been cancelled
2026-08-25 09:30:05 -07:00
Resham Joshi
518e0e2c70
fix(tui): q during the cold-start fill shows a status line; second press quits immediately (#1143) (#1144)
* fix(tui): show quit feedback during the cold-start fill and exit on the second q (#1143)

Follow-up to #1142. With the input fix in place, q pressed during the
post-paint background index landed instantly but the exit path drained the
fill first - a deterministic ~16.5s silent wait on a 21k-file corpus.

The first q while the fill is active now arms a confirmation and renders
'Finishing background index so the next launch starts warm - press q or
Ctrl+C again to quit now' in the footer area, styled to match the
StatusBar (DIM border, ORANGE accent on the action key). The second q
takes the abrupt path, which #1109 already proved kill-safe (nothing
marked seen without being parsed, resume converges). Ctrl+C is unchanged:
it always exits through the abrupt path, whether or not the confirmation
is armed. q with no fill active exits immediately as before, with no
status-line flicker. The confirmation auto-clears the moment the fill
lands so a stale flag can never trap a later q.

The fill's indexing signal already flows to the dashboard (the #1109
banner reuses it), so no new global state was introduced. Pinning tests
added to tests/dashboard-exit.test.ts.

BREAKING NOTE FOR 0.9.22: ships in 0.9.22, not Unreleased.

* test: flush the unmount frame before the no-flicker assertion
2026-08-25 07:22:43 -07:00
iamtoruk
747ebae9b4 chore: drop stray builder brief from the alignment commit 2026-08-25 06:42:50 -07:00
iamtoruk
8d27241e0c chore: align app and windows versions with CLI 0.9.22 (installer manifest guard) 2026-08-25 06:42:34 -07:00