Commit graph

7 commits

Author SHA1 Message Date
iamtoruk
dba9a45825 test(mac): stop the concurrent-timeout test starving the pool it waits on
The test blocked on a DispatchSemaphore with a 15s deadline, commented as
running "on a real thread, not the cooperative pool". Swift Testing invokes
synchronous test bodies from a task on the cooperative pool, so the wait
parked one of activeProcessorCount workers on the very task group it was
waiting for. With 16 cores locally there is slack; on the 3-core macos-latest
runner, alongside the rest of the parallel suite, the group made no progress
at all and the wait expired.

Await the group directly instead, which also lets the compiler reject the
blocking wait (unavailable from async contexts), and bound the test with
.timeLimit rather than a hand-rolled wall clock. Assert each child came back
with a signal status, so the test now proves the timeout killed every hung
process instead of only that the group returned.

Reproduced by parking all but 3 cooperative threads for the run: 3/3 failures
at 15.0s before, 3/3 passes after. 10x full suite under CPU load: 160/160.
2026-08-19 11:58:39 -07:00
AgentSeal
6b6b6b8df6 feat(menubar): Claude config selector, hardened (supersedes #635)
Scopes the menubar to one Claude config for multi-config (CLAUDE_CONFIG_DIRS)
setups, with All as the default. Rebased onto main and fixed the review
findings from the original #635:

- Fix a TS2206 build break (a 'type' modifier inside an import type block).
- Reject --claude-config-source with a non-Claude --provider, and scan Claude
  only in the scoped branch (a config is Claude-only): fixes provider data
  leaking into a scoped query and avoids parsing every provider's corpus.
- Scope the macOS menu-bar figure to the selected config (badge matched the
  popover), clear the selection when switching to a non-Claude provider tab,
  and stop the on-disk badge fallback from showing an unscoped number while
  scoped.
- Tag Claude Desktop / Cowork sessions as their own 'claude-desktop' source so
  they are a selectable bucket instead of silently vanishing from per-config
  views (sum of options now equals All).
- Skip the redundant Claude discovery walk for plain single-config users while
  keeping idle configs and Claude Desktop selectable.

Reviewed by Codex 5.6; all findings addressed. Full suite: 1581 TS tests, 76
Swift tests, tsc clean.
2026-07-10 01:44:51 +02:00
ZacharyHu0
a485087b5b Add LingTai TUI provider support 2026-07-09 23:47:59 +02:00
ozymandiashh
805d2ffa49
feat(menubar): add Local/Combined usage toggle (#566) (#568)
Some checks are pending
CI / semgrep (push) Waiting to run
Mirror the combined multi-device dashboard in the macOS menubar via a
Local/Combined scope toggle (Settings -> Display). Builds on the menubar-json
`--scope combined` contract.

- MenubarScope enum (.local default) persisted in UserDefaults; PayloadCacheKey
  gains a scope dimension so local and combined payloads cache separately.
- DataClient.fetch(scope:): a pure statusSubcommand builder appends
  `--scope combined` only for combined, forces `--provider all` (the CLI rejects
  combined with a provider/project/exclude filter), and coerces multi-day
  selections to local (the CLI rejects combined + --days).
- MenubarPayload decodes the optional `combined` block (per-device + totals);
  nil when absent (back-compat).
- The badge always stays on the local payload; the combined network pull lives
  on a separate cache key and falls back to local with a "Combined unavailable"
  indicator, so a slow/offline peer never blocks the badge.
- Hero shows combined totals + a per-device breakdown in Combined scope; the
  token metric stays input+output (cache excluded) to match local; the
  per-device daily-budget warning is suppressed for a multi-device total;
  selecting Combined resets the provider tab to All.

Tests run via `swift test` (CommandLineTools Testing.framework on the rpath, no
Xcode): scope persistence, argv (local/combined/provider-forcing/multi-day),
cache-key partition, badge-survives-combined-failure, combined decode, token
consistency, provider reset, budget suppression.
2026-06-28 22:01:01 +02:00
Resham Joshi
454d836f6e
fix(menubar): surface CLI stdout/stderr on decode failure (#515) (#547)
* fix(menubar): surface CLI stdout/stderr on decode failure (#515)

A failed decode of the CLI menubar-json output threw an opaque
DataClientError.decode(error) that surfaced only "not valid JSON", hiding
whether stdout was empty or carried a non-JSON prefix (e.g. a stray Node
banner on stdout, the root cause in #515). Wrap it in a CLIDecodeFailure that
carries a bounded stdout snippet, the stdout byte count, and stderr, so
String(describing:) is self-diagnosing in logs and the UI.

* test(menubar): add missing codexCredits arg so the Swift test target compiles

Two tests built CurrentBlock without the codexCredits parameter added in #510,
so the Swift test target failed to compile (CI does not run the Swift tests, so
it went unnoticed). Add codexCredits: nil to both.
2026-06-22 01:33:41 +02:00
Resham Joshi
f5d1a8513d
fix(menubar): await terminationHandler instead of blocking a queue thread; cap CLI spawns (#462)
The #426 fix moved waitUntilExit and its timeout onto the same global(qos:.utility)
queue. Under sustained load every utility worker blocked in waitUntilExit, so the
timeout could never be scheduled to kill them and the menubar wedged on Loading
forever (confirmed via sample after ~a week of soak). Await process.terminationHandler
(fires on a Foundation queue, blocks no worker) so the timeout always has a free
thread. Add an actor-based async semaphore capping concurrent CLI spawns at 6.
2026-06-09 21:27:32 +02:00
iamtoruk
4ffec37861 fix(menubar): run CLI exit-wait and timeout off the cooperative pool
The menubar wedged on "Loading Today…" for hours after an idle period.
Root cause: DataClient.runCLI called the blocking process.waitUntilExit()
from an async function on Swift's cooperative thread pool. On a 16-core
machine, 16 concurrent slow `codeburn` subprocesses pinned all 16
cooperative threads inside waitUntilExit; the 45s timeout — itself a Task
on that same pool — could then never be scheduled to kill them, so the
deadlock was permanent. Confirmed via sample: 16/16 cooperative threads
parked in waitUntilExit. PR #412 (AppStore inFlightKeys bookkeeping) was a
layer above the OS-thread deadlock and could not fix it.

Move both blocking points off the cooperative pool: bridge waitUntilExit
through a global (overcommit) queue via a continuation, and drive the
timeout from a DispatchSource on a global queue so it fires even when the
pool is saturated. Extract runProcess for testability; add a concurrency +
timeout smoke test and an output/exit-code test.
2026-06-01 02:09:05 -07:00