@phnx-labs/agents-cli 1.22.84 → 1.22.86

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.
Files changed (259) hide show
  1. package/CHANGELOG.md +96 -0
  2. package/README.md +80 -53
  3. package/dist/bootstrap.d.ts +7 -6
  4. package/dist/bootstrap.js +20 -27
  5. package/dist/cli/command-registry.js +5 -1
  6. package/dist/commands/accounts.d.ts +6 -13
  7. package/dist/commands/accounts.js +330 -143
  8. package/dist/commands/alias.js +7 -3
  9. package/dist/commands/apply.js +1 -1
  10. package/dist/commands/auth-mint.d.ts +7 -3
  11. package/dist/commands/auth-mint.js +21 -9
  12. package/dist/commands/auth.js +2 -3
  13. package/dist/commands/browser.js +10 -10
  14. package/dist/commands/daemon.d.ts +7 -3
  15. package/dist/commands/daemon.js +40 -25
  16. package/dist/commands/doctor.js +28 -2
  17. package/dist/commands/exec.js +87 -39
  18. package/dist/commands/fleet-capture.js +21 -2
  19. package/dist/commands/harness-wizard.js +2 -2
  20. package/dist/commands/lease.js +7 -12
  21. package/dist/commands/profiles.d.ts +2 -2
  22. package/dist/commands/profiles.js +20 -13
  23. package/dist/commands/repo.js +3 -3
  24. package/dist/commands/run-account-picker.d.ts +3 -3
  25. package/dist/commands/run-account-picker.js +29 -30
  26. package/dist/commands/secrets-passthrough.d.ts +20 -0
  27. package/dist/commands/secrets-passthrough.js +43 -0
  28. package/dist/commands/setup-accounts.d.ts +3 -2
  29. package/dist/commands/setup-accounts.js +4 -4
  30. package/dist/commands/setup-secrets.d.ts +16 -21
  31. package/dist/commands/setup-secrets.js +43 -224
  32. package/dist/commands/ssh.js +2 -2
  33. package/dist/commands/sync.js +1 -1
  34. package/dist/commands/update.d.ts +10 -0
  35. package/dist/commands/update.js +43 -40
  36. package/dist/commands/versions.d.ts +11 -11
  37. package/dist/commands/versions.js +131 -83
  38. package/dist/commands/view.d.ts +1 -9
  39. package/dist/commands/view.js +41 -55
  40. package/dist/commands/webhook.js +5 -5
  41. package/dist/commands/workflows.js +2 -1
  42. package/dist/index.d.ts +12 -12
  43. package/dist/index.js +13 -42
  44. package/dist/lib/account-capabilities.d.ts +1 -1
  45. package/dist/lib/account-capabilities.js +1 -1
  46. package/dist/lib/account-catalog.d.ts +121 -6
  47. package/dist/lib/account-catalog.js +421 -7
  48. package/dist/lib/account-registry.d.ts +8 -30
  49. package/dist/lib/account-registry.js +36 -79
  50. package/dist/lib/account-schema.d.ts +1 -1
  51. package/dist/lib/account-schema.js +1 -1
  52. package/dist/lib/accounting/account-pool-collect.js +16 -4
  53. package/dist/lib/accounting/rotate.d.ts +20 -7
  54. package/dist/lib/accounting/rotate.js +89 -13
  55. package/dist/lib/accounting/usage.js +11 -16
  56. package/dist/lib/accounts/add.d.ts +138 -0
  57. package/dist/lib/accounts/add.js +651 -0
  58. package/dist/lib/accounts/migrate.d.ts +117 -0
  59. package/dist/lib/accounts/migrate.js +536 -0
  60. package/dist/lib/accounts/slots.d.ts +13 -0
  61. package/dist/lib/accounts/slots.js +100 -0
  62. package/dist/lib/agent-spec/agents.d.ts +16 -0
  63. package/dist/lib/agent-spec/agents.js +38 -4
  64. package/dist/lib/app-bundle-install.js +5 -4
  65. package/dist/lib/auth-health.d.ts +3 -0
  66. package/dist/lib/auth-health.js +11 -0
  67. package/dist/lib/auth-mint.d.ts +52 -16
  68. package/dist/lib/auth-mint.js +117 -39
  69. package/dist/lib/browser/chrome.d.ts +1 -1
  70. package/dist/lib/browser/chrome.js +5 -5
  71. package/dist/lib/byok-usage.js +3 -3
  72. package/dist/lib/claude-account-token.d.ts +47 -3
  73. package/dist/lib/claude-account-token.js +172 -50
  74. package/dist/lib/cloud/antigravity.js +4 -4
  75. package/dist/lib/cloud/cursor.js +4 -4
  76. package/dist/lib/crabbox/cli.d.ts +1 -1
  77. package/dist/lib/crabbox/cli.js +6 -6
  78. package/dist/lib/crabbox/runtimes.d.ts +3 -3
  79. package/dist/lib/crabbox/runtimes.js +5 -5
  80. package/dist/lib/daemon/account-state-daemon-service.d.ts +37 -1
  81. package/dist/lib/daemon/account-state-daemon-service.js +199 -3
  82. package/dist/lib/daemon/auth-sync-service.js +32 -1
  83. package/dist/lib/daemon/daemon.d.ts +6 -16
  84. package/dist/lib/daemon/daemon.js +52 -97
  85. package/dist/lib/daemon/daemon.test-fixture.d.ts +7 -9
  86. package/dist/lib/daemon/daemon.test-fixture.js +22 -40
  87. package/dist/lib/daemon/harness-update-service.d.ts +1 -1
  88. package/dist/lib/daemon/harness-update-service.js +1 -1
  89. package/dist/lib/daemon/runner.d.ts +15 -3
  90. package/dist/lib/daemon/runner.js +49 -24
  91. package/dist/lib/daemon-health.d.ts +1 -2
  92. package/dist/lib/daemon-health.js +7 -6
  93. package/dist/lib/daemon-services.d.ts +1 -1
  94. package/dist/lib/daemon-services.js +1 -11
  95. package/dist/lib/daemon-webhooks.d.ts +8 -7
  96. package/dist/lib/daemon-webhooks.js +10 -9
  97. package/dist/lib/device-config.js +1 -1
  98. package/dist/lib/devices/doctor-findings.d.ts +1 -1
  99. package/dist/lib/devices/harness-inventory.d.ts +39 -0
  100. package/dist/lib/devices/harness-inventory.js +126 -4
  101. package/dist/lib/doctor-diff.js +2 -1
  102. package/dist/lib/exec-account-home.d.ts +38 -0
  103. package/dist/lib/exec-account-home.js +164 -0
  104. package/dist/lib/exec.d.ts +30 -0
  105. package/dist/lib/exec.js +101 -31
  106. package/dist/lib/fleet/apply.d.ts +2 -2
  107. package/dist/lib/fleet/apply.js +4 -4
  108. package/dist/lib/fleet/auth-sync.js +4 -3
  109. package/dist/lib/fleet-shared-repo-sync.js +6 -9
  110. package/dist/lib/harness/adapter.d.ts +7 -7
  111. package/dist/lib/harness/adapter.js +10 -6
  112. package/dist/lib/harness/adapters/grok.js +8 -3
  113. package/dist/lib/harness/adapters/muse.js +1 -1
  114. package/dist/lib/harness/adapters/opencode.js +12 -3
  115. package/dist/lib/harness-auth-capabilities.d.ts +61 -0
  116. package/dist/lib/harness-auth-capabilities.js +52 -0
  117. package/dist/lib/helper-versions.d.ts +6 -4
  118. package/dist/lib/helper-versions.js +5 -4
  119. package/dist/lib/hosts/credential-transport.d.ts +10 -0
  120. package/dist/lib/hosts/credential-transport.js +37 -0
  121. package/dist/lib/hosts/dispatch.d.ts +7 -0
  122. package/dist/lib/hosts/dispatch.js +13 -6
  123. package/dist/lib/identity/client.d.ts +3 -3
  124. package/dist/lib/identity/client.js +3 -3
  125. package/dist/lib/installations/index.d.ts +1 -1
  126. package/dist/lib/installations/index.js +1 -1
  127. package/dist/lib/installations/migrate.js +9 -0
  128. package/dist/lib/installations/resolve.js +1 -1
  129. package/dist/lib/installations/shims.d.ts +21 -4
  130. package/dist/lib/installations/shims.js +86 -5
  131. package/dist/lib/installations/store.d.ts +43 -0
  132. package/dist/lib/installations/store.js +75 -7
  133. package/dist/lib/installations/versions.js +1 -1
  134. package/dist/lib/menubar/install-menubar.d.ts +3 -3
  135. package/dist/lib/menubar/install-menubar.js +3 -3
  136. package/dist/lib/native-accounts.d.ts +35 -0
  137. package/dist/lib/native-accounts.js +41 -0
  138. package/dist/lib/net-close.d.ts +11 -0
  139. package/dist/lib/net-close.js +25 -0
  140. package/dist/lib/openclaw-keychain.js +27 -1
  141. package/dist/lib/profiles.js +22 -9
  142. package/dist/lib/project-resources.js +1 -1
  143. package/dist/lib/reserved-stores.d.ts +53 -0
  144. package/dist/lib/reserved-stores.js +120 -0
  145. package/dist/lib/secrets-client.d.ts +199 -0
  146. package/dist/lib/secrets-client.js +718 -0
  147. package/dist/lib/secrets-policy.d.ts +235 -0
  148. package/dist/lib/secrets-policy.js +505 -0
  149. package/dist/lib/secrets-types.d.ts +188 -0
  150. package/dist/lib/secrets-types.js +24 -0
  151. package/dist/lib/self-heal/checks/shims.js +5 -3
  152. package/dist/lib/service-manifest.js +3 -4
  153. package/dist/lib/session/db.d.ts +18 -0
  154. package/dist/lib/session/db.js +47 -14
  155. package/dist/lib/session/sync/config.js +2 -2
  156. package/dist/lib/sha256-asset.d.ts +11 -16
  157. package/dist/lib/sha256-asset.js +11 -16
  158. package/dist/lib/share/config.js +14 -5
  159. package/dist/lib/signin-badge.d.ts +26 -0
  160. package/dist/lib/signin-badge.js +42 -0
  161. package/dist/lib/staleness/detectors/workflows.js +3 -105
  162. package/dist/lib/staleness/writers/workflows.js +2 -1
  163. package/dist/lib/state.d.ts +7 -4
  164. package/dist/lib/state.js +18 -7
  165. package/dist/lib/sync-umbrella.js +2 -2
  166. package/dist/lib/teams/agents.js +1 -1
  167. package/dist/lib/types.d.ts +74 -33
  168. package/dist/lib/view-types.d.ts +6 -3
  169. package/dist/lib/workflows-registry.d.ts +71 -0
  170. package/dist/lib/workflows-registry.js +280 -0
  171. package/dist/lib/workflows.d.ts +21 -21
  172. package/dist/lib/workflows.js +12 -327
  173. package/package.json +2 -3
  174. package/scripts/postinstall.js +35 -30
  175. package/dist/commands/secrets-import.d.ts +0 -18
  176. package/dist/commands/secrets-import.js +0 -74
  177. package/dist/commands/secrets-migrate.d.ts +0 -25
  178. package/dist/commands/secrets-migrate.js +0 -334
  179. package/dist/commands/secrets-rotate-passphrase.d.ts +0 -17
  180. package/dist/commands/secrets-rotate-passphrase.js +0 -96
  181. package/dist/commands/secrets-sync.d.ts +0 -11
  182. package/dist/commands/secrets-sync.js +0 -153
  183. package/dist/commands/secrets-vault.d.ts +0 -10
  184. package/dist/commands/secrets-vault.js +0 -130
  185. package/dist/commands/secrets.d.ts +0 -212
  186. package/dist/commands/secrets.js +0 -3030
  187. package/dist/lib/accounts/connect.d.ts +0 -176
  188. package/dist/lib/accounts/connect.js +0 -453
  189. package/dist/lib/daemon/keychain-reap-service.d.ts +0 -17
  190. package/dist/lib/daemon/keychain-reap-service.js +0 -32
  191. package/dist/lib/daemon/secrets-broker-service.d.ts +0 -21
  192. package/dist/lib/daemon/secrets-broker-service.js +0 -51
  193. package/dist/lib/secrets/agent.d.ts +0 -358
  194. package/dist/lib/secrets/agent.js +0 -1291
  195. package/dist/lib/secrets/audit.d.ts +0 -46
  196. package/dist/lib/secrets/audit.js +0 -101
  197. package/dist/lib/secrets/bundles.d.ts +0 -290
  198. package/dist/lib/secrets/bundles.js +0 -1547
  199. package/dist/lib/secrets/download-keychain.d.ts +0 -47
  200. package/dist/lib/secrets/download-keychain.js +0 -70
  201. package/dist/lib/secrets/drivers/rush.d.ts +0 -14
  202. package/dist/lib/secrets/drivers/rush.js +0 -90
  203. package/dist/lib/secrets/fallback.d.ts +0 -48
  204. package/dist/lib/secrets/fallback.js +0 -48
  205. package/dist/lib/secrets/filestore.d.ts +0 -222
  206. package/dist/lib/secrets/filestore.js +0 -1099
  207. package/dist/lib/secrets/headless.d.ts +0 -43
  208. package/dist/lib/secrets/headless.js +0 -56
  209. package/dist/lib/secrets/icloud-import.d.ts +0 -79
  210. package/dist/lib/secrets/icloud-import.js +0 -206
  211. package/dist/lib/secrets/index.d.ts +0 -451
  212. package/dist/lib/secrets/index.js +0 -1568
  213. package/dist/lib/secrets/install-helper.d.ts +0 -72
  214. package/dist/lib/secrets/install-helper.js +0 -245
  215. package/dist/lib/secrets/lease.d.ts +0 -25
  216. package/dist/lib/secrets/lease.js +0 -44
  217. package/dist/lib/secrets/linux.d.ts +0 -77
  218. package/dist/lib/secrets/linux.js +0 -393
  219. package/dist/lib/secrets/list-filter.d.ts +0 -109
  220. package/dist/lib/secrets/list-filter.js +0 -261
  221. package/dist/lib/secrets/mcp.d.ts +0 -93
  222. package/dist/lib/secrets/mcp.js +0 -211
  223. package/dist/lib/secrets/profiles.d.ts +0 -10
  224. package/dist/lib/secrets/profiles.js +0 -13
  225. package/dist/lib/secrets/push.d.ts +0 -133
  226. package/dist/lib/secrets/push.js +0 -273
  227. package/dist/lib/secrets/rc-hygiene.d.ts +0 -63
  228. package/dist/lib/secrets/rc-hygiene.js +0 -143
  229. package/dist/lib/secrets/read-backoff.d.ts +0 -27
  230. package/dist/lib/secrets/read-backoff.js +0 -64
  231. package/dist/lib/secrets/reaper.d.ts +0 -90
  232. package/dist/lib/secrets/reaper.js +0 -243
  233. package/dist/lib/secrets/remote.d.ts +0 -152
  234. package/dist/lib/secrets/remote.js +0 -344
  235. package/dist/lib/secrets/reserved-sync.d.ts +0 -66
  236. package/dist/lib/secrets/reserved-sync.js +0 -147
  237. package/dist/lib/secrets/scope.d.ts +0 -26
  238. package/dist/lib/secrets/scope.js +0 -29
  239. package/dist/lib/secrets/session-store.d.ts +0 -107
  240. package/dist/lib/secrets/session-store.js +0 -342
  241. package/dist/lib/secrets/sync-backend.d.ts +0 -48
  242. package/dist/lib/secrets/sync-backend.js +0 -13
  243. package/dist/lib/secrets/sync-commands.d.ts +0 -21
  244. package/dist/lib/secrets/sync-commands.js +0 -21
  245. package/dist/lib/secrets/sync.d.ts +0 -48
  246. package/dist/lib/secrets/sync.js +0 -237
  247. package/dist/lib/secrets/unlock-hints.d.ts +0 -27
  248. package/dist/lib/secrets/unlock-hints.js +0 -36
  249. package/dist/lib/secrets/usage-db.d.ts +0 -46
  250. package/dist/lib/secrets/usage-db.js +0 -96
  251. package/dist/lib/secrets/vault-age-helper.d.ts +0 -1
  252. package/dist/lib/secrets/vault-age-helper.js +0 -34
  253. package/dist/lib/secrets/vault.d.ts +0 -49
  254. package/dist/lib/secrets/vault.js +0 -399
  255. package/dist/lib/secrets/windows.d.ts +0 -81
  256. package/dist/lib/secrets/windows.js +0 -556
  257. package/scripts/install-helper.js +0 -97
  258. /package/dist/lib/{secrets/sync-passphrase.d.ts → sync-passphrase.d.ts} +0 -0
  259. /package/dist/lib/{secrets/sync-passphrase.js → sync-passphrase.js} +0 -0
