Redesign the three token-waste detectors as pure @codeburn/core detectors that
consume an ObservationEnvelope of fingerprinted resource refs (never raw paths):
- Envelope schema 0.2.0: CallObservation gains optional resourceReads /
resourceEdits (ResourceRef = {resourceId 16-hex, resourceClass}). claude/codex
toObservations fingerprint toolSequence file paths into these; raw paths never
cross the boundary. Keep observation-0.1.0.json frozen; emit 0.2.0.
- Host privacy key (D1): sync keystore under the codeburn config dir, random
32-byte key generated on first use, stable across runs, never emitted.
- core/detectors: junk-reads, duplicate-reads, context-bloat — pure, zero
fs/env, each emitting Finding[] with confidence(basis), machine-readable
evidence, algorithmVersion.
- optimize.ts delegates the three to core, mapping Finding -> WasteFinding;
display strings, fix payloads and trend stay host-derived.
Numbers parity: all optimize tests pass unchanged; frozen-corpus optimize JSON
(3552 sessions) is byte-identical PRE vs POST.
Carve the Codex decoder into @codeburn/core/providers/codex as a pure function
over supplied rollout records with EXPLICIT, JSON-serializable state
(CodexDecodeState): the running cumulative token counters, fork-replay cutoff,
mid-turn accumulators, id/turn bookkeeping, and the cross-file dedup memory
(seenKeys). Decoding a corpus in one pass equals decoding it in any number of
passes when the state threads between them (the resume invariant).
Cost leaves the decoder: the rich calls are cost-free and the CLI prices them
through the established estimated-cost seam (costBasis: 'estimated'), retiring
both Phase-0 residual calculateCost sites. The CLI codex provider is now
discovery + file streaming + cache I/O + pricing.
codex-results cache bumped to v8 (one deliberate bump): it persists the decoder
end-state blob + a byte offset + host-priced calls, so a grown rollout resumes
from the stored state and decodes only the appended bytes. Lossless — rollout
files are durable, so the one-time re-derive rebuilds identical data.
Adds a codex path to the content-smuggling guardrail and the import-smoke
subpath set. Byte-parity verified against a frozen 826-file corpus (codex-only
and all-providers, rel_tol 1e-9); perf within ±1.5% cold / ±0.5% warm.
Move the pure Claude JSONL decode out of packages/cli/src/parser.ts into
packages/core/src/providers/claude/: the large-line buffer scanner, line
parsing, compaction, per-call/turn extraction, rich-capture meta collectors,
turn grouping, and streaming dedup. These are pure over supplied records/lines
(no fs/env/clock/pricing) and carry content in-memory only at this host-facing
layer.
Add the minimizing transform toObservations(richDecode, {privacyKey,...}) that
maps the rich decode into the strict Phase-2 observation envelope: only
fingerprints, enums, numbers, timestamps, dedup keys, and canonical tool names
cross the boundary. Extend the content-smuggling guardrail with a real claude
decode -> toObservations case planting secrets in every captured free-text
field and asserting the serialized envelope surfaces none.
The CLI parser is now the adapter: it keeps all I/O, discovery, incremental
append, cache shapes, classification, and host-side pricing. parseApiCall /
parseAdvisorCalls / groupIntoTurns become thin wrappers that price the cost-free
core decode (calculateCost + local-model savings) and split raw bash commands
(strip-ansi stays out of the zod-only core), then map into ParsedApiCall /
ParsedTurn. Shared record + tool-vocab definitions move to core; the CLI
re-exports them so every existing import path is unchanged.
No parse-version bump; CachedCall/CachedTurn shapes and values byte-identical.
Core runtime deps stay zod-only.
Kimi Code tokens live ~15 min and only the CLI renews them, so the load
state sits in .terminalFailure as its dominant steady state between CLI
uses. The Plan tab and tab-strip chip flapped to a reconnect screen every
cycle even with a good last snapshot on hand.
Extract the display decision into a pure KimiQuotaPresentation helper:
terminal-with-data shows the usage bars (flagged idle) and only the
no-data case falls through to reconnect. loadedBody stamps an 'as of
<time>' caption once a snapshot is older than 10 min, in every state that
renders it. The chip's kimiQuotaSummary downgrades terminal-with-data to
.stale so the bar and popover keep the last-known rows instead of the
reconnect card. Settings' connect/disconnect pane still explains the
terminal reason.
Adds KimiQuotaPresentationTests.
- Catch errors in 'sync setup' action: AuthError/DiscoveryError (login
timeout, port exhaustion, discovery failures) now print a clean one-line
message and exit 1 instead of crashing Node with an unhandled rejection
stack trace. Also guard the double rejection when all callback ports are
in use (both 'ready' and the callback promise reject).
- Windows: open the auth URL via 'rundll32 url.dll,FileProtocolHandler'
instead of 'cmd /c start'. cmd.exe splits its command line on '&', which
truncated the OAuth query string at the first parameter, so the IdP
received a bare /authorize request ('no parameters'). rundll32 receives
the URL as a plain process argument with no shell parsing.
- Theme the OAuth callback landing pages to match the dashboard (warm
paper background, ink text, forest-green success / terracotta failure),
fully inline with no network or asset dependencies.
AI-Origin: human
Extract StatusItemContextMenuPolicy (event mask, debounce, presentation
mode) and unit-test it so rightMouseUp + statusItemMenu cannot regress
to the flash/scroll-jump paths without a failing test.
Present the context menu on rightMouseUp (not down) so the matching
mouse-up no longer dismisses it, and open via statusItem.menu +
performClick so AppKit tracks the menu under the status item instead of
manual popUp (which scrolled the Today row away on mouse move).
Clears statusItem.menu in menuDidClose so left-click still opens the
popover. Debounce + legacy rightMouseUp path retained for macOS <= 26.
Closes#802
The desktop sessions resolver returned a single per-platform path, so
Microsoft Store (MSIX) installs of Claude Desktop were invisible: their
data lives under %LOCALAPPDATA%\Packages\<Claude package>\LocalCache\Roaming\Claude\local-agent-mode-sessions
and a filesystem junction workaround breaks Cowork's own file access
(reported and verified in #611).
getDesktopSessionsDir() becomes getDesktopSessionsDirs(): an ordered,
deduped candidate list (override, then classic APPDATA, then MSIX packages
matching Claude_* or *.Claude_*, existence-checked, lexicographically
sorted; .config on Linux). Results are memoized per env-input tuple so the
parser's per-file classification never rescans Packages. All call sites
scan every candidate; macOS, Linux and classic Windows behavior unchanged.
Fixes#611
Read ~/.kimi-code credentials directly and poll api.kimi.com
/coding/v1/usages for the weekly quota and rate-limit windows,
mirroring the existing Codex/Claude quota services.
- New KimiSubscriptionService with lenient decoding (string/number
values, enum-style time units, derived used from remaining) and
429 backoff
- Quota chip on the Kimi Code tab, Plan insight, and a Kimi tab in
Settings with connect/disconnect
- Independent refresh cadence anchor so Kimi-only setups don't poll
on every payload tick
- Expired tokens surface as terminal with automatic recovery once
the CLI refreshes the credential file
- Keep insights visible for quota-capable providers on empty days
- Widen Settings so six tabs don't collapse into the overflow menu
- Resolve k3/k3-agent/k2d6-agent model aliases to canonical Kimi names
- Discover sessions across all Kimi Code homes (CLI + desktop runtime)
- Accept conv-*/ctitle-* session directory naming, not just session_*
- Add Kimi Code provider tab with brand color to the menubar
- Show short model names (Kimi K3, Kimi K2.6) in the menubar payload