@phnx-labs/agents-cli 1.22.46 → 1.22.47
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +279 -0
- package/LICENSE +102 -182
- package/README.md +31 -18
- package/dist/cli/command-registry.d.ts +1 -1
- package/dist/cli/command-registry.js +4 -2
- package/dist/commands/accounts.js +56 -12
- package/dist/commands/auth.js +7 -5
- package/dist/commands/browser-sessions-picker.js +9 -7
- package/dist/commands/browser.js +266 -15
- package/dist/commands/config.js +26 -4
- package/dist/commands/cost.d.ts +1 -1
- package/dist/commands/detach.d.ts +13 -0
- package/dist/commands/detach.js +11 -8
- package/dist/commands/exec.js +55 -38
- package/dist/commands/feed-watch.d.ts +2 -0
- package/dist/commands/feed-watch.js +28 -0
- package/dist/commands/feed.js +27 -0
- package/dist/commands/feedback.js +3 -19
- package/dist/commands/insights.d.ts +1 -1
- package/dist/commands/insights.js +2 -2
- package/dist/commands/lease.js +3 -3
- package/dist/commands/routines.js +1 -1
- package/dist/commands/sessions-picker-factory.d.ts +2 -1
- package/dist/commands/sessions-picker-factory.js +1 -1
- package/dist/commands/sessions-stop.d.ts +24 -0
- package/dist/commands/sessions-stop.js +66 -0
- package/dist/commands/sessions-trace.d.ts +82 -0
- package/dist/commands/sessions-trace.js +387 -0
- package/dist/commands/sessions.js +4 -0
- package/dist/commands/ssh.d.ts +15 -0
- package/dist/commands/ssh.js +194 -16
- package/dist/commands/teams.js +34 -2
- package/dist/commands/view.d.ts +2 -0
- package/dist/commands/view.js +18 -31
- package/dist/commands/watchdog.js +9 -14
- package/dist/lib/account-capabilities.d.ts +4 -0
- package/dist/lib/account-capabilities.js +13 -9
- package/dist/lib/account-registry.d.ts +2 -0
- package/dist/lib/account-registry.js +30 -2
- package/dist/lib/accounting/rotate.d.ts +9 -0
- package/dist/lib/accounting/rotate.js +12 -2
- package/dist/lib/accounting/usage.d.ts +70 -3
- package/dist/lib/accounting/usage.js +235 -63
- package/dist/lib/agent-modes.js +29 -1
- package/dist/lib/agent-spec/agents.d.ts +4 -3
- package/dist/lib/agent-spec/agents.js +14 -6
- package/dist/lib/analytics/mix-commands.d.ts +1 -1
- package/dist/lib/analytics/mix-commands.js +2 -2
- package/dist/lib/auth-health.d.ts +13 -3
- package/dist/lib/auth-health.js +50 -8
- package/dist/lib/browser/devices.d.ts +19 -0
- package/dist/lib/browser/devices.js +23 -0
- package/dist/lib/browser/ipc.d.ts +1 -1
- package/dist/lib/browser/ipc.js +33 -7
- package/dist/lib/browser/profiles.d.ts +106 -1
- package/dist/lib/browser/profiles.js +300 -17
- package/dist/lib/browser/remote-control.d.ts +26 -2
- package/dist/lib/browser/remote-control.js +33 -3
- package/dist/lib/browser/runtime-state.d.ts +9 -0
- package/dist/lib/browser/runtime-state.js +24 -8
- package/dist/lib/browser/service.d.ts +37 -0
- package/dist/lib/browser/service.js +78 -20
- package/dist/lib/browser/sessions-list.d.ts +1 -3
- package/dist/lib/browser/sessions-list.js +3 -16
- package/dist/lib/browser/types.d.ts +12 -1
- package/dist/lib/codex-policy.d.ts +11 -1
- package/dist/lib/codex-policy.js +20 -5
- package/dist/lib/config-keys.d.ts +4 -3
- package/dist/lib/config-keys.js +17 -7
- package/dist/lib/daemon/runner.js +15 -2
- package/dist/lib/daemon-ticks.js +2 -2
- package/dist/lib/device-config.d.ts +25 -8
- package/dist/lib/device-config.js +90 -17
- package/dist/lib/devices/config-migration.d.ts +7 -0
- package/dist/lib/devices/config-migration.js +67 -3
- package/dist/lib/devices/health.d.ts +27 -2
- package/dist/lib/devices/health.js +39 -9
- package/dist/lib/devices/interactive-host.d.ts +15 -0
- package/dist/lib/devices/interactive-host.js +53 -0
- package/dist/lib/devices/registry.d.ts +61 -4
- package/dist/lib/devices/registry.js +94 -38
- package/dist/lib/devices/stats-cache.d.ts +6 -0
- package/dist/lib/devices/stats-cache.js +10 -2
- package/dist/lib/exec.d.ts +5 -3
- package/dist/lib/exec.js +32 -16
- package/dist/lib/feed/answer.d.ts +29 -0
- package/dist/lib/feed/answer.js +145 -0
- package/dist/lib/feed/attention.d.ts +83 -0
- package/dist/lib/feed/attention.js +0 -0
- package/dist/lib/feed/feed.d.ts +109 -0
- package/dist/lib/feed/feed.js +154 -3
- package/dist/lib/feed/pr-status.d.ts +14 -0
- package/dist/lib/feed/pr-status.js +47 -0
- package/dist/lib/feed/watch.d.ts +59 -0
- package/dist/lib/feed/watch.js +180 -0
- package/dist/lib/fleet/capture.js +6 -0
- package/dist/lib/fleet/remote-login.js +8 -3
- package/dist/lib/fleet/types.d.ts +17 -0
- package/dist/lib/harness/adapter.d.ts +11 -0
- package/dist/lib/harness/adapters/claude.js +38 -24
- package/dist/lib/harness/adapters/codex.js +2 -4
- package/dist/lib/hooks/cache.js +8 -0
- package/dist/lib/hooks/match.d.ts +3 -0
- package/dist/lib/hooks/match.js +25 -0
- package/dist/lib/hosts/passthrough.js +16 -0
- package/dist/lib/hosts/registry.js +7 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
- package/dist/lib/menubar/install-menubar.d.ts +13 -0
- package/dist/lib/menubar/install-menubar.js +26 -2
- package/dist/lib/open-url.d.ts +60 -2
- package/dist/lib/open-url.js +167 -12
- package/dist/lib/refresh-coordinator.d.ts +1 -1
- package/dist/lib/refresh-coordinator.js +1 -1
- package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/Info.plist +2 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/Resources/AppIcon.icns +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/_CodeSignature/CodeResources +13 -1
- package/dist/lib/session/active.d.ts +67 -0
- package/dist/lib/session/active.js +69 -1
- package/dist/lib/session/db.js +4 -2
- package/dist/lib/session/discover.d.ts +2 -0
- package/dist/lib/session/discover.js +12 -6
- package/dist/lib/session/prompt.d.ts +35 -0
- package/dist/lib/session/prompt.js +56 -0
- package/dist/lib/session/remote/watch.d.ts +1 -1
- package/dist/lib/session/remote/watch.js +6 -1
- package/dist/lib/session/render.js +7 -3
- package/dist/lib/session/session-cache.js +5 -1
- package/dist/lib/session/trajectory-compare.d.ts +47 -0
- package/dist/lib/session/trajectory-compare.js +116 -0
- package/dist/lib/session/trajectory-html.d.ts +15 -0
- package/dist/lib/session/trajectory-html.js +750 -0
- package/dist/lib/session/trajectory-lineage.d.ts +92 -0
- package/dist/lib/session/trajectory-lineage.js +195 -0
- package/dist/lib/session/trajectory-text.d.ts +37 -0
- package/dist/lib/session/trajectory-text.js +277 -0
- package/dist/lib/session/trajectory.d.ts +102 -0
- package/dist/lib/session/trajectory.js +335 -0
- package/dist/lib/startup/command-registry.d.ts +3 -1
- package/dist/lib/startup/command-registry.js +5 -2
- package/dist/lib/state.d.ts +1 -1
- package/dist/lib/state.js +4 -2
- package/dist/lib/teams/agents.js +9 -0
- package/dist/lib/terminal/inject.d.ts +19 -0
- package/dist/lib/terminal/inject.js +19 -9
- package/dist/lib/tmux/session.d.ts +46 -13
- package/dist/lib/tmux/session.js +173 -27
- package/dist/lib/types.d.ts +16 -0
- package/dist/lib/usage-backoff.d.ts +2 -1
- package/dist/lib/usage-backoff.js +60 -19
- package/dist/lib/usage-refresh.d.ts +17 -3
- package/dist/lib/usage-refresh.js +57 -11
- package/dist/lib/watchdog/log.d.ts +1 -1
- package/dist/lib/watchdog/log.js +1 -1
- package/dist/lib/watchdog/runner.d.ts +36 -45
- package/dist/lib/watchdog/runner.js +150 -119
- package/dist/lib/watchdog/service.d.ts +0 -1
- package/dist/lib/watchdog/service.js +0 -1
- package/dist/lib/watchdog/watchdog-agent.d.ts +38 -0
- package/dist/lib/watchdog/watchdog-agent.js +59 -0
- package/dist/lib/watchdog/watchdog.d.ts +11 -2
- package/dist/lib/watchdog/watchdog.js +37 -104
- package/package.json +2 -2
- package/dist/commands/usage.d.ts +0 -27
- package/dist/commands/usage.js +0 -96
|
@@ -118,6 +118,57 @@ export function usageUnreachableError(agent, cause) {
|
|
|
118
118
|
? `${agent} usage read failed: ${detail}`
|
|
119
119
|
: `${agent} usage read failed.`;
|
|
120
120
|
}
|
|
121
|
+
/**
|
|
122
|
+
* Marker for a log-based (`network: false`) provider — Codex, Grok — that has
|
|
123
|
+
* simply never recorded a rate-limit event on this machine yet: no session
|
|
124
|
+
* log exists, or no session in it carries usage data. Distinct on purpose from
|
|
125
|
+
* `usageUnreachableError`: that one means the local log COULDN'T be read (a
|
|
126
|
+
* real failure worth surfacing distinctly); this one means there is nothing to
|
|
127
|
+
* read because the account has not run here, which is expected for a fresh
|
|
128
|
+
* install and should render as a benign state, not an error (RUSH-3040).
|
|
129
|
+
*/
|
|
130
|
+
export const USAGE_NO_RECENT_USAGE_MARKER = 'no usage recorded yet';
|
|
131
|
+
export const USAGE_BENIGN_STATE = Symbol('usageBenignState');
|
|
132
|
+
/**
|
|
133
|
+
* Shared error-classification + 429 backoff for a networked usage fetch whose
|
|
134
|
+
* only signal is an HTTP status (or none at all, on a network failure) —
|
|
135
|
+
* Antigravity's :retrieveUserQuota and Muse's Meta Model API probe are both
|
|
136
|
+
* this shape. They were added to `USAGE_SOURCES` after the four original
|
|
137
|
+
* `usageXError` constructors and did not get their own scheme (usage.ts's
|
|
138
|
+
* error handling was written for "four networked providers"; RUSH-3040).
|
|
139
|
+
* Route every no-snapshot outcome for either through this one function so a
|
|
140
|
+
* future entry cannot be added second-class again — it owns noting the 429
|
|
141
|
+
* backoff, so callers must NOT also call {@link noteUsageRateLimited} for the
|
|
142
|
+
* same response.
|
|
143
|
+
*/
|
|
144
|
+
export function classifyUsageFetchFailure(agent, agentId, status, retryAfterHeader, usageScope) {
|
|
145
|
+
if (status === 429) {
|
|
146
|
+
noteUsageRateLimited(agentId, retryAfterHeader ?? null, { account: usageScope });
|
|
147
|
+
return usageRejectedError(agent, 429);
|
|
148
|
+
}
|
|
149
|
+
if (status !== null)
|
|
150
|
+
return usageRejectedError(agent, status);
|
|
151
|
+
return usageUnreachableError(agent);
|
|
152
|
+
}
|
|
153
|
+
/** Classify a `UsageInfo.error` string into its {@link UsageErrorKind}, or null when there is no error. */
|
|
154
|
+
export function classifyUsageErrorKind(error) {
|
|
155
|
+
if (!error)
|
|
156
|
+
return null;
|
|
157
|
+
if (isUsageHeadlessScopeError(error))
|
|
158
|
+
return 'headless-scope';
|
|
159
|
+
if (error.startsWith('No readable '))
|
|
160
|
+
return 'no-credential';
|
|
161
|
+
if (error.includes('credential expired'))
|
|
162
|
+
return 'expired-credential';
|
|
163
|
+
if (error.includes('rate-limited this machine') || error.includes('is rate-limiting the usage endpoint')) {
|
|
164
|
+
return 'rate-limited';
|
|
165
|
+
}
|
|
166
|
+
if (error.includes('rejected the usage read'))
|
|
167
|
+
return 'rejected';
|
|
168
|
+
if (error.includes('usage read failed'))
|
|
169
|
+
return 'unreachable';
|
|
170
|
+
return 'rejected';
|
|
171
|
+
}
|
|
121
172
|
/**
|
|
122
173
|
* True when a Claude OAuth access token is within the refresh leeway of expiry
|
|
123
174
|
* (or already expired) — i.e. it "would need a refresh" before the next use.
|
|
@@ -165,6 +216,14 @@ const COMPACT_BAR_LEN = 5;
|
|
|
165
216
|
const USAGE_BAR_LEN = 10;
|
|
166
217
|
const FULL = '\u2588';
|
|
167
218
|
const EMPTY = '\u2591';
|
|
219
|
+
/** Construct the benign no-local-log result without overloading `error`. */
|
|
220
|
+
export function usageNoRecentUsageInfo() {
|
|
221
|
+
return { snapshot: null, error: null, [USAGE_BENIGN_STATE]: 'no-recent-usage' };
|
|
222
|
+
}
|
|
223
|
+
/** Read a benign state for the human renderer; symbols are omitted by JSON serialization. */
|
|
224
|
+
export function getUsageBenignState(info) {
|
|
225
|
+
return info[USAGE_BENIGN_STATE] ?? null;
|
|
226
|
+
}
|
|
168
227
|
/** The single registry of agent usage sources and their transport. */
|
|
169
228
|
const USAGE_SOURCES = {
|
|
170
229
|
claude: { fetch: getClaudeUsageInfo, network: true },
|
|
@@ -333,6 +392,9 @@ async function fetchLiveUsageDeduped(input, usageKey, cached, fileOnly) {
|
|
|
333
392
|
home: input.home,
|
|
334
393
|
cliVersion: input.cliVersion,
|
|
335
394
|
organizationId: input.info.organizationId,
|
|
395
|
+
// Scope this fetch's 429 backoff to the account being fetched, so one
|
|
396
|
+
// throttled account cannot park the whole provider (RUSH-3036).
|
|
397
|
+
usageScope: usageKey,
|
|
336
398
|
fileOnly,
|
|
337
399
|
});
|
|
338
400
|
if (usage.snapshot) {
|
|
@@ -391,6 +453,26 @@ export function pickCompactUsageWindows(windows, maxWindows) {
|
|
|
391
453
|
}
|
|
392
454
|
return chosen;
|
|
393
455
|
}
|
|
456
|
+
/** Human label for a classified usage error, for the no-bars branch of {@link formatUsageSummary}. */
|
|
457
|
+
function formatUsageErrorKindLabel(kind, detail) {
|
|
458
|
+
switch (kind) {
|
|
459
|
+
case 'no-credential':
|
|
460
|
+
return 'sign in / provision token';
|
|
461
|
+
case 'expired-credential':
|
|
462
|
+
return 're-auth for usage';
|
|
463
|
+
case 'rate-limited': {
|
|
464
|
+
const retryHint = detail?.match(/not retrying for (.+)\.$/)?.[1] ?? null;
|
|
465
|
+
return retryHint ? `rate-limited (retry ~${retryHint})` : 'rate-limited';
|
|
466
|
+
}
|
|
467
|
+
case 'rejected':
|
|
468
|
+
case 'unreachable':
|
|
469
|
+
case 'headless-scope':
|
|
470
|
+
case null:
|
|
471
|
+
case undefined:
|
|
472
|
+
default:
|
|
473
|
+
return 'usage unavailable';
|
|
474
|
+
}
|
|
475
|
+
}
|
|
394
476
|
/** Format a one-line usage summary with compact bars for inline display. */
|
|
395
477
|
export function formatUsageSummary(plan, snapshot, planWidth = 3, opts) {
|
|
396
478
|
const parts = [];
|
|
@@ -445,11 +527,16 @@ export function formatUsageSummary(plan, snapshot, planWidth = 3, opts) {
|
|
|
445
527
|
// mistaken for a missing setup-token or seeding failure (RUSH-2392).
|
|
446
528
|
parts.push(chalk.dim(USAGE_HEADLESS_SCOPE_MARKER));
|
|
447
529
|
}
|
|
530
|
+
else if (opts?.benignState === 'no-recent-usage') {
|
|
531
|
+
parts.push(chalk.dim(USAGE_NO_RECENT_USAGE_MARKER));
|
|
532
|
+
}
|
|
448
533
|
else if (opts?.unavailable) {
|
|
449
534
|
// Signed-in account we could NOT fetch usage for (no live token in a reachable
|
|
450
535
|
// home / org mismatch / fetch error). Say so explicitly instead of drawing a
|
|
451
|
-
// blank gauge that reads like "0% used"
|
|
452
|
-
|
|
536
|
+
// blank gauge that reads like "0% used" — and name the SPECIFIC cause when
|
|
537
|
+
// the caller passed one, rather than the generic bucket that used to cover
|
|
538
|
+
// ~6 different failures (RUSH-3040).
|
|
539
|
+
parts.push(chalk.dim(formatUsageErrorKindLabel(opts.errorKind, opts.errorDetail)));
|
|
453
540
|
}
|
|
454
541
|
return parts.join(' ');
|
|
455
542
|
}
|
|
@@ -598,11 +685,17 @@ async function getCodexUsageInfo(options) {
|
|
|
598
685
|
return { snapshot: null, error: null };
|
|
599
686
|
}
|
|
600
687
|
const files = collectCodexSessionFiles(options?.home, sinceMs);
|
|
688
|
+
const now = new Date();
|
|
601
689
|
for (const filePath of files) {
|
|
602
690
|
const match = await readLatestCodexRateLimits(filePath);
|
|
603
691
|
if (!match)
|
|
604
692
|
continue;
|
|
605
|
-
|
|
693
|
+
// Same freshness filter Grok already applies (RUSH-3040): a window whose
|
|
694
|
+
// reset time or windowMinutes-derived expiry has passed is a STALE read,
|
|
695
|
+
// not a current one — rendering it as-is is how a codex bar kept showing
|
|
696
|
+
// "100% used" past its own reset. Try the next-older session file rather
|
|
697
|
+
// than surfacing a stale bar.
|
|
698
|
+
const windows = normalizeCodexWindows(match.rateLimits).filter((window) => isCachedUsageWindowFresh(window, match.capturedAt, now));
|
|
606
699
|
if (windows.length === 0)
|
|
607
700
|
continue;
|
|
608
701
|
return {
|
|
@@ -615,10 +708,14 @@ async function getCodexUsageInfo(options) {
|
|
|
615
708
|
error: null,
|
|
616
709
|
};
|
|
617
710
|
}
|
|
618
|
-
|
|
711
|
+
// No session ever recorded a rate-limit event on this machine (or none of
|
|
712
|
+
// the ones found were still fresh) — a benign "nothing to show yet", not a
|
|
713
|
+
// failure (RUSH-3040). Distinct from the outer catch below, which is a
|
|
714
|
+
// genuine read/parse failure.
|
|
715
|
+
return usageNoRecentUsageInfo();
|
|
619
716
|
}
|
|
620
|
-
catch {
|
|
621
|
-
return { snapshot: null, error:
|
|
717
|
+
catch (err) {
|
|
718
|
+
return { snapshot: null, error: usageUnreachableError('Codex', err) };
|
|
622
719
|
}
|
|
623
720
|
}
|
|
624
721
|
/**
|
|
@@ -671,7 +768,7 @@ async function getClaudeUsageInfo(options) {
|
|
|
671
768
|
}
|
|
672
769
|
// Honour a live Retry-After rather than re-arming the penalty (see
|
|
673
770
|
// usage-backoff.ts). No request at all while the window is open.
|
|
674
|
-
const throttledUntil = usageRateLimitedUntil('claude');
|
|
771
|
+
const throttledUntil = usageRateLimitedUntil('claude', Date.now(), options?.usageScope);
|
|
675
772
|
if (throttledUntil) {
|
|
676
773
|
return { snapshot: null, error: usageThrottledError('Claude', throttledUntil) };
|
|
677
774
|
}
|
|
@@ -687,7 +784,7 @@ async function getClaudeUsageInfo(options) {
|
|
|
687
784
|
});
|
|
688
785
|
if (!response.ok) {
|
|
689
786
|
if (response.status === 429) {
|
|
690
|
-
noteUsageRateLimited('claude', response.headers.get('retry-after'));
|
|
787
|
+
noteUsageRateLimited('claude', response.headers.get('retry-after'), { account: options?.usageScope });
|
|
691
788
|
}
|
|
692
789
|
// Setup-token is user:inference only; usage needs user:profile → 403
|
|
693
790
|
// with a scope-requirement body. Distinct from a real rejection so the
|
|
@@ -780,7 +877,7 @@ async function getKimiUsageInfo(options) {
|
|
|
780
877
|
}
|
|
781
878
|
// Honour a live Retry-After rather than re-arming the penalty (see
|
|
782
879
|
// usage-backoff.ts). No request at all while the window is open.
|
|
783
|
-
const throttledUntil = usageRateLimitedUntil('kimi');
|
|
880
|
+
const throttledUntil = usageRateLimitedUntil('kimi', Date.now(), options?.usageScope);
|
|
784
881
|
if (throttledUntil) {
|
|
785
882
|
return { snapshot: null, error: usageThrottledError('Kimi', throttledUntil) };
|
|
786
883
|
}
|
|
@@ -796,7 +893,7 @@ async function getKimiUsageInfo(options) {
|
|
|
796
893
|
// way there are no bars to draw, and the status is what tells them apart.
|
|
797
894
|
if (!response.ok) {
|
|
798
895
|
if (response.status === 429) {
|
|
799
|
-
noteUsageRateLimited('kimi', response.headers.get('retry-after'));
|
|
896
|
+
noteUsageRateLimited('kimi', response.headers.get('retry-after'), { account: options?.usageScope });
|
|
800
897
|
}
|
|
801
898
|
return { snapshot: null, error: usageRejectedError('Kimi', response.status) };
|
|
802
899
|
}
|
|
@@ -914,7 +1011,7 @@ async function getDroidUsageInfo(options) {
|
|
|
914
1011
|
}
|
|
915
1012
|
// Honour a live Retry-After rather than re-arming the penalty (see
|
|
916
1013
|
// usage-backoff.ts). No request at all while the window is open.
|
|
917
|
-
const throttledUntil = usageRateLimitedUntil('droid');
|
|
1014
|
+
const throttledUntil = usageRateLimitedUntil('droid', Date.now(), options?.usageScope);
|
|
918
1015
|
if (throttledUntil) {
|
|
919
1016
|
return { snapshot: null, error: usageThrottledError('Droid', throttledUntil) };
|
|
920
1017
|
}
|
|
@@ -929,7 +1026,7 @@ async function getDroidUsageInfo(options) {
|
|
|
929
1026
|
// 401 => revoked/expired token. No bars to draw, and the status says why.
|
|
930
1027
|
if (!response.ok) {
|
|
931
1028
|
if (response.status === 429) {
|
|
932
|
-
noteUsageRateLimited('droid', response.headers.get('retry-after'));
|
|
1029
|
+
noteUsageRateLimited('droid', response.headers.get('retry-after'), { account: options?.usageScope });
|
|
933
1030
|
}
|
|
934
1031
|
return { snapshot: null, error: usageRejectedError('Droid', response.status) };
|
|
935
1032
|
}
|
|
@@ -956,7 +1053,7 @@ async function getDroidUsageInfo(options) {
|
|
|
956
1053
|
}
|
|
957
1054
|
}
|
|
958
1055
|
/** Probe Claude's OAuth token against the usage endpoint. Never refreshes — reports `expired` for a near-expiry token; see the comment below (RUSH-1822). */
|
|
959
|
-
export async function probeClaudeStatus(home, cliVersion) {
|
|
1056
|
+
export async function probeClaudeStatus(home, cliVersion, usageScope) {
|
|
960
1057
|
// accessTokenCache: the daemon warms this probe every ~3 min per account, so it
|
|
961
1058
|
// reads ONLY the file-based setup-token and never the interactive login —
|
|
962
1059
|
// transmitting that ACL-bound token to the usage API from a background loop is
|
|
@@ -981,7 +1078,7 @@ export async function probeClaudeStatus(home, cliVersion) {
|
|
|
981
1078
|
// window is open, report the throttle from the recorded state instead of
|
|
982
1079
|
// firing again and re-arming it (usage-backoff.ts). This 3-min-cadence
|
|
983
1080
|
// probe is what created the loop it now respects.
|
|
984
|
-
if (usageRateLimitedUntil('claude'))
|
|
1081
|
+
if (usageRateLimitedUntil('claude', Date.now(), usageScope))
|
|
985
1082
|
return { status: 429, token: 'present' };
|
|
986
1083
|
try {
|
|
987
1084
|
const response = await fetch(CLAUDE_USAGE_URL, {
|
|
@@ -995,7 +1092,7 @@ export async function probeClaudeStatus(home, cliVersion) {
|
|
|
995
1092
|
signal: AbortSignal.timeout(8000),
|
|
996
1093
|
});
|
|
997
1094
|
if (response.status === 429) {
|
|
998
|
-
noteUsageRateLimited('claude', response.headers.get('retry-after'));
|
|
1095
|
+
noteUsageRateLimited('claude', response.headers.get('retry-after'), { account: usageScope });
|
|
999
1096
|
}
|
|
1000
1097
|
// Setup-token is user:inference only; usage needs user:profile → 403.
|
|
1001
1098
|
// That is NOT a revocation — the account still runs (RUSH-2392).
|
|
@@ -1023,7 +1120,7 @@ export async function probeClaudeStatus(home, cliVersion) {
|
|
|
1023
1120
|
}
|
|
1024
1121
|
}
|
|
1025
1122
|
/** Probe Kimi's OAuth token against the /usages endpoint. Never refreshes (single-use rotation — see getKimiUsageInfo). */
|
|
1026
|
-
export async function probeKimiStatus(home) {
|
|
1123
|
+
export async function probeKimiStatus(home, usageScope) {
|
|
1027
1124
|
const credPath = resolveKimiCredentialPath(home);
|
|
1028
1125
|
if (!credPath)
|
|
1029
1126
|
return { status: null, token: 'missing' };
|
|
@@ -1047,7 +1144,7 @@ export async function probeKimiStatus(home) {
|
|
|
1047
1144
|
// is what created the loop it now respects. It sits AFTER the local
|
|
1048
1145
|
// missing/expired checks — as in probeClaudeStatus and probeDroidStatus — so
|
|
1049
1146
|
// a genuinely broken credential is never misreported as merely throttled.
|
|
1050
|
-
if (usageRateLimitedUntil('kimi'))
|
|
1147
|
+
if (usageRateLimitedUntil('kimi', Date.now(), usageScope))
|
|
1051
1148
|
return { status: 429, token: 'present' };
|
|
1052
1149
|
try {
|
|
1053
1150
|
const response = await fetch(KIMI_USAGES_URL, {
|
|
@@ -1056,7 +1153,7 @@ export async function probeKimiStatus(home) {
|
|
|
1056
1153
|
signal: AbortSignal.timeout(8000),
|
|
1057
1154
|
});
|
|
1058
1155
|
if (response.status === 429) {
|
|
1059
|
-
noteUsageRateLimited('kimi', response.headers.get('retry-after'));
|
|
1156
|
+
noteUsageRateLimited('kimi', response.headers.get('retry-after'), { account: usageScope });
|
|
1060
1157
|
}
|
|
1061
1158
|
return { status: response.status, token: 'present' };
|
|
1062
1159
|
}
|
|
@@ -1065,7 +1162,7 @@ export async function probeKimiStatus(home) {
|
|
|
1065
1162
|
}
|
|
1066
1163
|
}
|
|
1067
1164
|
/** Probe Droid's WorkOS token against the billing-limits endpoint. Never refreshes (single-use rotation — see getDroidUsageInfo). */
|
|
1068
|
-
export async function probeDroidStatus(home) {
|
|
1165
|
+
export async function probeDroidStatus(home, usageScope) {
|
|
1069
1166
|
const cred = decryptDroidAuthPayload(home || os.homedir());
|
|
1070
1167
|
const accessToken = cred?.access_token;
|
|
1071
1168
|
if (typeof accessToken !== 'string' || !accessToken)
|
|
@@ -1077,7 +1174,7 @@ export async function probeDroidStatus(home) {
|
|
|
1077
1174
|
// window is open, report the throttle from the recorded state instead of
|
|
1078
1175
|
// firing again and re-arming it (usage-backoff.ts). This 3-min-cadence
|
|
1079
1176
|
// probe is what created the loop it now respects.
|
|
1080
|
-
if (usageRateLimitedUntil('droid'))
|
|
1177
|
+
if (usageRateLimitedUntil('droid', Date.now(), usageScope))
|
|
1081
1178
|
return { status: 429, token: 'present' };
|
|
1082
1179
|
try {
|
|
1083
1180
|
const response = await fetch(DROID_USAGE_URL, {
|
|
@@ -1086,7 +1183,7 @@ export async function probeDroidStatus(home) {
|
|
|
1086
1183
|
signal: AbortSignal.timeout(8000),
|
|
1087
1184
|
});
|
|
1088
1185
|
if (response.status === 429) {
|
|
1089
|
-
noteUsageRateLimited('droid', response.headers.get('retry-after'));
|
|
1186
|
+
noteUsageRateLimited('droid', response.headers.get('retry-after'), { account: usageScope });
|
|
1090
1187
|
}
|
|
1091
1188
|
return { status: response.status, token: 'present' };
|
|
1092
1189
|
}
|
|
@@ -1561,6 +1658,10 @@ function serializeClaudeUsageSnapshot(snapshot) {
|
|
|
1561
1658
|
* candidate (RUSH-2858). Dropping mirrors the Grok collector, and an all-expired
|
|
1562
1659
|
* snapshot deserializes to null so `readClaudeUsageCache` deletes the entry and
|
|
1563
1660
|
* callers surface "usage unavailable" plus the recorded throttle reason.
|
|
1661
|
+
*
|
|
1662
|
+
* A row that carries a plan survives even with no fresh windows: the plan is a
|
|
1663
|
+
* truthful reading in its own right, and losing it is what made the cached view
|
|
1664
|
+
* contradict the refreshed one for meterless harnesses. See the guard below.
|
|
1564
1665
|
*/
|
|
1565
1666
|
function deserializeClaudeUsageSnapshot(snapshot, now) {
|
|
1566
1667
|
const capturedAt = parseDateValue(snapshot.capturedAt);
|
|
@@ -1575,7 +1676,17 @@ function deserializeClaudeUsageSnapshot(snapshot, now) {
|
|
|
1575
1676
|
}))
|
|
1576
1677
|
.filter((window) => isCachedUsageWindowFresh(window, capturedAt, now));
|
|
1577
1678
|
const unavailable = deserializeUnavailable(snapshot.unavailable, now);
|
|
1578
|
-
|
|
1679
|
+
// A windowless row is not automatically worthless. Grok's collector reports
|
|
1680
|
+
// the subscription tier and no meters at all, so treating "no fresh windows"
|
|
1681
|
+
// as "nothing cached" deleted the only truthful thing we knew about the
|
|
1682
|
+
// account: `--refresh` wrote {plan: 'SuperGrok Heavy', windows: []}, the very
|
|
1683
|
+
// next plain `agents view` deserialized it to null, `readClaudeUsageCache`
|
|
1684
|
+
// pruned the row, and the row rendered "usage unavailable" one read after a
|
|
1685
|
+
// successful refresh. Keep a plan-bearing row — it renders as the plan alone,
|
|
1686
|
+
// and `deriveUsageStatusFromSnapshot` still returns null for zero windows, so
|
|
1687
|
+
// it can never read as a 0% bar or an "available" badge (the RUSH-2858
|
|
1688
|
+
// property that made expired windows drop in the first place).
|
|
1689
|
+
if (windows.length === 0 && !unavailable && !snapshot.plan) {
|
|
1579
1690
|
return null;
|
|
1580
1691
|
}
|
|
1581
1692
|
return {
|
|
@@ -1955,11 +2066,12 @@ async function getGrokUsageInfo(options) {
|
|
|
1955
2066
|
try {
|
|
1956
2067
|
const base = options?.home || os.homedir();
|
|
1957
2068
|
const logPath = path.join(base, '.grok', 'logs', 'unified.jsonl');
|
|
2069
|
+
// No log yet: a benign "nothing recorded here", not a failure (RUSH-3040).
|
|
1958
2070
|
if (!fs.existsSync(logPath))
|
|
1959
|
-
return
|
|
2071
|
+
return usageNoRecentUsageInfo();
|
|
1960
2072
|
const match = await readLatestGrokBilling(logPath);
|
|
1961
2073
|
if (!match)
|
|
1962
|
-
return
|
|
2074
|
+
return usageNoRecentUsageInfo();
|
|
1963
2075
|
// Grok has no live usage API (`network: false`) — bars are last-seen from
|
|
1964
2076
|
// this machine's unified.jsonl only. Drop windows whose billing period has
|
|
1965
2077
|
// already ended so a stale 100% does not paint "rate-limited" after reset,
|
|
@@ -1968,6 +2080,10 @@ async function getGrokUsageInfo(options) {
|
|
|
1968
2080
|
// (see readLatestGrokBilling).
|
|
1969
2081
|
const now = new Date();
|
|
1970
2082
|
const windows = match.windows.filter((window) => isCachedUsageWindowFresh(window, match.capturedAt, now));
|
|
2083
|
+
// NOTE: an empty `windows` here (all windows expired) still returns a real
|
|
2084
|
+
// snapshot, not the no-recent-usage marker — the plan/tier is still valid
|
|
2085
|
+
// and callers (e.g. `formatUsageSummary`) render "no bars" correctly from
|
|
2086
|
+
// an empty array. The marker above is only for "nothing was ever recorded".
|
|
1971
2087
|
return {
|
|
1972
2088
|
snapshot: {
|
|
1973
2089
|
source: 'last_seen',
|
|
@@ -1979,8 +2095,8 @@ async function getGrokUsageInfo(options) {
|
|
|
1979
2095
|
error: null,
|
|
1980
2096
|
};
|
|
1981
2097
|
}
|
|
1982
|
-
catch {
|
|
1983
|
-
return { snapshot: null, error:
|
|
2098
|
+
catch (err) {
|
|
2099
|
+
return { snapshot: null, error: usageUnreachableError('Grok', err) };
|
|
1984
2100
|
}
|
|
1985
2101
|
}
|
|
1986
2102
|
/**
|
|
@@ -1994,16 +2110,40 @@ async function getGrokUsageInfo(options) {
|
|
|
1994
2110
|
async function getMuseUsageInfo(options) {
|
|
1995
2111
|
try {
|
|
1996
2112
|
const base = options?.home || os.homedir();
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2113
|
+
// Honour a live Retry-After rather than re-arming the penalty (see
|
|
2114
|
+
// usage-backoff.ts). The local log fallback still works while the live
|
|
2115
|
+
// probe is throttled — only report the throttle when there is truly
|
|
2116
|
+
// nothing else to show.
|
|
2117
|
+
const throttledUntil = usageRateLimitedUntil('muse', Date.now(), options?.usageScope);
|
|
2118
|
+
if (throttledUntil) {
|
|
2119
|
+
const local = await readMuseLocalSessionUsage(base);
|
|
2120
|
+
if (local)
|
|
2121
|
+
return { snapshot: local, error: null };
|
|
2122
|
+
return { snapshot: null, error: usageThrottledError('Muse', throttledUntil) };
|
|
2123
|
+
}
|
|
2124
|
+
const probe = await probeMuseRateLimits(base);
|
|
2125
|
+
if (probe.snapshot)
|
|
2126
|
+
return { snapshot: probe.snapshot, error: null };
|
|
2000
2127
|
const local = await readMuseLocalSessionUsage(base);
|
|
2001
|
-
if (
|
|
2002
|
-
return { snapshot:
|
|
2003
|
-
|
|
2128
|
+
if (local)
|
|
2129
|
+
return { snapshot: local, error: null };
|
|
2130
|
+
// No live snapshot and no local log — every source came up empty. Only
|
|
2131
|
+
// now does the probe's own outcome become the reported error, mirroring
|
|
2132
|
+
// Cursor's "surface the last resort's failure" pattern above.
|
|
2133
|
+
if (!probe.hasKey)
|
|
2134
|
+
return { snapshot: null, error: usageNoCredentialError('Muse') };
|
|
2135
|
+
if (probe.noHeaders)
|
|
2136
|
+
return usageNoRecentUsageInfo();
|
|
2137
|
+
return {
|
|
2138
|
+
snapshot: null,
|
|
2139
|
+
error: classifyUsageFetchFailure('Muse', 'muse', probe.status, probe.retryAfter, options?.usageScope),
|
|
2140
|
+
};
|
|
2004
2141
|
}
|
|
2005
|
-
catch {
|
|
2006
|
-
|
|
2142
|
+
catch (err) {
|
|
2143
|
+
// A thrown request (timeout, DNS, TLS, a malformed payload) is a failed
|
|
2144
|
+
// read like any other — staying silent here would hand the caller a stale
|
|
2145
|
+
// snapshot to render as confirmed (RUSH-3040).
|
|
2146
|
+
return { snapshot: null, error: usageUnreachableError('Muse', err) };
|
|
2007
2147
|
}
|
|
2008
2148
|
}
|
|
2009
2149
|
/** Resolve a Muse API key from env or auth.json without logging the value. */
|
|
@@ -2037,25 +2177,28 @@ function resolveMuseApiKey(base) {
|
|
|
2037
2177
|
}
|
|
2038
2178
|
/**
|
|
2039
2179
|
* Probe Meta Model API for rate-limit headers. Uses GET /v1/models (no token
|
|
2040
|
-
* spend).
|
|
2180
|
+
* spend). The 429 backoff is noted by the CALLER via
|
|
2181
|
+
* {@link classifyUsageFetchFailure}, not here, so a throttled read is recorded
|
|
2182
|
+
* exactly once regardless of which branch of `getMuseUsageInfo` observes it.
|
|
2041
2183
|
*/
|
|
2042
2184
|
async function probeMuseRateLimits(base) {
|
|
2043
|
-
if (usageRateLimitedUntil('muse'))
|
|
2044
|
-
return null;
|
|
2045
2185
|
const key = resolveMuseApiKey(base);
|
|
2046
2186
|
if (!key)
|
|
2047
|
-
return null;
|
|
2187
|
+
return { snapshot: null, hasKey: false, status: null, retryAfter: null, noHeaders: false };
|
|
2048
2188
|
try {
|
|
2049
2189
|
const response = await fetch('https://api.meta.ai/v1/models', {
|
|
2050
2190
|
headers: { Authorization: `Bearer ${key}` },
|
|
2051
2191
|
signal: AbortSignal.timeout(8_000),
|
|
2052
2192
|
});
|
|
2053
|
-
if (response.
|
|
2054
|
-
|
|
2055
|
-
|
|
2193
|
+
if (!response.ok) {
|
|
2194
|
+
return {
|
|
2195
|
+
snapshot: null,
|
|
2196
|
+
hasKey: true,
|
|
2197
|
+
status: response.status,
|
|
2198
|
+
retryAfter: response.headers.get('retry-after'),
|
|
2199
|
+
noHeaders: false,
|
|
2200
|
+
};
|
|
2056
2201
|
}
|
|
2057
|
-
if (!response.ok)
|
|
2058
|
-
return null;
|
|
2059
2202
|
const limitTokens = headerNumber(response.headers, 'x-ratelimit-limit-tokens');
|
|
2060
2203
|
const remainingTokens = headerNumber(response.headers, 'x-ratelimit-remaining-tokens');
|
|
2061
2204
|
const limitRequests = headerNumber(response.headers, 'x-ratelimit-limit-requests');
|
|
@@ -2083,18 +2226,25 @@ async function probeMuseRateLimits(base) {
|
|
|
2083
2226
|
windowMinutes: 1,
|
|
2084
2227
|
});
|
|
2085
2228
|
}
|
|
2086
|
-
if (windows.length === 0)
|
|
2087
|
-
return null;
|
|
2229
|
+
if (windows.length === 0) {
|
|
2230
|
+
return { snapshot: null, hasKey: true, status: response.status, retryAfter: null, noHeaders: true };
|
|
2231
|
+
}
|
|
2088
2232
|
return {
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2233
|
+
snapshot: {
|
|
2234
|
+
source: 'live',
|
|
2235
|
+
sourceLabel: 'Meta Model API rate limits',
|
|
2236
|
+
capturedAt: new Date(),
|
|
2237
|
+
windows,
|
|
2238
|
+
plan: 'Meta Model API',
|
|
2239
|
+
},
|
|
2240
|
+
hasKey: true,
|
|
2241
|
+
status: response.status,
|
|
2242
|
+
retryAfter: null,
|
|
2243
|
+
noHeaders: false,
|
|
2094
2244
|
};
|
|
2095
2245
|
}
|
|
2096
2246
|
catch {
|
|
2097
|
-
return null;
|
|
2247
|
+
return { snapshot: null, hasKey: true, status: null, retryAfter: null, noHeaders: false };
|
|
2098
2248
|
}
|
|
2099
2249
|
}
|
|
2100
2250
|
function headerNumber(headers, name) {
|
|
@@ -2660,10 +2810,14 @@ async function refreshAntigravityAccessToken(refreshToken) {
|
|
|
2660
2810
|
}
|
|
2661
2811
|
/**
|
|
2662
2812
|
* POST :retrieveUserQuota against the Code Assist endpoints in order, returning
|
|
2663
|
-
* the first successful bucket list. null when every endpoint rejects
|
|
2664
|
-
* token, no quota API for the account) or the network fails
|
|
2813
|
+
* the first successful bucket list. `buckets: null` when every endpoint rejects
|
|
2814
|
+
* (expired token, no quota API for the account) or the network fails — `status`
|
|
2815
|
+
* carries the LAST rejection's HTTP status (or null when every attempt threw)
|
|
2816
|
+
* so the caller can classify the failure instead of it reading as silence.
|
|
2665
2817
|
*/
|
|
2666
2818
|
async function fetchAntigravityQuota(accessToken) {
|
|
2819
|
+
let lastStatus = null;
|
|
2820
|
+
let lastRetryAfter = null;
|
|
2667
2821
|
for (const url of ANTIGRAVITY_QUOTA_URLS) {
|
|
2668
2822
|
try {
|
|
2669
2823
|
const response = await fetch(url, {
|
|
@@ -2676,16 +2830,19 @@ async function fetchAntigravityQuota(accessToken) {
|
|
|
2676
2830
|
body: '{}',
|
|
2677
2831
|
signal: AbortSignal.timeout(5000),
|
|
2678
2832
|
});
|
|
2679
|
-
if (!response.ok)
|
|
2833
|
+
if (!response.ok) {
|
|
2834
|
+
lastStatus = response.status;
|
|
2835
|
+
lastRetryAfter = response.headers.get('retry-after');
|
|
2680
2836
|
continue;
|
|
2837
|
+
}
|
|
2681
2838
|
const data = (await response.json());
|
|
2682
|
-
return Array.isArray(data?.buckets) ? data.buckets : [];
|
|
2839
|
+
return { buckets: Array.isArray(data?.buckets) ? data.buckets : [], status: response.status, retryAfter: null };
|
|
2683
2840
|
}
|
|
2684
2841
|
catch {
|
|
2685
2842
|
continue;
|
|
2686
2843
|
}
|
|
2687
2844
|
}
|
|
2688
|
-
return null;
|
|
2845
|
+
return { buckets: null, status: lastStatus, retryAfter: lastRetryAfter };
|
|
2689
2846
|
}
|
|
2690
2847
|
/** Compact model tag for the inline bar — 'gemini-2.5-flash-lite' => '2.5FL'. */
|
|
2691
2848
|
export function antigravityModelShortLabel(modelId) {
|
|
@@ -2747,16 +2904,28 @@ async function getAntigravityUsageInfo(options) {
|
|
|
2747
2904
|
try {
|
|
2748
2905
|
const token = await loadAntigravityOauth(options?.home);
|
|
2749
2906
|
if (!token)
|
|
2750
|
-
return { snapshot: null, error:
|
|
2907
|
+
return { snapshot: null, error: usageNoCredentialError('Antigravity') };
|
|
2751
2908
|
let accessToken = normalizeString(token.access_token);
|
|
2752
2909
|
if ((!accessToken || antigravityTokenNeedsRefresh(token.expiry)) && token.refresh_token) {
|
|
2753
2910
|
accessToken = await refreshAntigravityAccessToken(token.refresh_token);
|
|
2754
2911
|
}
|
|
2755
2912
|
if (!accessToken)
|
|
2756
|
-
return { snapshot: null, error:
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2913
|
+
return { snapshot: null, error: usageExpiredCredentialError('Antigravity') };
|
|
2914
|
+
// Honour a live Retry-After rather than re-arming the penalty (see
|
|
2915
|
+
// usage-backoff.ts). No request at all while the window is open. Antigravity
|
|
2916
|
+
// previously had NO rate-limit backoff at all (RUSH-3040) — every refresh
|
|
2917
|
+
// re-hit a throttled endpoint.
|
|
2918
|
+
const throttledUntil = usageRateLimitedUntil('antigravity', Date.now(), options?.usageScope);
|
|
2919
|
+
if (throttledUntil) {
|
|
2920
|
+
return { snapshot: null, error: usageThrottledError('Antigravity', throttledUntil) };
|
|
2921
|
+
}
|
|
2922
|
+
const { buckets, status, retryAfter } = await fetchAntigravityQuota(accessToken);
|
|
2923
|
+
if (!buckets) {
|
|
2924
|
+
return {
|
|
2925
|
+
snapshot: null,
|
|
2926
|
+
error: classifyUsageFetchFailure('Antigravity', 'antigravity', status, retryAfter, options?.usageScope),
|
|
2927
|
+
};
|
|
2928
|
+
}
|
|
2760
2929
|
const windows = normalizeAntigravityWindows(buckets);
|
|
2761
2930
|
if (windows.length === 0)
|
|
2762
2931
|
return { snapshot: null, error: null };
|
|
@@ -2770,7 +2939,10 @@ async function getAntigravityUsageInfo(options) {
|
|
|
2770
2939
|
error: null,
|
|
2771
2940
|
};
|
|
2772
2941
|
}
|
|
2773
|
-
catch {
|
|
2774
|
-
|
|
2942
|
+
catch (err) {
|
|
2943
|
+
// A thrown request (timeout, DNS, TLS, a malformed payload) is a failed
|
|
2944
|
+
// read like any other — staying silent here would hand the caller a stale
|
|
2945
|
+
// snapshot to render as confirmed (RUSH-3040).
|
|
2946
|
+
return { snapshot: null, error: usageUnreachableError('Antigravity', err) };
|
|
2775
2947
|
}
|
|
2776
2948
|
}
|
package/dist/lib/agent-modes.js
CHANGED
|
@@ -14,9 +14,28 @@ import { resolveRunDefaults } from './run-defaults.js';
|
|
|
14
14
|
export const MODE_DESCRIPTIONS = {
|
|
15
15
|
plan: 'read-only investigation; no writes, no shell side-effects',
|
|
16
16
|
edit: 'may edit files; prompts for shell / risky operations',
|
|
17
|
-
auto: '
|
|
17
|
+
auto: 'more autonomy than edit; the exact policy is per-harness (see notes)',
|
|
18
18
|
skip: 'bypass every permission prompt (dangerously-skip-permissions)',
|
|
19
19
|
};
|
|
20
|
+
/**
|
|
21
|
+
* What `auto` actually does, per harness. Two genuinely different mechanisms
|
|
22
|
+
* wear the same mode name, and conflating them is a safety error in both
|
|
23
|
+
* directions: telling a claude operator that auto never prompts invites an
|
|
24
|
+
* unattended run that stalls on a risky-operation gate, and telling a codex
|
|
25
|
+
* operator that auto prompts for risky work asserts a gate codex does not have.
|
|
26
|
+
* Absent entry = no extra note; the row's own text suffices.
|
|
27
|
+
*/
|
|
28
|
+
const AUTO_SEMANTICS = {
|
|
29
|
+
// Classifier-style: still escalates. --permission-mode auto / --autopilot.
|
|
30
|
+
claude: 'a smart classifier auto-approves safe operations and still prompts for risky ones.',
|
|
31
|
+
copilot: 'a smart classifier auto-approves safe operations and still prompts for risky ones.',
|
|
32
|
+
// Approvals off, sandbox kept: never escalates. A denied command just fails.
|
|
33
|
+
codex: 'approval_policy=never over the same sandbox as edit — it never prompts, and a sandbox-denied command fails instead of raising an approval request.',
|
|
34
|
+
muse: 'runs --disable-approval, which turns approvals off while keeping the sandbox — like codex, it never prompts and a denied command fails.',
|
|
35
|
+
// Native autonomy dial at its top setting.
|
|
36
|
+
droid: 'runs --auto high, droid\'s full-autonomy setting.',
|
|
37
|
+
kimi: "runs kimi's native --auto.",
|
|
38
|
+
};
|
|
20
39
|
/**
|
|
21
40
|
* Build the permission-mode catalog for one harness.
|
|
22
41
|
* `version` only affects the configured run.defaults lookup (modes themselves
|
|
@@ -42,6 +61,15 @@ export function getAgentModesCatalog(agent, version, cwd = process.cwd()) {
|
|
|
42
61
|
if (unsupported.includes('auto')) {
|
|
43
62
|
notes.push(`--mode auto degrades to edit on ${agent} (no native auto classifier).`);
|
|
44
63
|
}
|
|
64
|
+
// MODE_DESCRIPTIONS is one flat Record<Mode, string> rendered for every agent,
|
|
65
|
+
// so it cannot name any single harness's mechanism without lying about the
|
|
66
|
+
// others: claude/copilot auto STILL PROMPTS for risky operations, while codex
|
|
67
|
+
// auto never prompts at all. The row states only what is true everywhere; the
|
|
68
|
+
// mechanism rides here, per harness.
|
|
69
|
+
const autoSemantics = AUTO_SEMANTICS[agent];
|
|
70
|
+
if (autoSemantics && supported.includes('auto')) {
|
|
71
|
+
notes.push(`${agent} --mode auto: ${autoSemantics}`);
|
|
72
|
+
}
|
|
45
73
|
if (unsupported.includes('plan')) {
|
|
46
74
|
notes.push(`--mode plan degrades to ${defaultMode} on ${agent} (no native read-only mode).`);
|
|
47
75
|
}
|
|
@@ -552,9 +552,9 @@ export declare function resolveAgentName(input: string): AgentId | null;
|
|
|
552
552
|
/** Check whether the input string matches any known agent name or alias. */
|
|
553
553
|
export declare function isAgentName(input: string): boolean;
|
|
554
554
|
/**
|
|
555
|
-
* Split a CLI-provided `<agent>[@<version>]` spec into its bare agent
|
|
556
|
-
* optional exact version token, the same way
|
|
557
|
-
*
|
|
555
|
+
* Split a CLI-provided `<agent>[@<version>][#<label>]` spec into its bare agent
|
|
556
|
+
* id, optional exact version token, and optional account label, the same way
|
|
557
|
+
* `agents run` does — NOT the
|
|
558
558
|
* agent-spec qualifier engine (`@latest`/`@all`), which enumerates installed
|
|
559
559
|
* versions for read/diagnostic commands, not a single launch target. Returns an
|
|
560
560
|
* error message rather than throwing so callers decide exit-vs-continue.
|
|
@@ -562,6 +562,7 @@ export declare function isAgentName(input: string): boolean;
|
|
|
562
562
|
export declare function parseAgentVersionSpec(raw: string): {
|
|
563
563
|
agent: AgentId;
|
|
564
564
|
version?: string;
|
|
565
|
+
label?: string;
|
|
565
566
|
} | {
|
|
566
567
|
error: string;
|
|
567
568
|
};
|