@@ -1,8 +1,9 @@
1
1
  import { beforeEach, afterEach } from 'vitest';
2
+ import * as fs from 'fs';
3
+ import * as os from 'os';
2
4
  import * as path from 'path';
3
5
  import { fileURLToPath } from 'url';
4
- import { setKeychainBackendForTest } from '../secrets/index.js';
5
- import { deleteBundle } from '../secrets/bundles.js';
6
+ import { deleteBundleSync, _resetSecretsClientForTest } from '../secrets-client.js';
6
7
  /**
7
8
  * Shared fixture for the daemon.*.test.ts suite slices (RUSH-2819).
8
9
  *
@@ -13,54 +14,35 @@ import { deleteBundle } from '../secrets/bundles.js';
13
14
  * process-spawning / real-daemon integration tests across workers; the
14
15
  * helpers shared by more than one slice live here.
15
16
  */
16
- /** An in-memory KeychainBackend stand-in, so a test never touches the real OS keychain. */
17
- export function makeMemoryBackend() {
18
- const store = new Map();
19
- const backend = {
20
- has: (item) => store.has(item),
21
- get: (item) => {
22
- const v = store.get(item);
23
- if (v === undefined)
24
- throw new Error(`Keychain item '${item}' not found.`);
25
- return v;
26
- },
27
- set: (item, value) => { store.set(item, value); },
28
- delete: (item) => store.delete(item),
29
- list: (prefix) => Array.from(store.keys()).filter((k) => k.startsWith(prefix)),
30
- };
31
- return { backend, store };
32
- }
33
17
  /**
34
- * Registers the keychain-backend swap + AGENTS_SECRETS_NO_AGENT hermeticity
35
- * beforeEach/afterEach every slice of the original daemon.test.ts ran under,
36
- * verbatim. Call once at the top level of each slice file.
18
+ * Isolates every slice against a fresh, empty standalone `secrets` store
19
+ * (PHNX-3989) — an in-process fake keychain can no longer serve reads, since
20
+ * the client always spawns the real standalone, and several slices here also
21
+ * spawn a REAL `__daemon-run` subprocess that must see the SAME isolated
22
+ * store (a fresh `SECRETS_HOME` covers both: `useFreshSecretsHome` sets it as
23
+ * a `process.env` var, which every spawned child inherits by default). Call
24
+ * once at the top level of each slice file.
37
25
  */
