@phnx-labs/agents-cli 1.22.53 → 1.22.55
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 +208 -0
- package/README.md +59 -9
- package/dist/bootstrap.js +55 -154
- package/dist/cli/command-registry.d.ts +5 -0
- package/dist/cli/command-registry.js +8 -1
- package/dist/commands/accounts.js +219 -173
- package/dist/commands/apply.js +6 -3
- package/dist/commands/auth-mint.d.ts +8 -0
- package/dist/commands/auth-mint.js +96 -0
- package/dist/commands/auth.js +5 -1
- package/dist/commands/browser.js +1 -1
- package/dist/commands/cost.js +8 -2
- package/dist/commands/daemon.js +2 -2
- package/dist/commands/doctor.js +6 -1
- package/dist/commands/exec.js +10 -8
- package/dist/commands/focus.d.ts +1 -0
- package/dist/commands/focus.js +2 -2
- package/dist/commands/go.d.ts +5 -4
- package/dist/commands/go.js +7 -7
- package/dist/commands/insights.js +9 -0
- package/dist/commands/monitors.js +85 -30
- package/dist/commands/output.js +8 -2
- package/dist/commands/repo.js +18 -0
- package/dist/commands/routines.js +31 -2
- package/dist/commands/secrets.js +33 -14
- package/dist/commands/sessions.d.ts +20 -12
- package/dist/commands/sessions.js +64 -20
- package/dist/commands/setup-accounts.d.ts +8 -0
- package/dist/commands/setup-accounts.js +47 -0
- package/dist/commands/setup.d.ts +1 -1
- package/dist/commands/setup.js +11 -2
- package/dist/commands/share.d.ts +79 -3
- package/dist/commands/share.js +347 -18
- package/dist/commands/ssh.d.ts +7 -0
- package/dist/commands/ssh.js +18 -2
- package/dist/commands/status.js +14 -0
- package/dist/commands/view.d.ts +11 -1
- package/dist/commands/view.js +35 -7
- package/dist/lib/account-registry.js +15 -3
- package/dist/lib/accounting/rotate.d.ts +20 -6
- package/dist/lib/accounting/rotate.js +38 -7
- package/dist/lib/accounting/usage.d.ts +68 -1
- package/dist/lib/accounting/usage.js +116 -10
- package/dist/lib/agent-spec/agents.d.ts +5 -2
- package/dist/lib/agent-spec/agents.js +25 -7
- package/dist/lib/analytics/mix-commands.js +12 -6
- package/dist/lib/auth-mint.d.ts +150 -0
- package/dist/lib/auth-mint.js +434 -0
- package/dist/lib/browser/cdp.d.ts +1 -1
- package/dist/lib/browser/cdp.js +1 -1
- package/dist/lib/browser/ffmpeg.d.ts +12 -0
- package/dist/lib/browser/ffmpeg.js +184 -0
- package/dist/lib/browser/remote-control.d.ts +9 -7
- package/dist/lib/browser/remote-control.js +9 -7
- package/dist/lib/browser/service.js +119 -25
- package/dist/lib/claude-account-token.d.ts +10 -0
- package/dist/lib/claude-account-token.js +14 -4
- package/dist/lib/config-drift.d.ts +37 -0
- package/dist/lib/config-drift.js +72 -0
- package/dist/lib/daemon/auth-sync-service.d.ts +19 -0
- package/dist/lib/daemon/auth-sync-service.js +34 -0
- package/dist/lib/daemon/browser-task-reap-service.d.ts +14 -0
- package/dist/lib/daemon/browser-task-reap-service.js +26 -0
- package/dist/lib/daemon/daemon.js +87 -176
- package/dist/lib/daemon/heartbeat-service.d.ts +13 -0
- package/dist/lib/daemon/heartbeat-service.js +26 -0
- package/dist/lib/daemon/monitor-engine-service.d.ts +9 -5
- package/dist/lib/daemon/monitor-engine-service.js +15 -7
- package/dist/lib/daemon/runner.d.ts +15 -0
- package/dist/lib/daemon/runner.js +23 -0
- package/dist/lib/daemon/secrets-broker-service.d.ts +5 -4
- package/dist/lib/daemon/secrets-broker-service.js +17 -32
- package/dist/lib/daemon/service.d.ts +2 -2
- package/dist/lib/daemon/service.js +1 -1
- package/dist/lib/daemon/session-state-service.d.ts +21 -0
- package/dist/lib/daemon/session-state-service.js +34 -0
- package/dist/lib/daemon/supervisor.d.ts +17 -7
- package/dist/lib/daemon/supervisor.js +87 -14
- package/dist/lib/daemon/tmux-reap-service.d.ts +11 -0
- package/dist/lib/daemon/tmux-reap-service.js +28 -0
- package/dist/lib/daemon/webhook-receiver-service.d.ts +9 -0
- package/dist/lib/daemon/webhook-receiver-service.js +17 -0
- package/dist/lib/daemon-services.d.ts +1 -1
- package/dist/lib/daemon-services.js +25 -0
- package/dist/lib/device-config.d.ts +3 -3
- package/dist/lib/device-config.js +5 -5
- package/dist/lib/devices/connect.d.ts +26 -0
- package/dist/lib/devices/connect.js +48 -1
- package/dist/lib/devices/doctor-findings.d.ts +5 -1
- package/dist/lib/devices/doctor-findings.js +19 -1
- package/dist/lib/devices/harness-inventory.js +5 -2
- package/dist/lib/exec.d.ts +28 -0
- package/dist/lib/exec.js +73 -7
- package/dist/lib/feed/feed.d.ts +1 -1
- package/dist/lib/feed/feed.js +23 -1
- package/dist/lib/feed-broadcast.js +1 -1
- package/dist/lib/fleet/apply.d.ts +11 -0
- package/dist/lib/fleet/apply.js +23 -3
- package/dist/lib/fleet/auth-sync.js +5 -3
- package/dist/lib/help.d.ts +9 -0
- package/dist/lib/help.js +29 -1
- package/dist/lib/hosts/passthrough.d.ts +1 -10
- package/dist/lib/hosts/passthrough.js +1 -13
- package/dist/lib/installations/versions.js +9 -1
- package/dist/lib/linux-userns.d.ts +58 -0
- package/dist/lib/linux-userns.js +116 -0
- package/dist/lib/memory.d.ts +26 -0
- package/dist/lib/memory.js +80 -1
- package/dist/lib/monitors/config.d.ts +11 -0
- package/dist/lib/monitors/config.js +8 -0
- package/dist/lib/monitors/engine.d.ts +5 -1
- package/dist/lib/monitors/engine.js +13 -4
- package/dist/lib/monitors/state.d.ts +37 -1
- package/dist/lib/monitors/state.js +79 -4
- package/dist/lib/permissions-registry.d.ts +2 -0
- package/dist/lib/permissions-registry.js +116 -14
- package/dist/lib/permissions.d.ts +5 -3
- package/dist/lib/permissions.js +25 -27
- package/dist/lib/profiles.d.ts +8 -7
- package/dist/lib/profiles.js +12 -0
- package/dist/lib/project-key.d.ts +9 -0
- package/dist/lib/project-key.js +11 -0
- package/dist/lib/scheduling/routines.d.ts +47 -0
- package/dist/lib/scheduling/routines.js +70 -1
- package/dist/lib/secrets/bundles.d.ts +35 -0
- package/dist/lib/secrets/bundles.js +78 -1
- package/dist/lib/secrets/push.d.ts +3 -8
- package/dist/lib/secrets/push.js +18 -14
- package/dist/lib/secrets/remote.d.ts +9 -18
- package/dist/lib/secrets/remote.js +11 -26
- package/dist/lib/secrets/reserved-sync.d.ts +65 -0
- package/dist/lib/secrets/reserved-sync.js +129 -0
- package/dist/lib/self-heal/checks/hook-manifest.d.ts +2 -0
- package/dist/lib/self-heal/checks/hook-manifest.js +56 -0
- package/dist/lib/self-heal/registry.js +4 -0
- package/dist/lib/self-heal/types.d.ts +1 -1
- package/dist/lib/session/active.js +1 -4
- package/dist/lib/session/db.d.ts +41 -5
- package/dist/lib/session/db.js +132 -30
- package/dist/lib/session/discover.d.ts +32 -4
- package/dist/lib/session/discover.js +119 -25
- package/dist/lib/session/insights.d.ts +14 -0
- package/dist/lib/session/insights.js +25 -2
- package/dist/lib/session/linear.js +1 -1
- package/dist/lib/session/shell-programs.d.ts +17 -0
- package/dist/lib/session/shell-programs.js +21 -0
- package/dist/lib/session/state.js +2 -1
- package/dist/lib/session/stream-render.js +2 -1
- package/dist/lib/session/tool-calls.js +2 -5
- package/dist/lib/session/trajectory-html.js +2 -1
- package/dist/lib/session/trajectory.js +3 -12
- package/dist/lib/session/types.d.ts +8 -0
- package/dist/lib/share/capture.js +11 -2
- package/dist/lib/share/publish.d.ts +56 -5
- package/dist/lib/share/publish.js +126 -18
- package/dist/lib/share/worker-template.d.ts +3 -12
- package/dist/lib/share/worker-template.js +860 -59
- package/dist/lib/startup/root-command.js +2 -1
- package/dist/lib/state.d.ts +16 -0
- package/dist/lib/state.js +178 -46
- package/dist/lib/sync-status.d.ts +4 -0
- package/dist/lib/sync-status.js +3 -0
- package/dist/lib/traces/classify.js +24 -19
- package/dist/lib/usage-refresh.js +2 -1
- package/dist/lib/view-types.d.ts +7 -0
- package/package.json +9 -2
package/dist/commands/ssh.js
CHANGED
|
@@ -34,7 +34,7 @@ import { clearPendingSentinel } from '../lib/devices/pending.js';
|
|
|
34
34
|
import { getDeviceDiscoveryStatus, setDeviceDiscoveryStatus } from '../lib/devices/discovery-policy.js';
|
|
35
35
|
import { isInteractiveTerminal, isPromptCancelled } from './utils.js';
|
|
36
36
|
import { hostNameFor, renderSshConfig } from '../lib/devices/ssh-config.js';
|
|
37
|
-
import { ASKPASS_BUNDLE_ENV, ASKPASS_KEY_ENV, buildSshInvocation, deviceIdentityArgs, fleetDialTarget, writeAskpassShim, } from '../lib/devices/connect.js';
|
|
37
|
+
import { ASKPASS_BUNDLE_ENV, ASKPASS_KEY_ENV, buildSshInvocation, deviceIdentityArgs, fleetDialTarget, isAgentsBrowserDrive, markFleetRemote, writeAskpassShim, } from '../lib/devices/connect.js';
|
|
38
38
|
import { ensureManagedKnownHostsDir, isHostPinned } from '../lib/devices/known-hosts.js';
|
|
39
39
|
import { shouldSyncTerminfo, syncTerminfoToDevice, terminfoHostKey } from '../lib/devices/terminfo.js';
|
|
40
40
|
import { fanOutDevices, fleetHealthSkip, planFleetTargets, remoteFleetTargets, runFleet, skipLabel, upgradeCommand, } from '../lib/devices/fleet.js';
|
|
@@ -351,9 +351,21 @@ function trySshLeasedBox(name, cmd) {
|
|
|
351
351
|
console.error(chalk.red(`Leased box '${name}' is not reachable yet (status: ${boxStatus(box)}).`));
|
|
352
352
|
process.exit(1);
|
|
353
353
|
}
|
|
354
|
-
|
|
354
|
+
// Crabbox ssh does not go through buildSshInvocation; stamp the same
|
|
355
|
+
// consent marker so a browser drive on a leased box is gated too.
|
|
356
|
+
const remoteCmd = leasedBoxRemoteCmd(cmd);
|
|
357
|
+
const res = spawnSync(sshArgv[0], [...sshArgv.slice(1), ...remoteCmd], { stdio: 'inherit' });
|
|
355
358
|
process.exit(res.status ?? 1);
|
|
356
359
|
}
|
|
360
|
+
/**
|
|
361
|
+
* Remote argv for `agents ssh <slug>` into a leased crabbox. Crabbox ssh does
|
|
362
|
+
* not go through {@link buildSshInvocation}, so this stamps the same
|
|
363
|
+
* AGENTS_FLEET_REMOTE consent marker a registered-device ssh would (PHNX-3065).
|
|
364
|
+
* Exported so the branch is unit-testable without a live crabbox.
|
|
365
|
+
*/
|
|
366
|
+
export function leasedBoxRemoteCmd(cmd) {
|
|
367
|
+
return isAgentsBrowserDrive(cmd) ? markFleetRemote(cmd, { shell: 'posix' }) : cmd;
|
|
368
|
+
}
|
|
357
369
|
/** Resolve a device or exit with a clear error. */
|
|
358
370
|
async function mustGetDevice(name) {
|
|
359
371
|
const d = await getDevice(name);
|
|
@@ -2412,6 +2424,10 @@ An interactive login with no command mirrors the home-relative directory you
|
|
|
2412
2424
|
launched from — 'agents ssh yosemite-s0' from ~/src/app lands in ~/src/app on
|
|
2413
2425
|
the target when it exists, else the remote home. Same portable-cwd rule as
|
|
2414
2426
|
'agents run --device'. Passing a command keeps the remote home.
|
|
2427
|
+
|
|
2428
|
+
An 'agents browser …', 'ag browser …', or standalone 'browser …' command is
|
|
2429
|
+
stamped AGENTS_FLEET_REMOTE so the target's browser.remote-control consent
|
|
2430
|
+
gate applies, same as 'agents browser <verb> --device <name>'.
|
|
2415
2431
|
`)
|
|
2416
2432
|
.action(async (name, cmd) => {
|
|
2417
2433
|
// Hidden askpass bridge: ssh execs the shim, which re-invokes us here.
|
package/dist/commands/status.js
CHANGED
|
@@ -97,6 +97,20 @@ export function registerStatusCommand(syncCmd) {
|
|
|
97
97
|
if (status.totals.orphan > 0) {
|
|
98
98
|
console.log(chalk.gray(` (${status.totals.orphan} orphan${status.totals.orphan === 1 ? '' : 's'} — run \`agents prune cleanup\`)`));
|
|
99
99
|
}
|
|
100
|
+
// Config drift is its OWN class, not a per-agent "N missing": a box that has
|
|
101
|
+
// not folded its device-scoped state still carries a stale top-level header or
|
|
102
|
+
// a lingering central fleet/hosts/accounts/browser block. Surface it distinctly
|
|
103
|
+
// so an un-drained box is SEEN here instead of via a mystery pull conflict
|
|
104
|
+
// (PHNX-3315).
|
|
105
|
+
if (status.config.staleHeader || status.config.centralLeaks.length > 0) {
|
|
106
|
+
console.log(` ${'config (device-scoped)'.padEnd(28)} ${chalk.yellow('not drained — folds automatically on normal `agents` use here (idempotent)')}`);
|
|
107
|
+
if (status.config.staleHeader) {
|
|
108
|
+
console.log(chalk.gray(' · top-level agents.yaml header is stale (pre-rename)'));
|
|
109
|
+
}
|
|
110
|
+
for (const leak of status.config.centralLeaks) {
|
|
111
|
+
console.log(chalk.gray(` · central ${leak} should be device-scoped`));
|
|
112
|
+
}
|
|
113
|
+
}
|
|
100
114
|
// Hand off to the shared interactive/apply flow (summary already printed above).
|
|
101
115
|
await promptDriftSync({ cwd, yes: opts.yes, status, quiet: true });
|
|
102
116
|
});
|
package/dist/commands/view.d.ts
CHANGED
|
@@ -11,6 +11,7 @@ import { accountDisplayLabel } from '../lib/agents.js';
|
|
|
11
11
|
import type { AccountInfo } from '../lib/agents.js';
|
|
12
12
|
import type { AgentId } from '../lib/types.js';
|
|
13
13
|
import type { FormatUsageSummaryOpts, UsageInfo } from '../lib/accounting/usage.js';
|
|
14
|
+
import { type ConfiguredDeviceRole } from '../lib/device-config.js';
|
|
14
15
|
import type { ResourceSection, ViewJsonAgent } from '../lib/view-types.js';
|
|
15
16
|
export type { ResourceItemJson, ResourceSection, SyncState, VersionResourcesJson, ViewJsonAgent, ViewJsonVersion } from '../lib/view-types.js';
|
|
16
17
|
import { type ProfileSummary } from '../lib/profiles.js';
|
|
@@ -68,6 +69,13 @@ export declare function descriptionForPrefix(desc: string | undefined, prefix: s
|
|
|
68
69
|
* host is missing is flagged rather than silently listed as runnable.
|
|
69
70
|
*/
|
|
70
71
|
export declare function renderHarnessBlocks(harnesses: ProfileSummary[], installedHosts: Set<AgentId>, showPaths: boolean, byokMap?: Map<string, ByokUsageResult>): void;
|
|
72
|
+
/**
|
|
73
|
+
* The USAGE-READ-2 decision, isolated from its runtime inputs so it can be
|
|
74
|
+
* tested directly: a usage read may fall through to the interactive OAuth login
|
|
75
|
+
* ONLY for a foreground human render on a `personal` device. Both conditions are
|
|
76
|
+
* required — role alone is not sufficient.
|
|
77
|
+
*/
|
|
78
|
+
export declare function allowInteractiveUsageLogin(role: ConfiguredDeviceRole | undefined, isTTY: boolean): boolean;
|
|
71
79
|
/** Build the set of resource sections to include in `--json` from the
|
|
72
80
|
* `--resources` / `--detailed` flags, plus (in --json mode) the per-section
|
|
73
81
|
* boolean filters (`--skills` etc.) that plain `--json` historically ignored. */
|
|
@@ -81,7 +89,9 @@ export declare function parseResourceSections(options: {
|
|
|
81
89
|
* agents-cli extension's "resume current session in best available version"
|
|
82
90
|
* command).
|
|
83
91
|
*/
|
|
84
|
-
export declare function collectAgentsJson(filterAgentId?: AgentId, resourceSections?: Set<ResourceSection
|
|
92
|
+
export declare function collectAgentsJson(filterAgentId?: AgentId, resourceSections?: Set<ResourceSection>, opts?: {
|
|
93
|
+
forceRefresh?: boolean;
|
|
94
|
+
}): Promise<ViewJsonAgent[]>;
|
|
85
95
|
/**
|
|
86
96
|
* Identity key for duplicate-install detection. Prefers accountKey — which
|
|
87
97
|
* encodes account AND org — over the bare email: two installs can share an
|
package/dist/commands/view.js
CHANGED
|
@@ -8,7 +8,8 @@ import { AGENTS, ALL_AGENT_IDS, accountDisplayLabel, getAllCliStates, getUnmanag
|
|
|
8
8
|
import { ambientClaudeToken, loginHint } from '../lib/signin-badge.js';
|
|
9
9
|
import { machineId } from '../lib/machine-id.js';
|
|
10
10
|
import { authCacheKey, readAuthHealthCache } from '../lib/auth-health.js';
|
|
11
|
-
import { agentReportsUsage, classifyUsageErrorKind, deriveUsageStatusFromSnapshot, formatUsageSection, formatUsageSummary, formatUsageStatusBadge, getUsageBenignState, getUsageInfoForIdentity, getUsageInfoByIdentity, getUsageLookupKey, isUsageHeadlessScopeError, } from '../lib/accounting/usage.js';
|
|
11
|
+
import { agentReportsUsage, classifyUsageErrorKind, deriveUsageStatusFromSnapshot, formatUsageSection, formatUsageSummary, formatUsageStatusBadge, getUsageBenignState, getUsageInfoForIdentity, getUsageInfoByIdentity, getUsageLookupKey, isUsageHeadlessScopeError, usageErrorForDisplay, } from '../lib/accounting/usage.js';
|
|
12
|
+
import { selfConfiguredDeviceRole } from '../lib/device-config.js';
|
|
12
13
|
import { readManifest } from '../lib/manifest.js';
|
|
13
14
|
import { listInstalledVersions, listInstalledVersionDirs, getGlobalDefault, getVersionHomePath, getVersionDir, removeVersion, printTrashFooter, reconcileStaleLatestForAgent, isGlobalBinaryAgent, getLiveVersion, isVersionIsolated, getIsolatedDefault, } from '../lib/installations/versions.js';
|
|
14
15
|
import { ensureVersionedAliasCurrent, removeShim, } from '../lib/installations/shims.js';
|
|
@@ -316,6 +317,29 @@ function renderHostClisSection(cwd) {
|
|
|
316
317
|
console.log(` ${chalk.red('error')} ${chalk.gray(err.file)}: ${chalk.gray(err.reason)}`);
|
|
317
318
|
}
|
|
318
319
|
}
|
|
320
|
+
/**
|
|
321
|
+
* The USAGE-READ-2 decision, isolated from its runtime inputs so it can be
|
|
322
|
+
* tested directly: a usage read may fall through to the interactive OAuth login
|
|
323
|
+
* ONLY for a foreground human render on a `personal` device. Both conditions are
|
|
324
|
+
* required — role alone is not sufficient.
|
|
325
|
+
*/
|
|
326
|
+
export function allowInteractiveUsageLogin(role, isTTY) {
|
|
327
|
+
return role === 'personal' && isTTY === true;
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Whether this `agents view` invocation may fall through to the interactive
|
|
331
|
+
* OAuth login for a usage read (USAGE-READ-2). True only for a foreground human
|
|
332
|
+
* render on a `personal` device: the interactive login is the sole credential
|
|
333
|
+
* carrying the `user:profile` scope the usage endpoint needs, and a human
|
|
334
|
+
* running one command is not the unattended-loop revocation risk RUSH-1822
|
|
335
|
+
* fixed. The `--json` path never reaches these render functions (it returns
|
|
336
|
+
* early via `collectAgentsJson`), and a non-TTY (piped/scripted) run is excluded
|
|
337
|
+
* here too, so a machine reader can never silently acquire the interactive
|
|
338
|
+
* credential — role alone is not sufficient.
|
|
339
|
+
*/
|
|
340
|
+
function usageAllowInteractiveLogin() {
|
|
341
|
+
return allowInteractiveUsageLogin(selfConfiguredDeviceRole(), process.stdout.isTTY === true);
|
|
342
|
+
}
|
|
319
343
|
async function showInstalledVersions(filterAgentId, viewOpts) {
|
|
320
344
|
const spinnerText = filterAgentId
|
|
321
345
|
? `Checking ${agentLabel(filterAgentId)} agents...`
|
|
@@ -422,7 +446,10 @@ async function showInstalledVersions(filterAgentId, viewOpts) {
|
|
|
422
446
|
cliVersion,
|
|
423
447
|
info,
|
|
424
448
|
})),
|
|
425
|
-
], {
|
|
449
|
+
], {
|
|
450
|
+
forceRefresh: viewOpts?.forceRefresh,
|
|
451
|
+
allowInteractiveLogin: usageAllowInteractiveLogin(),
|
|
452
|
+
});
|
|
426
453
|
spinner.stop();
|
|
427
454
|
console.log(chalk.bold('Installed Agent CLIs\n'));
|
|
428
455
|
const mergeCanonical = (info) => {
|
|
@@ -930,7 +957,7 @@ async function showAgentResources(agentId, requestedVersion, filter) {
|
|
|
930
957
|
home,
|
|
931
958
|
cliVersion: version,
|
|
932
959
|
info: accountInfo,
|
|
933
|
-
});
|
|
960
|
+
}, { allowInteractiveLogin: usageAllowInteractiveLogin() });
|
|
934
961
|
const accountLabel = accountColumnLabel(accountInfo);
|
|
935
962
|
const emailStr = accountLabel ? chalk.cyan(` ${accountLabel}`) : '';
|
|
936
963
|
const status = chalk.green(version);
|
|
@@ -1209,7 +1236,7 @@ export function parseResourceSections(options, jsonMode) {
|
|
|
1209
1236
|
* agents-cli extension's "resume current session in best available version"
|
|
1210
1237
|
* command).
|
|
1211
1238
|
*/
|
|
1212
|
-
export async function collectAgentsJson(filterAgentId, resourceSections) {
|
|
1239
|
+
export async function collectAgentsJson(filterAgentId, resourceSections, opts) {
|
|
1213
1240
|
const agentsToShow = filterAgentId ? [filterAgentId] : ALL_AGENT_IDS;
|
|
1214
1241
|
const authCache = readAuthHealthCache();
|
|
1215
1242
|
const host = machineId();
|
|
@@ -1230,7 +1257,7 @@ export async function collectAgentsJson(filterAgentId, resourceSections) {
|
|
|
1230
1257
|
home,
|
|
1231
1258
|
cliVersion: version,
|
|
1232
1259
|
info,
|
|
1233
|
-
})));
|
|
1260
|
+
})), { forceRefresh: opts?.forceRefresh === true });
|
|
1234
1261
|
const mergeCanonical = (info) => {
|
|
1235
1262
|
const key = getUsageLookupKey(info);
|
|
1236
1263
|
if (!key)
|
|
@@ -1273,6 +1300,7 @@ export async function collectAgentsJson(filterAgentId, resourceSections) {
|
|
|
1273
1300
|
plan: info.plan,
|
|
1274
1301
|
usageStatus: info.usageStatus,
|
|
1275
1302
|
overageCredits: info.overageCredits,
|
|
1303
|
+
usageError: usageErrorForDisplay(usageInfo?.error),
|
|
1276
1304
|
windows: snapshot
|
|
1277
1305
|
? snapshot.windows.map((w) => ({
|
|
1278
1306
|
key: w.key,
|
|
@@ -1562,7 +1590,7 @@ export async function viewAction(agentArg, options) {
|
|
|
1562
1590
|
return;
|
|
1563
1591
|
}
|
|
1564
1592
|
if (json) {
|
|
1565
|
-
const data = await collectAgentsJson(undefined, resourceSections);
|
|
1593
|
+
const data = await collectAgentsJson(undefined, resourceSections, { forceRefresh });
|
|
1566
1594
|
console.log(JSON.stringify(data, null, 2));
|
|
1567
1595
|
return;
|
|
1568
1596
|
}
|
|
@@ -1624,7 +1652,7 @@ export async function viewAction(agentArg, options) {
|
|
|
1624
1652
|
if (json) {
|
|
1625
1653
|
// --json ignores the @version suffix, but --resources/--detailed (or a
|
|
1626
1654
|
// section flag) now attach each version's resource inventory + sync-state.
|
|
1627
|
-
const data = await collectAgentsJson(agentId, resourceSections);
|
|
1655
|
+
const data = await collectAgentsJson(agentId, resourceSections, { forceRefresh });
|
|
1628
1656
|
console.log(JSON.stringify(data[0] ?? { agent: agentId, versions: [], harnesses: [] }, null, 2));
|
|
1629
1657
|
return;
|
|
1630
1658
|
}
|
|
@@ -23,9 +23,9 @@ import * as yaml from 'yaml';
|
|
|
23
23
|
import { atomicWriteFileSync } from './fs-atomic.js';
|
|
24
24
|
import { getUserAgentsDir, readMeta, updateMeta } from './state.js';
|
|
25
25
|
import { deleteKeychainToken, getKeychainToken, hasKeychainToken } from './secrets/index.js';
|
|
26
|
-
import { bundleExists, deleteBundle, listBundles, readBundle, renameBundle, writeBundleWithItems } from './secrets/bundles.js';
|
|
26
|
+
import { bundleExists, deleteBundle, listBundles, readAndResolveBundleEnv, readBundle, renameBundle, writeBundleWithItems } from './secrets/bundles.js';
|
|
27
27
|
import { getAccountProvider } from './account-provider-registry.js';
|
|
28
|
-
import { accountSecretItem, buildAccountBundle, parseAccountBundle } from './account-schema.js';
|
|
28
|
+
import { accountSecretItem, buildAccountBundle, parseAccountBundle, secretVarFor } from './account-schema.js';
|
|
29
29
|
const NAME = /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/;
|
|
30
30
|
const NATIVE_LABEL = /^[a-zA-Z0-9][a-zA-Z0-9@._+-]*$/;
|
|
31
31
|
const AUTH_KINDS = ['api-key', 'setup-token', 'bearer-token'];
|
|
@@ -455,6 +455,18 @@ export function resolveCredentialAccount(name, host, expectedProvider, base = ge
|
|
|
455
455
|
const envVar = account.auth === 'setup-token' ? 'CLAUDE_CODE_OAUTH_TOKEN' : adapter.envFor(host, account.auth);
|
|
456
456
|
if (!hasKeychainToken(account.secretRef))
|
|
457
457
|
throw new Error(`Credential for account '${account.name}' is missing on this device. Add it with 'agents accounts set-key ${account.name}'.`);
|
|
458
|
+
const secretVar = secretVarFor(account.auth);
|
|
459
|
+
// Account bundles are policy `never`, so their value items carry no biometry
|
|
460
|
+
// ACL. Resolve through the bundle path that verifies that policy and attests
|
|
461
|
+
// `silentNoAcl` to the headless keychain guard. Calling getKeychainToken()
|
|
462
|
+
// directly makes a headless --account launch reject the prompt-free item as
|
|
463
|
+
// if it required Touch ID before the helper ever reads it (PHNX-2939).
|
|
464
|
+
const secret = readAndResolveBundleEnv(account.name, {
|
|
465
|
+
keys: [secretVar],
|
|
466
|
+
keyMode: 'storage',
|
|
467
|
+
agentOnly: true,
|
|
468
|
+
caller: 'accounts resolve',
|
|
469
|
+
}).env[secretVar];
|
|
458
470
|
const connectionEnv = { ...adapter.connectionEnvFor(host) };
|
|
459
471
|
if (account.baseUrl) {
|
|
460
472
|
const baseUrlEnv = adapter.baseUrlEnvFor(host);
|
|
@@ -467,7 +479,7 @@ export function resolveCredentialAccount(name, host, expectedProvider, base = ge
|
|
|
467
479
|
name: account.name,
|
|
468
480
|
provider: account.provider,
|
|
469
481
|
auth: account.auth,
|
|
470
|
-
env: { ...connectionEnv, [envVar]:
|
|
482
|
+
env: { ...connectionEnv, [envVar]: secret },
|
|
471
483
|
};
|
|
472
484
|
}
|
|
473
485
|
/**
|
|
@@ -336,16 +336,30 @@ export declare function resolveAccountVersion(agent: AgentId, account: string):
|
|
|
336
336
|
export declare function selectBalancedVersion(agent: AgentId): Promise<RotateResult | null>;
|
|
337
337
|
/** Select the configured version if available, otherwise another available version. */
|
|
338
338
|
export declare function selectAvailableVersion(agent: AgentId, preferredVersion?: string | null): Promise<RotateResult | null>;
|
|
339
|
+
/**
|
|
340
|
+
* Resolve the version `agents run` should use when the caller did not pin
|
|
341
|
+
* one with `@version`. The caller supplies the effective strategy.
|
|
342
|
+
*
|
|
343
|
+
* `pinned` still prefers the workspace/global default, but it MUST NOT launch
|
|
344
|
+
* a logged-out (or revoked) default when the same device holds a signed-in
|
|
345
|
+
* version that can run — that was a silent 1-second death on fleet workers
|
|
346
|
+
* whose default home had no credential (PHNX-2685). A rate-limited pin is
|
|
347
|
+
* still honoured: `--strategy pinned` remains the escape hatch to force the
|
|
348
|
+
* default through a throttle. When the default is auth-blocked and nothing
|
|
349
|
+
* else is healthy, `exhausted` is set so the caller fails loud instead of
|
|
350
|
+
* spawning into a credential-less home.
|
|
351
|
+
*/
|
|
339
352
|
export declare function resolveRunVersion(agent: AgentId, strategy: RunStrategy, cwd?: string, collect?: (agent: AgentId) => Promise<RotateCandidate[]>): Promise<{
|
|
340
353
|
version: string | null;
|
|
341
354
|
rotation: RotateResult | null;
|
|
342
355
|
/**
|
|
343
|
-
* Set when a
|
|
344
|
-
*
|
|
345
|
-
*
|
|
346
|
-
*
|
|
347
|
-
*
|
|
348
|
-
*
|
|
356
|
+
* Set when a strategy found ZERO healthy candidates among the installed
|
|
357
|
+
* versions: the full excluded set, so callers fail loud with per-account
|
|
358
|
+
* reasons instead of launching the exhausted pinned default (RUSH-2132).
|
|
359
|
+
* Also set for `pinned` when the default is logged out / revoked and no
|
|
360
|
+
* signed-in alternative exists (PHNX-2685). Undefined for successful picks
|
|
361
|
+
* and when no version is installed at all (the pre-existing not-installed
|
|
362
|
+
* path — there is no account to be "unhealthy").
|
|
349
363
|
*/
|
|
350
364
|
exhausted?: RotateCandidate[];
|
|
351
365
|
}>;
|
|
@@ -690,12 +690,6 @@ export async function selectBalancedVersion(agent) {
|
|
|
690
690
|
export async function selectAvailableVersion(agent, preferredVersion) {
|
|
691
691
|
return pickAvailableCandidate(await collectRunCandidates(agent), preferredVersion);
|
|
692
692
|
}
|
|
693
|
-
/**
|
|
694
|
-
* Resolve the version `agents run` should use when the caller did not pin
|
|
695
|
-
* one with `@version`. The caller supplies the effective strategy; if that
|
|
696
|
-
* strategy cannot find a usable candidate, fall back to the pinned
|
|
697
|
-
* workspace/global version.
|
|
698
|
-
*/
|
|
699
693
|
/**
|
|
700
694
|
* Record a rotation pick so parallel callers see it as recently-used.
|
|
701
695
|
* Writes a stamp file per agent — lightweight, no locking needed since
|
|
@@ -722,12 +716,49 @@ function readRotationStamp(agent) {
|
|
|
722
716
|
catch { /* missing or corrupt — treat as no stamp */ }
|
|
723
717
|
return null;
|
|
724
718
|
}
|
|
719
|
+
/**
|
|
720
|
+
* Resolve the version `agents run` should use when the caller did not pin
|
|
721
|
+
* one with `@version`. The caller supplies the effective strategy.
|
|
722
|
+
*
|
|
723
|
+
* `pinned` still prefers the workspace/global default, but it MUST NOT launch
|
|
724
|
+
* a logged-out (or revoked) default when the same device holds a signed-in
|
|
725
|
+
* version that can run — that was a silent 1-second death on fleet workers
|
|
726
|
+
* whose default home had no credential (PHNX-2685). A rate-limited pin is
|
|
727
|
+
* still honoured: `--strategy pinned` remains the escape hatch to force the
|
|
728
|
+
* default through a throttle. When the default is auth-blocked and nothing
|
|
729
|
+
* else is healthy, `exhausted` is set so the caller fails loud instead of
|
|
730
|
+
* spawning into a credential-less home.
|
|
731
|
+
*/
|
|
725
732
|
export async function resolveRunVersion(agent, strategy, cwd = process.cwd(), collect = collectRunCandidates) {
|
|
726
733
|
const fallback = resolveVersion(agent, cwd);
|
|
734
|
+
const candidates = await collect(agent);
|
|
727
735
|
if (strategy === 'pinned') {
|
|
736
|
+
const pinnedCandidate = fallback
|
|
737
|
+
? candidates.find((c) => c.version === fallback)
|
|
738
|
+
: undefined;
|
|
739
|
+
// Auth-blocked pin: the home cannot authenticate, so launching it is a
|
|
740
|
+
// guaranteed miss. Prefer a signed-in sibling on this device.
|
|
741
|
+
if (pinnedCandidate && isSignInRecoverable(readinessFromCandidate(pinnedCandidate))) {
|
|
742
|
+
const rotation = pickAvailableCandidate(candidates, fallback);
|
|
743
|
+
if (rotation) {
|
|
744
|
+
emit('rotation.resolved', {
|
|
745
|
+
module: 'rotate',
|
|
746
|
+
agent,
|
|
747
|
+
version: rotation.picked.version,
|
|
748
|
+
strategy,
|
|
749
|
+
healthy: rotation.healthy.length,
|
|
750
|
+
excluded: rotation.excluded.length,
|
|
751
|
+
});
|
|
752
|
+
return { version: rotation.picked.version, rotation };
|
|
753
|
+
}
|
|
754
|
+
return {
|
|
755
|
+
version: fallback,
|
|
756
|
+
rotation: null,
|
|
757
|
+
exhausted: candidates.length > 0 ? candidates : undefined,
|
|
758
|
+
};
|
|
759
|
+
}
|
|
728
760
|
return { version: fallback, rotation: null };
|
|
729
761
|
}
|
|
730
|
-
const candidates = await collect(agent);
|
|
731
762
|
const rotation = strategy === 'available'
|
|
732
763
|
? pickAvailableCandidate(candidates, fallback)
|
|
733
764
|
: pickBalancedCandidate(candidates);
|
|
@@ -22,6 +22,12 @@ import type { AgentId } from '../types.js';
|
|
|
22
22
|
*/
|
|
23
23
|
export declare function usageNoCredentialError(agent: string): string;
|
|
24
24
|
export declare function usageExpiredCredentialError(agent: string): string;
|
|
25
|
+
/**
|
|
26
|
+
* Kimi-specific expired-credential wording. A normal Kimi launch refreshes its
|
|
27
|
+
* own OAuth access token, so the recovery action for an expired Kimi credential
|
|
28
|
+
* is to run Kimi once — not to re-auth through agents-cli (RUSH-3198).
|
|
29
|
+
*/
|
|
30
|
+
export declare function usageExpiredKimiCredentialError(): string;
|
|
25
31
|
export declare function usageRejectedError(agent: string, status: number): string;
|
|
26
32
|
/**
|
|
27
33
|
* Canonical phrase for the Anthropic setup-token scope gap (RUSH-2392).
|
|
@@ -39,6 +45,26 @@ export declare const USAGE_HEADLESS_SCOPE_MARKER = "usage unavailable (headless)
|
|
|
39
45
|
export declare function usageHeadlessScopeError(agent?: string): string;
|
|
40
46
|
/** True when an error string is the setup-token scope gap (RUSH-2392). */
|
|
41
47
|
export declare function isUsageHeadlessScopeError(error: string | null | undefined): boolean;
|
|
48
|
+
/**
|
|
49
|
+
* Canonical phrase for a Claude account the usage reader holds no usable
|
|
50
|
+
* credential for. Distinct from {@link USAGE_HEADLESS_SCOPE_MARKER}, which
|
|
51
|
+
* means a setup-token WAS read and the endpoint refused its scope.
|
|
52
|
+
*/
|
|
53
|
+
export declare const USAGE_NO_USAGE_CREDENTIAL_MARKER = "usage unavailable (no usage credential)";
|
|
54
|
+
/**
|
|
55
|
+
* Claude's own no-credential message. The shared
|
|
56
|
+
* {@link usageNoCredentialError} offers "sign in" as the remedy, which holds
|
|
57
|
+
* for Kimi/Droid/Cursor — their CLIs rotate a readable token on the next launch
|
|
58
|
+
* — and is false for Claude: the usage read deliberately never touches the
|
|
59
|
+
* interactive login (RUSH-1822), so an account that IS signed in reads as
|
|
60
|
+
* unreadable here and signing in again changes nothing. Naming only the second
|
|
61
|
+
* remedy would send the operator to `claude setup-token`, whose token then hits
|
|
62
|
+
* the `user:profile` scope gap (RUSH-2392) — the loop reported in #2987 — so
|
|
63
|
+
* this message states both constraints and that the account still runs.
|
|
64
|
+
*/
|
|
65
|
+
export declare function usageNoClaudeUsageCredentialError(): string;
|
|
66
|
+
/** True when an error string is the Claude no-usage-credential state (#2987). */
|
|
67
|
+
export declare function isUsageNoUsageCredentialError(error: string | null | undefined): boolean;
|
|
42
68
|
/**
|
|
43
69
|
* Detect Anthropic's usage-endpoint scope denial: HTTP 403 whose body names
|
|
44
70
|
* `user:profile` (or "scope requirement"). A bare 403 without that body stays
|
|
@@ -72,6 +98,28 @@ export declare function usageUnreachableError(agent: string, cause?: unknown): s
|
|
|
72
98
|
export declare const USAGE_NO_RECENT_USAGE_MARKER = "no usage recorded yet";
|
|
73
99
|
export declare const USAGE_BENIGN_STATE: unique symbol;
|
|
74
100
|
export type UsageBenignState = 'no-recent-usage';
|
|
101
|
+
/**
|
|
102
|
+
* Sentinel `UsageInfo.error` for a read-only lookup whose cache held nothing
|
|
103
|
+
* (`getUsageInfoForIdentity`). No request was made and nothing failed — the
|
|
104
|
+
* daemon simply has not collected this account yet. It was an unclassified
|
|
105
|
+
* literal, so `classifyUsageErrorKind` fell through to `'rejected'` and
|
|
106
|
+
* `agents view` printed the generic "usage unavailable" for a cold cache,
|
|
107
|
+
* which reads as a failure the operator should chase (#2987). The string value
|
|
108
|
+
* is unchanged; callers that already compare against `'stale'` keep working.
|
|
109
|
+
*/
|
|
110
|
+
export declare const USAGE_NOT_COLLECTED_MARKER = "stale";
|
|
111
|
+
/**
|
|
112
|
+
* Human-facing form of a `UsageInfo.error` for a machine/JSON consumer
|
|
113
|
+
* (`agents view --json`'s `usageError`). Every error string this module
|
|
114
|
+
* constructs is already a full human sentence EXCEPT the internal
|
|
115
|
+
* {@link USAGE_NOT_COLLECTED_MARKER} (`'stale'`) sentinel, which a read-only
|
|
116
|
+
* lookup returns for a never-cached account when `--refresh` was not passed. That
|
|
117
|
+
* value is an internal cache signal, not an error message, and leaking it verbatim
|
|
118
|
+
* contradicts the field's "human-readable" contract (PHNX-3348). Map it to a
|
|
119
|
+
* plain-language, actionable string and pass every genuine error through
|
|
120
|
+
* unchanged. Returns `null` when there is no error.
|
|
121
|
+
*/
|
|
122
|
+
export declare function usageErrorForDisplay(error: string | null | undefined): string | null;
|
|
75
123
|
/**
|
|
76
124
|
* Shared error-classification + 429 backoff for a networked usage fetch whose
|
|
77
125
|
* only signal is an HTTP status (or none at all, on a network failure) —
|
|
@@ -91,7 +139,7 @@ export declare function classifyUsageFetchFailure(agent: string, agentId: 'antig
|
|
|
91
139
|
* distinct causes (RUSH-3040). Matched against the canonical strings this file
|
|
92
140
|
* constructs — never re-derive these prefixes at a call site.
|
|
93
141
|
*/
|
|
94
|
-
export type UsageErrorKind = 'no-credential' | 'expired-credential' | 'rate-limited' | 'rejected' | 'headless-scope' | 'unreachable';
|
|
142
|
+
export type UsageErrorKind = 'no-credential' | 'no-usage-credential' | 'expired-credential' | 'rate-limited' | 'rejected' | 'headless-scope' | 'unreachable' | 'not-collected';
|
|
95
143
|
/** Classify a `UsageInfo.error` string into its {@link UsageErrorKind}, or null when there is no error. */
|
|
96
144
|
export declare function classifyUsageErrorKind(error: string | null | undefined): UsageErrorKind | null;
|
|
97
145
|
/**
|
|
@@ -176,6 +224,16 @@ interface UsageOptions {
|
|
|
176
224
|
* setup-token, or `<home>/.claude/.credentials.json` only.
|
|
177
225
|
*/
|
|
178
226
|
fileOnly?: boolean;
|
|
227
|
+
/**
|
|
228
|
+
* When true, a read that finds no file-based setup-token MAY fall through to
|
|
229
|
+
* Claude Code's interactive OAuth login (the only credential carrying
|
|
230
|
+
* `user:profile`, which `/api/oauth/usage` requires). OFF by default and set
|
|
231
|
+
* ONLY by a foreground human `agents view` on a `personal` device (see
|
|
232
|
+
* USAGE-READ-2). Every background caller — daemon usage warm, auth-health
|
|
233
|
+
* probe, watchdog — leaves it unset, preserving the RUSH-1822 guarantee that
|
|
234
|
+
* an unattended loop never transmits the interactive login to Anthropic.
|
|
235
|
+
*/
|
|
236
|
+
allowInteractiveLogin?: boolean;
|
|
179
237
|
}
|
|
180
238
|
/** Canonical input for a single usage fetch operation. */
|
|
181
239
|
export interface UsageFetchInput {
|
|
@@ -272,6 +330,14 @@ export declare const USAGE_FETCH_CONCURRENCY = 3;
|
|
|
272
330
|
export interface UsageLookupOptions {
|
|
273
331
|
forceRefresh?: boolean;
|
|
274
332
|
fileOnly?: boolean;
|
|
333
|
+
/**
|
|
334
|
+
* Permit a foreground personal-device usage read to fall through to the
|
|
335
|
+
* interactive login when no setup-token exists (USAGE-READ-2). Set ONLY by
|
|
336
|
+
* `agents view` when `selfConfiguredDeviceRole() === 'personal'` and the
|
|
337
|
+
* output is a human TTY (not `--json`). Threads into `getClaudeUsageInfo` →
|
|
338
|
+
* `loadClaudeOauth`. Unset for every other lookup.
|
|
339
|
+
*/
|
|
340
|
+
allowInteractiveLogin?: boolean;
|
|
275
341
|
}
|
|
276
342
|
export declare function getUsageInfoByIdentity(inputs: UsageIdentityInput[], opts?: UsageLookupOptions): Promise<{
|
|
277
343
|
canonicalByUsageKey: Map<string, AccountInfo>;
|
|
@@ -574,6 +640,7 @@ export declare function normalizeDroidWindows(data: DroidBillingLimitsResponse):
|
|
|
574
640
|
export declare function loadClaudeOauth(home?: string, opts?: {
|
|
575
641
|
accessTokenCache?: boolean;
|
|
576
642
|
fileOnly?: boolean;
|
|
643
|
+
allowInteractiveLogin?: boolean;
|
|
577
644
|
}): Promise<ClaudeOauthCredentials | null>;
|
|
578
645
|
/**
|
|
579
646
|
* Save Claude OAuth credentials to the system keychain/keyring.
|