38
26
  export function installKeychainHermeticity() {
39
- let restore = null;
40
- let prevNoAgent;
27
+ let home = '';
28
+ let saved;
41
29
  beforeEach(() => {
42
- const m = makeMemoryBackend();
43
- restore = setKeychainBackendForTest(m.backend);
44
- // Hermeticity: readAndResolveBundleEnv consults the running secrets-agent
45
- // (bundles.ts agentGetSync fast-path) BEFORE the injected keychain backend.
46
- // On a dev machine where the agent is live and the real `claude` bundle is
47
- // unlocked, that returns the machine's real CLAUDE_CODE_OAUTH_TOKEN and this
48
- // test reads a live credential instead of the seeded value (CI has no agent,
49
- // so it only bites locally). Disable the agent so the read falls through to
50
- // the in-memory backend above — hermetic regardless of host state.
51
- prevNoAgent = process.env.AGENTS_SECRETS_NO_AGENT;
52
- process.env.AGENTS_SECRETS_NO_AGENT = '1';
30
+ saved = process.env.SECRETS_HOME;
31
+ home = fs.mkdtempSync(path.join(os.tmpdir(), 'agents-daemon-secrets-home-'));
32
+ process.env.SECRETS_HOME = home;
33
+ _resetSecretsClientForTest();
53
34
  });
54
35
  afterEach(() => {
55
36
  try {
56
- deleteBundle('claude');
37
+ deleteBundleSync('claude');
57
38
  }
58
39
  catch { /* not created */ }
59
- setKeychainBackendForTest(restore);
60
- if (prevNoAgent === undefined)
61
- delete process.env.AGENTS_SECRETS_NO_AGENT;
40
+ if (saved === undefined)
41
+ delete process.env.SECRETS_HOME;
62
42
  else
63
- process.env.AGENTS_SECRETS_NO_AGENT = prevNoAgent;
43
+ process.env.SECRETS_HOME = saved;
44
+ _resetSecretsClientForTest();
45
+ fs.rmSync(home, { recursive: true, force: true });
64
46
  });
65
47
  }
66
48
  // The real compiled CLI entry, shared by every slice that drives an actual
@@ -10,7 +10,7 @@
10
10
  * staging an npm install into a sibling directory, `fs.renameSync`/`fs.cpSync`
11
11
  * swaps, `fs.rmSync` cleanup — potentially across several installations in one
12
12
  * tick. Running that inline on the daemon's own event loop would stall every
13
- * other service (secrets broker, browser IPC, the scheduler) for the
13
+ * other service (browser IPC, the scheduler, monitors) for the
14
14
  * duration, the same class of problem `self-update-service.ts` solves for the
15
15
  * CLI's OWN upgrade by installing into a fresh process it then exits into.
16
16
  * Here there is no "exit and let the supervisor restart" option (the daemon
@@ -10,7 +10,7 @@
10
10
  * staging an npm install into a sibling directory, `fs.renameSync`/`fs.cpSync`
11
11
  * swaps, `fs.rmSync` cleanup — potentially across several installations in one
12
12
  * tick. Running that inline on the daemon's own event loop would stall every
13
- * other service (secrets broker, browser IPC, the scheduler) for the
13
+ * other service (browser IPC, the scheduler, monitors) for the
14
14
  * duration, the same class of problem `self-update-service.ts` solves for the
15
15
  * CLI's OWN upgrade by installing into a fresh process it then exits into.
16
16
  * Here there is no "exit and let the supervisor restart" option (the daemon
@@ -18,7 +18,7 @@ import { readMeta } from '../state.js';
18
18
  import type { AgentId } from '../types.js';
19
19
  import { type FallbackEntry } from '../exec.js';
20
20
  import type { LoopDeps } from '../loop.js';
21
- import { resolveRunVersion, resolveAccountVersion, type RotateResult } from '../accounting/rotate.js';
21
+ import { resolveRunVersion, resolveAccountCandidate, resolveAccountVersion, type RotateResult } from '../accounting/rotate.js';
22
22
  /** Result of a completed job execution, including metadata and optional report. */
23
23
  export interface RunResult {
24
24
  meta: RunMeta;
@@ -110,7 +110,11 @@ export interface RoutineLaunchPlan {
110
110
  rotation: RotateResult | null;
111
111
  /** True when `config.version` pinned the target (no rotation). */
112
112
  pinned: boolean;
113
- /** False when `account` names a harness-native login rather than a durable credential. */
113
+ /**
114
+ * When true, `buildJobCommand` appends `--account`. Native slot accounts
115
+ * share one managed-install version, so the pin is forwarded rather than
116
+ * treated as implied by the version. False only when the caller opts out.
117
+ */
114
118
  forwardAccount?: boolean;
115
119
  }
116
120
  /** Claude's own local auth check; unlike account metadata it cannot mistake identity for a usable login. */
@@ -127,6 +131,7 @@ export declare function claudeVersionIsAuthenticated(version: string): boolean;
127
131
  export declare function resolveRoutineLaunch(config: JobConfig, cwd?: string, deps?: {
128
132
  resolveRunVersion?: typeof resolveRunVersion;
129
133
  resolveAccountVersion?: typeof resolveAccountVersion;
134
+ resolveAccountCandidate?: typeof resolveAccountCandidate;
130
135
  findCredentialAccount?: (name: string) => boolean;
131
136
  readMeta?: typeof readMeta;
132
137
  resolveCredentialAccount?: (name: string, host: AgentId) => {
@@ -181,7 +186,14 @@ export declare function buildHostDispatchOptions(config: JobConfig, ctx: {
181
186
  runDir: string;
182
187
  detached: boolean;
183
188
  }): import('../hosts/run-target.js').HostPromptRun;
184
- export declare function dispatchesViaAgentsRun(config: Pick<JobConfig, 'workflow' | 'resume' | 'agent'>): boolean;
189
+ export declare function dispatchesViaAgentsRun(config: Pick<JobConfig, 'workflow' | 'resume' | 'agent' | 'account'>): boolean;
190
+ /**
191
+ * Inject a provider account's env into a routine spawn. Native accounts do not
192
+ * belong here — they re-enter `agents run <agent>#<name>` so T5 slot resolution
193
+ * picks the HOME. A provider-pinned routine stays on this path so the durable
194
+ * credential is still in the child env (PHNX-3940 T5 seam / T7).
195
+ */
196
+ export declare function mergeRoutineProviderEnv(env: Record<string, string>, config: Pick<JobConfig, 'account' | 'agent' | 'workflow' | 'resume'>, agent: AgentId): Record<string, string>;
185
197
  /**
186
198
  * Merge sandbox/base env with the canonical per-version exec env
187
199
  * (CLAUDE_CONFIG_DIR / CODEX_HOME / …) so routines share account isolation
@@ -39,10 +39,11 @@ import { logAndContinueOnLockCompromised } from '../lock-compromise.js';
39
39
  import { walkForFiles } from '../fs-walk.js';
40
40
  import { getBinaryPath, isVersionInstalled, resolveVersion, getVersionHomePath } from '../installations/versions.js';
41
41
  import { resolveClaudeSetupToken } from '../claude-account-token.js';
42
- import { getConfiguredRunStrategy, resolveRunVersion, resolveAccountVersion, rotationFailoverChain, readinessFromCandidate, formatNoHealthyAccountError, formatNoVerifiedUsageError, } from '../accounting/rotate.js';
42
+ import { getConfiguredRunStrategy, resolveRunVersion, resolveAccountCandidate, rotationFailoverChain, readinessFromCandidate, formatNoHealthyAccountError, formatNoVerifiedUsageError, } from '../accounting/rotate.js';
43
43
  import { isHeadedDeviceRole, selfConfiguredDeviceRole } from '../device-config.js';
44
44
  import { isSelfUpdatingAgent, ROUTINE_AGENT_IDS, isAgentHardDeprecated, hardDeprecationError } from '../agents.js';
45
45
  import { isCustomHarnessName, readProfile } from '../profiles.js';
46
+ import { findAccount, findUnifiedAccount, resolveAccountSelection, resolveCredentialAccount } from '../account-registry.js';
46
47
  export class RoutineAlreadyRunningError extends Error {
47
48
  constructor(jobName, runId) {
48
49
  super(`Routine '${jobName}' already has a running execution (${runId})`);
@@ -571,6 +572,19 @@ export function buildJobCommand(config, resolvedPrompt, forwardAccount = true) {
571
572
  cmd.push('--account', config.account);
572
573
  return cmd;
573
574
  }
575
+ // Native slot accounts share one managed binary version, so the baked
576
+ // harness argv cannot identify which login to spawn. Dispatch through
577
+ // `agents run <agent>#<name>` (the same seam as resume/custom) so T5
578
+ // spawn-time resolution selects the slot. `--account` is an agents-cli
579
+ // flag; the harness binary would ignore or reject it.
580
+ // Provider/durable-pinned accounts stay on the harness argv + env-injection
581
+ // path: `#name` is a native selector and would not inject their bundle.
582
+ if (config.account && forwardAccount && !findAccount(config.account)) {
583
+ const spec = config.version
584
+ ? `${agent}@${config.version}#${config.account}`
585
+ : `${agent}#${config.account}`;
586
+ return ['agents', 'run', spec, resolvedPrompt, '--mode', config.mode];
587
+ }
574
588
  const template = bakeRoutineArgv(agent);
575
589
  if (!template) {
576
590
  // A name outside both tables was either never valid or was a custom
@@ -934,7 +948,11 @@ export async function resolveRoutineLaunch(config, cwd = process.cwd(), deps = {
934
948
  throw new Error(`Routine '${config.name}' account '${config.account}' is a ${unified.agent} login and cannot authenticate ${agent}.`);
935
949
  }
936
950
  const identity = unified?.kind === 'native' ? unified.identityKey : config.account;
937
- const accountVersion = await (deps.resolveAccountVersion ?? resolveAccountVersion)(agent, identity);
951
+ // Prefer the full candidate so a slot-sourced native account (shared
952
+ // managed-install `version`) still carries its name for `--account`.
953
+ const accountVersion = deps.resolveAccountVersion
954
+ ? await deps.resolveAccountVersion(agent, identity)
955
+ : (await (deps.resolveAccountCandidate ?? resolveAccountCandidate)(agent, identity))?.version ?? null;
938
956
  if (accountVersion) {
939
957
  if (config.version && config.version !== accountVersion) {
940
958
  throw new Error(`Routine '${config.name}' account '${config.account}' is signed in at ${agent}@${accountVersion}, not pinned ${agent}@${config.version}.`);
@@ -943,7 +961,10 @@ export async function resolveRoutineLaunch(config, cwd = process.cwd(), deps = {
943
961
  chain: [{ agent, version: config.version ?? accountVersion }],
944
962
  rotation: null,
945
963
  pinned: true,
946
- forwardAccount: false,
964
+ // Post-T5 every native slot on a harness shares the managed binary
965
+ // version, so the version pin is no longer enough — forward `--account`
966
+ // (name or `#name`) so spawn resolves the selected slot.
967
+ forwardAccount: true,
947
968
  };
948
969
  }
949
970
  throw new Error(`Routine '${config.name}' account '${config.account}' is not signed in for ${agent}; refusing to rotate to another account.`);
@@ -1144,7 +1165,29 @@ export function buildHostDispatchOptions(config, ctx) {
1144
1165
  };
1145
1166
  }
1146
1167
  export function dispatchesViaAgentsRun(config) {
1147
- return Boolean(config.workflow || config.resume || (config.agent && isCustomHarnessName(config.agent)));
1168
+ return Boolean(config.workflow
1169
+ || config.resume
1170
+ || (config.agent && isCustomHarnessName(config.agent))
1171
+ || (config.account && config.agent && !findAccount(config.account)));
1172
+ }
1173
+ /**
1174
+ * Inject a provider account's env into a routine spawn. Native accounts do not
1175
+ * belong here — they re-enter `agents run <agent>#<name>` so T5 slot resolution
1176
+ * picks the HOME. A provider-pinned routine stays on this path so the durable
1177
+ * credential is still in the child env (PHNX-3940 T5 seam / T7).
1178
+ */
1179
+ export function mergeRoutineProviderEnv(env, config, agent) {
1180
+ if (dispatchesViaAgentsRun({ ...config, agent }))
1181
+ return env;
1182
+ const meta = readMeta();
1183
+ const selected = resolveAccountSelection(config.account, agent, meta);
1184
+ if (!selected)
1185
+ return env;
1186
+ const unified = findUnifiedAccount(selected.id, meta, undefined, agent);
1187
+ if (unified?.kind !== 'provider')
1188
+ return env;
1189
+ Object.assign(env, resolveCredentialAccount(unified.name, agent).env);
1190
+ return env;
1148
1191
  }
1149
1192
  /**
1150
1193
  * Merge sandbox/base env with the canonical per-version exec env
@@ -1390,18 +1433,7 @@ async function executeJobPlaced(config, deps, attempt) {
1390
1433
  : harnessName
1391
1434
  ? readProfile(harnessName).host.agent
1392
1435
  : config.agent;
1393
- if (!dispatchesViaAgentsRun(config)) {
1394
- const { findUnifiedAccount, resolveAccountSelection, resolveCredentialAccount } = await import('../account-registry.js');
1395
- const meta = readMeta();
1396
- const selectedAccount = resolveAccountSelection(config.account, effectiveAgent, meta)?.id;
1397
- if (selectedAccount) {
1398
- // Only a provider account injects env; a native account (explicit or via a
1399
- // device-scoped binding) is read from the harness home and forwards nothing.
1400
- const unified = findUnifiedAccount(selectedAccount, meta);
1401
- if (unified?.kind === 'provider')
1402
- Object.assign(baseEnv, resolveCredentialAccount(selectedAccount, effectiveAgent).env);
1403
- }
1404
- }
1436
+ mergeRoutineProviderEnv(baseEnv, config, effectiveAgent);
1405
1437
  // RUSH-2860: if this host holds gh auth, the sandbox child MUST see it.
1406
1438
  if (useSandbox)
1407
1439
  assertSandboxForwardsHostGhAuth(baseEnv);
@@ -1912,14 +1944,7 @@ async function executeJobDetachedClaimed(config, attempt, hooks) {
1912
1944
  const baseEnv = injectRoutineActor(useSandbox
1913
1945
  ? buildSpawnEnv(overlayHome, config.env)
1914
1946
  : { ...process.env }, config);
1915
- if (!dispatchesViaAgentsRun(config)) {
1916
- const { findAccount, resolveAccountSelection, resolveCredentialAccount } = await import('../account-registry.js');
1917
- const selectedAccount = config.account && !findAccount(config.account)
1918
- ? undefined
1919
- : resolveAccountSelection(config.account, config.agent, readMeta())?.id;
1920
- if (selectedAccount)
1921
- Object.assign(baseEnv, resolveCredentialAccount(selectedAccount, config.agent).env);
1922
- }
1947
+ mergeRoutineProviderEnv(baseEnv, config, config.agent);
1923
1948
  const spawnEnv = dispatchesViaAgentsRun(config)
1924
1949
  ? (() => {
1925
1950
  const e = { ...baseEnv };
@@ -1,5 +1,4 @@
1
1
  /** Stable subsystem identifiers shared by the daemon (writer) and `agents daemon` (reader). */
2
- export declare const SUBSYSTEM_SECRETS_BROKER = "secrets-broker";
3
2
  export declare const SUBSYSTEM_BROWSER_IPC = "browser-ipc";
4
3
  /**
5
4
  * Daemon startup itself (RUSH-2418). Unlike the two above, this record is
@@ -12,7 +11,7 @@ export declare const SUBSYSTEM_BROWSER_IPC = "browser-ipc";
12
11
  export declare const SUBSYSTEM_DAEMON_START = "daemon-start";
13
12
  /** One subsystem's health as of the last time it reported in. */
14
13
  export interface SubsystemHealth {
15
- /** Stable identifier, e.g. 'secrets-broker', 'browser-ipc'. */
14
+ /** Stable identifier, e.g. 'browser-ipc', 'monitors'. */
16
15
  subsystem: string;
17
16
  /** Most recent error message, or null if it has never failed. */
18
17
  lastError: string | null;
@@ -4,17 +4,19 @@
4
4
  * Today a subsystem failure inside `runDaemon()` (daemon.ts) is a single
5
5
  * `log('ERROR', ...)` line that scrolls out of the log file and is never
6
6
  * surfaced anywhere else — `agents daemon status` has no way to answer "is the
7
- * secrets broker actually healthy right now?" beyond "the daemon process is
8
- * alive". This module gives every subsystem a small persisted record —
7
+ * browser IPC server actually healthy right now?" beyond "the daemon process
8
+ * is alive". This module gives every subsystem a small persisted record —
9
9
  * {@link SubsystemHealth} — so `agents daemon status` / `agents daemon
10
10
  * services` can report health, not just liveness (RUSH-2354).
11
11
  *
12
12
  * Scheduled routines get this for free once migrated onto `agents routines`
13
13
  * (their run history already carries success/failure — `agents routines
14
14
  * stats`). This module exists for the subsystems that predate routines and have
15
- * no run history of their own: the secrets broker and the browser IPC server,
16
- * plus the daemon's own startup (`SUBSYSTEM_DAEMON_START`, RUSH-2418) — which
17
- * is the one record that also GATES behaviour rather than only reporting it.
15
+ * no run history of their own: the browser IPC server, plus the daemon's own
16
+ * startup (`SUBSYSTEM_DAEMON_START`, RUSH-2418) — which is the one record that
17
+ * also GATES behaviour rather than only reporting it. The secrets broker moved
18
+ * with the standalone `secrets` engine (PHNX-3989 OWN-1) — this daemon no
19
+ * longer hosts or supervises it, so it carries no health record here.
18
20
  *
19
21
  * File-backed (one JSON object keyed by subsystem name) rather than in-memory
20
22
  * because `agents daemon status` runs as a SEPARATE process from the daemon —
@@ -26,7 +28,6 @@ import { getDaemonDir } from './state.js';
26
28
  import { atomicWriteFileSync, ensureLockTarget, withFileLock } from './fs-atomic.js';
27
29
  const HEALTH_FILE = 'health.json';
28
30
  /** Stable subsystem identifiers shared by the daemon (writer) and `agents daemon` (reader). */
29
- export const SUBSYSTEM_SECRETS_BROKER = 'secrets-broker';
30
31
  export const SUBSYSTEM_BROWSER_IPC = 'browser-ipc';
31
32
  /**
32
33
  * Daemon startup itself (RUSH-2418). Unlike the two above, this record is
@@ -7,7 +7,7 @@
7
7
  * enabled without pulling in the whole daemon lifecycle.
8
8
  */
9
9
  /** Every service the daemon can host. IDs are kebab-case and stable. */
10
- export type DaemonServiceId = 'secrets-broker' | 'scheduler' | 'catchup' | 'monitors' | 'browser-ipc' | 'webhook-receiver' | 'self-heal' | 'self-update' | 'keychain-reap' | 'account-state' | 'account-auth' | 'watchdog' | 'device-probe' | 'state-dir-check' | 'session-index' | 'auth-sync' | 'usage-sync' | 'daemon-heartbeat' | 'tmux-reap' | 'browser-task-reap' | 'session-state' | 'session-summarizer' | 'harness-update';
10
+ export type DaemonServiceId = 'scheduler' | 'catchup' | 'monitors' | 'browser-ipc' | 'webhook-receiver' | 'self-heal' | 'self-update' | 'account-state' | 'account-auth' | 'watchdog' | 'device-probe' | 'state-dir-check' | 'session-index' | 'auth-sync' | 'usage-sync' | 'daemon-heartbeat' | 'tmux-reap' | 'browser-task-reap' | 'session-state' | 'session-summarizer' | 'harness-update';
11
11
  /** Human-readable metadata for each service. */
12
12
  export interface DaemonServiceDef {
13
13
  id: DaemonServiceId;
@@ -12,11 +12,6 @@ import * as yaml from 'yaml';
12
12
  import { getDaemonConfigDir } from './state.js';
13
13
  import { atomicWriteFileSync } from './fs-atomic.js';
14
14
  export const DAEMON_SERVICES = [
15
- {
16
- id: 'secrets-broker',
17
- title: 'Secrets broker',
18
- description: 'Hosts the keychain-backed secrets broker socket so unlocked bundles stay warm across agent runs.',
19
- },
20
15
  {
21
16
  id: 'scheduler',
22
17
  title: 'Routine scheduler',
@@ -40,7 +35,7 @@ export const DAEMON_SERVICES = [
40
35
  {
41
36
  id: 'webhook-receiver',
42
37
  title: 'Webhook receiver',
43
- description: 'Hosts signed GitHub/Linear webhook ingress declared in daemon/webhooks.yaml, drawing signing secrets from the broker; binds nothing when no receivers are declared.',
38
+ description: 'Hosts signed GitHub/Linear webhook ingress declared in daemon/webhooks.yaml, drawing signing secrets from the standalone secrets CLI; binds nothing when no receivers are declared.',
44
39
  },
45
40
  {
46
41
  id: 'self-heal',
@@ -52,11 +47,6 @@ export const DAEMON_SERVICES = [
52
47
  title: 'Self-update',
53
48
  description: 'Checks npm for a newer agents-cli, installs + verifies it, then exits so the OS supervisor relaunches onto the new code (PHNX-3695).',
54
49
  },
55
- {
56
- id: 'keychain-reap',
57
- title: 'Keychain reap',
58
- description: 'Reaps orphaned keychain helpers and stuck agents processes.',
59
- },
60
50
  {
61
51
  id: 'account-state',
62
52
  title: 'Account usage refresh',
@@ -4,9 +4,10 @@
4
4
  * `~/.agents/daemon/webhooks.yaml` declares which signed webhook receivers THIS
5
5
  * box hosts — bundle, local port, rate limit, and an optional public Tailscale
6
6
  * Funnel port. The daemon's `webhook-receiver` service reads it and binds one
7
- * receiver per entry, drawing each receiver's signing secret from the broker /
8
- * machine-local file store (no `AGENTS_SECRETS_PASSPHRASE`, no `nohup`). An
9
- * absent or empty file hosts nothing, so an unconfigured box binds nothing.
7
+ * receiver per entry, drawing each receiver's signing secret through the
8
+ * standalone `secrets` CLI (an agentOnly read via secrets-client.ts — no
9
+ * `AGENTS_SECRETS_PASSPHRASE`, no `nohup`). An absent or empty file hosts
10
+ * nothing, so an unconfigured box binds nothing.
10
11
  *
11
12
  * This is per-box operational state (a public receiver runs on exactly one box),
12
13
  * so it is deliberately NOT part of the fleet-synced device config — it mirrors
@@ -87,15 +88,15 @@ type Logger = (level: string, message: string) => void;
87
88
  * `server.listen()` surfaces EADDRINUSE as an `'error'` event, so a `try/catch`
88
89
  * around the start call never sees it and the event reaches the process-level
89
90
  * `uncaughtException` handler (`index.ts`), which exits 1 for the supervisor to
90
- * restart — a crash loop that would take the secrets broker, scheduler,
91
- * monitors, browser IPC, and self-heal down with it. `waitForListening` is what
92
- * turns that into one skipped receiver.
91
+ * restart — a crash loop that would take the scheduler, monitors, browser IPC,
92
+ * and self-heal down with it. `waitForListening` is what turns that into one
93
+ * skipped receiver.
93
94
  */
94
95
  export declare function startHostedWebhookReceivers(opts: {
95
96
  log: Logger;
96
97
  /**
97
98
  * How a receiver's signing secrets are resolved. Defaults to
98
- * `resolveReceiverSecrets` (the real broker read). Injectable for the same
99
+ * `resolveReceiverSecrets` (the real secrets-client read). Injectable for the same
99
100
  * reason `FireWebhookOptions.dispatch` is — so a test can exercise the bind
100
101
  * path against real sockets without a machine-local secrets bundle.
101
102
  */
@@ -4,9 +4,10 @@
4
4
  * `~/.agents/daemon/webhooks.yaml` declares which signed webhook receivers THIS
5
5
  * box hosts — bundle, local port, rate limit, and an optional public Tailscale
6
6
  * Funnel port. The daemon's `webhook-receiver` service reads it and binds one
7
- * receiver per entry, drawing each receiver's signing secret from the broker /
8
- * machine-local file store (no `AGENTS_SECRETS_PASSPHRASE`, no `nohup`). An
9
- * absent or empty file hosts nothing, so an unconfigured box binds nothing.
7
+ * receiver per entry, drawing each receiver's signing secret through the
8
+ * standalone `secrets` CLI (an agentOnly read via secrets-client.ts — no
9
+ * `AGENTS_SECRETS_PASSPHRASE`, no `nohup`). An absent or empty file hosts
10
+ * nothing, so an unconfigured box binds nothing.
10
11
  *
11
12
  * This is per-box operational state (a public receiver runs on exactly one box),
12
13
  * so it is deliberately NOT part of the fleet-synced device config — it mirrors
@@ -18,8 +19,8 @@ import * as yaml from 'yaml';
18
19
  import { exec } from 'child_process';
19
20
  import { getDaemonConfigDir, getRuntimeStateDir } from './state.js';
20
21
  import { atomicWriteFileSync } from './fs-atomic.js';
21
- import { closeServerBounded } from './secrets/agent.js';
22
- import { readAndResolveBundleEnv } from './secrets/bundles.js';
22
+ import { closeServerBounded } from './net-close.js';
23
+ import { readAndResolveBundleEnvSync } from './secrets-client.js';
23
24
  import { startWebhookServer, createFileDeliveryStore, waitForListening } from './triggers/webhook.js';
24
25
  import { buildFunnelUpCommand, FUNNEL_PORTS } from './funnel.js';
25
26
  export const DEFAULT_WEBHOOK_PORT = 8787;
@@ -107,7 +108,7 @@ export function removeHostedReceiver(port) {
107
108
  * verifiable signature. Throws when neither webhook secret is present.
108
109
  */
109
110
  export function resolveReceiverSecrets(bundle) {
110
- const { env } = readAndResolveBundleEnv(bundle, { caller: 'daemon webhook-receiver', agentOnly: true });
111
+ const { env } = readAndResolveBundleEnvSync(bundle, { caller: 'daemon webhook-receiver', agentOnly: true });
111
112
  const secrets = {};
112
113
  if (env.GITHUB_WEBHOOK_SECRET)
113
114
  secrets.github = env.GITHUB_WEBHOOK_SECRET;
@@ -150,9 +151,9 @@ function reconcileFunnel(publicPort, localPort, log) {
150
151
  * `server.listen()` surfaces EADDRINUSE as an `'error'` event, so a `try/catch`
151
152
  * around the start call never sees it and the event reaches the process-level
152
153
  * `uncaughtException` handler (`index.ts`), which exits 1 for the supervisor to
153
- * restart — a crash loop that would take the secrets broker, scheduler,
154
- * monitors, browser IPC, and self-heal down with it. `waitForListening` is what
155
- * turns that into one skipped receiver.
154
+ * restart — a crash loop that would take the scheduler, monitors, browser IPC,
155
+ * and self-heal down with it. `waitForListening` is what turns that into one
156
+ * skipped receiver.
156
157
  */
157
158
  export async function startHostedWebhookReceivers(opts) {
158
159
  const { log } = opts;
@@ -182,7 +182,7 @@ export const CONFIG_KEYS = [
182
182
  visibility: 'machine',
183
183
  type: 'bool',
184
184
  defaultValue: true,
185
- description: 'Whether the daemon may run on this device at all (secrets broker, browser IPC, watchdog, and the ' +
185
+ description: 'Whether the daemon may run on this device at all (browser IPC, watchdog, and the ' +
186
186
  'routines scheduler). Disabling is the top-level kill switch: nothing auto-starts the daemon while it ' +
187
187
  'is set, including `routines add`/`routines start`/`routines catchup`/`monitors add`/webhook triggers. ' +
188
188
  '`agents daemon start` still starts it explicitly.',
@@ -1,7 +1,7 @@
1
1
  import type { AgentId } from '../types.js';
2
2
  import type { DuplicateVersionHook } from '../hooks/install.js';
3
3
  import type { AgentsBinaryShadow } from '../binary-shadow.js';
4
- import type { RcSecretFinding } from '../secrets/rc-hygiene.js';
4
+ import type { RcSecretFinding } from '../secrets-types.js';
5
5
  import type { OwnerSinkStatus } from '../channels/owner-sink.js';
6
6
  import { type WindowsSshEnrollmentAudit } from './windows-ssh-enrollment.js';
7
7
  import type { SyncStatusRow, OrphanRow } from '../drift.js';
@@ -1,6 +1,7 @@
1
1
  import { type AccountInfo } from '../agents.js';
2
2
  import type { AgentId } from '../types.js';
3
3
  import { type UsageSnapshot } from '../accounting/usage.js';
4
+ import { type AccountVerdict } from '../signin-badge.js';
4
5
  /** An account's usage headroom rolled into one glanceable summary. */
5
6
  export interface QuotaSummary {
6
7
  /**
@@ -33,6 +34,8 @@ export interface HarnessRow {
33
34
  ready: boolean;
34
35
  /** When `!ready`, a short reason ("signed out" / "rate-limited"). */
35
36
  reason?: string;
37
+ verdict: AccountVerdict;
38
+ fix: string | null;
36
39
  }
37
40
  /** One host's rows, or the reason it produced none. */
38
41
  export interface HostHarnessResult {
@@ -65,6 +68,25 @@ export interface AccountGroup {
65
68
  * account) and takes the highest utilization across those windows. Pure.
66
69
  */
67
70
  export declare function summarizeQuota(snapshot: UsageSnapshot | null | undefined, unavailableReason?: string | null, accountStatus?: AccountInfo['usageStatus']): QuotaSummary;
71
+ /**
72
+ * Scope/permission failures on a worker mean the usage endpoint cannot be read.
73
+ * They are not evidence of exhausted credits, even when stale account metadata
74
+ * previously carried that coarse status.
75
+ */
76
+ export declare function summarizeObservedQuota(snapshot: UsageSnapshot | null | undefined, error: string | null | undefined, accountStatus?: AccountInfo['usageStatus']): {
77
+ quota: QuotaSummary;
78
+ unverified: boolean;
79
+ };
80
+ /**
81
+ * A scope/permission failure on the usage endpoint is not a credit claim.
82
+ * Strip utilization and, only when auth still looks healthy, surface
83
+ * `unverified` instead of `live`/`rate_limited`. A real auth failure
84
+ * (expired/revoked/missing) stays the auth failure.
85
+ */
86
+ export declare function applyUsageHonesty(verdict: AccountVerdict, usage: QuotaSummary | null): {
87
+ verdict: AccountVerdict;
88
+ usage: QuotaSummary | null;
89
+ };
68
90
  /**
69
91
  * "Ready" = signed in AND not rate-limited. A missing quota snapshot does NOT
70
92
  * block readiness: the account is signed in and usable, we just have no live
@@ -104,3 +126,20 @@ export declare function renderHarnessMatrix(results: HostHarnessResult[]): strin
104
126
  * account (installs sharing it collapsed), showing which harnesses use it. Pure.
105
127
  */
106
128
  export declare function renderAccountsMatrix(results: HostHarnessResult[]): string[];
129
+ export interface AccountFleetMatrixRow {
130
+ harness: AgentId;
131
+ name: string | null;
132
+ identityLabel: string;
133
+ devices: Array<{
134
+ device: string;
135
+ verdict: AccountVerdict;
136
+ }>;
137
+ }
138
+ /**
139
+ * Account × device matrix used by `agents accounts list --fleet`.
140
+ * Rows are logical accounts, not installations. Columns come from the
141
+ * daemon-state observations already joined onto each account row.
142
+ */
143
+ export declare function renderAccountFleetMatrix(rows: AccountFleetMatrixRow[], opts?: {
144
+ uncoveredDevices?: string[];
145
+ }): string[];