@phnx-labs/agents-cli 1.22.84 → 1.22.85

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 (257) hide show
  1. package/CHANGELOG.md +57 -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/apply.js +1 -1
  9. package/dist/commands/auth-mint.d.ts +7 -3
  10. package/dist/commands/auth-mint.js +21 -9
  11. package/dist/commands/auth.js +2 -3
  12. package/dist/commands/browser.js +10 -10
  13. package/dist/commands/daemon.d.ts +7 -3
  14. package/dist/commands/daemon.js +40 -25
  15. package/dist/commands/doctor.js +28 -2
  16. package/dist/commands/exec.js +87 -39
  17. package/dist/commands/fleet-capture.js +21 -2
  18. package/dist/commands/harness-wizard.js +2 -2
  19. package/dist/commands/lease.js +7 -12
  20. package/dist/commands/profiles.d.ts +2 -2
  21. package/dist/commands/profiles.js +20 -13
  22. package/dist/commands/repo.js +3 -3
  23. package/dist/commands/run-account-picker.d.ts +3 -3
  24. package/dist/commands/run-account-picker.js +29 -30
  25. package/dist/commands/secrets-passthrough.d.ts +20 -0
  26. package/dist/commands/secrets-passthrough.js +43 -0
  27. package/dist/commands/setup-accounts.d.ts +3 -2
  28. package/dist/commands/setup-accounts.js +4 -4
  29. package/dist/commands/setup-secrets.d.ts +16 -21
  30. package/dist/commands/setup-secrets.js +43 -224
  31. package/dist/commands/ssh.js +2 -2
  32. package/dist/commands/sync.js +1 -1
  33. package/dist/commands/update.d.ts +10 -0
  34. package/dist/commands/update.js +43 -40
  35. package/dist/commands/versions.d.ts +11 -11
  36. package/dist/commands/versions.js +131 -83
  37. package/dist/commands/view.d.ts +1 -9
  38. package/dist/commands/view.js +41 -55
  39. package/dist/commands/webhook.js +5 -5
  40. package/dist/commands/workflows.js +2 -1
  41. package/dist/index.d.ts +12 -12
  42. package/dist/index.js +13 -42
  43. package/dist/lib/account-capabilities.d.ts +1 -1
  44. package/dist/lib/account-capabilities.js +1 -1
  45. package/dist/lib/account-catalog.d.ts +121 -6
  46. package/dist/lib/account-catalog.js +421 -7
  47. package/dist/lib/account-registry.d.ts +8 -30
  48. package/dist/lib/account-registry.js +36 -79
  49. package/dist/lib/account-schema.d.ts +1 -1
  50. package/dist/lib/account-schema.js +1 -1
  51. package/dist/lib/accounting/account-pool-collect.js +16 -4
  52. package/dist/lib/accounting/rotate.d.ts +20 -7
  53. package/dist/lib/accounting/rotate.js +89 -13
  54. package/dist/lib/accounting/usage.js +11 -16
  55. package/dist/lib/accounts/add.d.ts +138 -0
  56. package/dist/lib/accounts/add.js +651 -0
  57. package/dist/lib/accounts/migrate.d.ts +117 -0
  58. package/dist/lib/accounts/migrate.js +536 -0
  59. package/dist/lib/accounts/slots.d.ts +13 -0
  60. package/dist/lib/accounts/slots.js +100 -0
  61. package/dist/lib/agent-spec/agents.d.ts +16 -0
  62. package/dist/lib/agent-spec/agents.js +32 -4
  63. package/dist/lib/app-bundle-install.js +5 -4
  64. package/dist/lib/auth-health.d.ts +3 -0
  65. package/dist/lib/auth-health.js +11 -0
  66. package/dist/lib/auth-mint.d.ts +52 -16
  67. package/dist/lib/auth-mint.js +117 -39
  68. package/dist/lib/browser/chrome.d.ts +1 -1
  69. package/dist/lib/browser/chrome.js +5 -5
  70. package/dist/lib/byok-usage.js +3 -3
  71. package/dist/lib/claude-account-token.d.ts +47 -3
  72. package/dist/lib/claude-account-token.js +172 -50
  73. package/dist/lib/cloud/antigravity.js +4 -4
  74. package/dist/lib/cloud/cursor.js +4 -4
  75. package/dist/lib/crabbox/cli.d.ts +1 -1
  76. package/dist/lib/crabbox/cli.js +6 -6
  77. package/dist/lib/crabbox/runtimes.d.ts +3 -3
  78. package/dist/lib/crabbox/runtimes.js +5 -5
  79. package/dist/lib/daemon/account-state-daemon-service.d.ts +37 -1
  80. package/dist/lib/daemon/account-state-daemon-service.js +199 -3
  81. package/dist/lib/daemon/auth-sync-service.js +28 -1
  82. package/dist/lib/daemon/daemon.d.ts +6 -16
  83. package/dist/lib/daemon/daemon.js +52 -97
  84. package/dist/lib/daemon/daemon.test-fixture.d.ts +7 -9
  85. package/dist/lib/daemon/daemon.test-fixture.js +22 -40
  86. package/dist/lib/daemon/harness-update-service.d.ts +1 -1
  87. package/dist/lib/daemon/harness-update-service.js +1 -1
  88. package/dist/lib/daemon/runner.d.ts +15 -3
  89. package/dist/lib/daemon/runner.js +49 -24
  90. package/dist/lib/daemon-health.d.ts +1 -2
  91. package/dist/lib/daemon-health.js +7 -6
  92. package/dist/lib/daemon-services.d.ts +1 -1
  93. package/dist/lib/daemon-services.js +1 -11
  94. package/dist/lib/daemon-webhooks.d.ts +8 -7
  95. package/dist/lib/daemon-webhooks.js +10 -9
  96. package/dist/lib/device-config.js +1 -1
  97. package/dist/lib/devices/doctor-findings.d.ts +1 -1
  98. package/dist/lib/devices/harness-inventory.d.ts +39 -0
  99. package/dist/lib/devices/harness-inventory.js +126 -4
  100. package/dist/lib/doctor-diff.js +2 -1
  101. package/dist/lib/exec-account-home.d.ts +38 -0
  102. package/dist/lib/exec-account-home.js +164 -0
  103. package/dist/lib/exec.d.ts +30 -0
  104. package/dist/lib/exec.js +101 -31
  105. package/dist/lib/fleet/apply.d.ts +2 -2
  106. package/dist/lib/fleet/apply.js +4 -4
  107. package/dist/lib/fleet/auth-sync.js +4 -3
  108. package/dist/lib/fleet-shared-repo-sync.js +6 -9
  109. package/dist/lib/harness/adapter.d.ts +7 -7
  110. package/dist/lib/harness/adapter.js +10 -6
  111. package/dist/lib/harness/adapters/grok.js +8 -3
  112. package/dist/lib/harness/adapters/muse.js +1 -1
  113. package/dist/lib/harness/adapters/opencode.js +12 -3
  114. package/dist/lib/harness-auth-capabilities.d.ts +61 -0
  115. package/dist/lib/harness-auth-capabilities.js +52 -0
  116. package/dist/lib/helper-versions.d.ts +6 -4
  117. package/dist/lib/helper-versions.js +5 -4
  118. package/dist/lib/hosts/credential-transport.d.ts +10 -0
  119. package/dist/lib/hosts/credential-transport.js +37 -0
  120. package/dist/lib/hosts/dispatch.d.ts +7 -0
  121. package/dist/lib/hosts/dispatch.js +13 -6
  122. package/dist/lib/identity/client.d.ts +3 -3
  123. package/dist/lib/identity/client.js +3 -3
  124. package/dist/lib/installations/index.d.ts +1 -1
  125. package/dist/lib/installations/index.js +1 -1
  126. package/dist/lib/installations/migrate.js +9 -0
  127. package/dist/lib/installations/resolve.js +1 -1
  128. package/dist/lib/installations/shims.d.ts +15 -0
  129. package/dist/lib/installations/shims.js +69 -0
  130. package/dist/lib/installations/store.d.ts +43 -0
  131. package/dist/lib/installations/store.js +75 -7
  132. package/dist/lib/installations/versions.js +1 -1
  133. package/dist/lib/menubar/install-menubar.d.ts +3 -3
  134. package/dist/lib/menubar/install-menubar.js +3 -3
  135. package/dist/lib/native-accounts.d.ts +35 -0
  136. package/dist/lib/native-accounts.js +41 -0
  137. package/dist/lib/net-close.d.ts +11 -0
  138. package/dist/lib/net-close.js +25 -0
  139. package/dist/lib/openclaw-keychain.js +27 -1
  140. package/dist/lib/profiles.js +22 -9
  141. package/dist/lib/project-resources.js +1 -1
  142. package/dist/lib/reserved-stores.d.ts +53 -0
  143. package/dist/lib/reserved-stores.js +120 -0
  144. package/dist/lib/secrets-client.d.ts +191 -0
  145. package/dist/lib/secrets-client.js +710 -0
  146. package/dist/lib/secrets-policy.d.ts +222 -0
  147. package/dist/lib/secrets-policy.js +483 -0
  148. package/dist/lib/secrets-types.d.ts +188 -0
  149. package/dist/lib/secrets-types.js +24 -0
  150. package/dist/lib/service-manifest.js +3 -4
  151. package/dist/lib/session/db.d.ts +18 -0
  152. package/dist/lib/session/db.js +47 -14
  153. package/dist/lib/session/sync/config.js +2 -2
  154. package/dist/lib/sha256-asset.d.ts +11 -16
  155. package/dist/lib/sha256-asset.js +11 -16
  156. package/dist/lib/share/config.js +14 -5
  157. package/dist/lib/signin-badge.d.ts +26 -0
  158. package/dist/lib/signin-badge.js +42 -0
  159. package/dist/lib/staleness/detectors/workflows.js +3 -105
  160. package/dist/lib/staleness/writers/workflows.js +2 -1
  161. package/dist/lib/state.d.ts +7 -4
  162. package/dist/lib/state.js +18 -7
  163. package/dist/lib/sync-umbrella.js +2 -2
  164. package/dist/lib/teams/agents.js +1 -1
  165. package/dist/lib/types.d.ts +74 -33
  166. package/dist/lib/view-types.d.ts +6 -3
  167. package/dist/lib/workflows-registry.d.ts +71 -0
  168. package/dist/lib/workflows-registry.js +280 -0
  169. package/dist/lib/workflows.d.ts +21 -21
  170. package/dist/lib/workflows.js +12 -327
  171. package/package.json +2 -3
  172. package/scripts/postinstall.js +4 -29
  173. package/dist/commands/secrets-import.d.ts +0 -18
  174. package/dist/commands/secrets-import.js +0 -74
  175. package/dist/commands/secrets-migrate.d.ts +0 -25
  176. package/dist/commands/secrets-migrate.js +0 -334
  177. package/dist/commands/secrets-rotate-passphrase.d.ts +0 -17
  178. package/dist/commands/secrets-rotate-passphrase.js +0 -96
  179. package/dist/commands/secrets-sync.d.ts +0 -11
  180. package/dist/commands/secrets-sync.js +0 -153
  181. package/dist/commands/secrets-vault.d.ts +0 -10
  182. package/dist/commands/secrets-vault.js +0 -130
  183. package/dist/commands/secrets.d.ts +0 -212
  184. package/dist/commands/secrets.js +0 -3030
  185. package/dist/lib/accounts/connect.d.ts +0 -176
  186. package/dist/lib/accounts/connect.js +0 -453
  187. package/dist/lib/daemon/keychain-reap-service.d.ts +0 -17
  188. package/dist/lib/daemon/keychain-reap-service.js +0 -32
  189. package/dist/lib/daemon/secrets-broker-service.d.ts +0 -21
  190. package/dist/lib/daemon/secrets-broker-service.js +0 -51
  191. package/dist/lib/secrets/agent.d.ts +0 -358
  192. package/dist/lib/secrets/agent.js +0 -1291
  193. package/dist/lib/secrets/audit.d.ts +0 -46
  194. package/dist/lib/secrets/audit.js +0 -101
  195. package/dist/lib/secrets/bundles.d.ts +0 -290
  196. package/dist/lib/secrets/bundles.js +0 -1547
  197. package/dist/lib/secrets/download-keychain.d.ts +0 -47
  198. package/dist/lib/secrets/download-keychain.js +0 -70
  199. package/dist/lib/secrets/drivers/rush.d.ts +0 -14
  200. package/dist/lib/secrets/drivers/rush.js +0 -90
  201. package/dist/lib/secrets/fallback.d.ts +0 -48
  202. package/dist/lib/secrets/fallback.js +0 -48
  203. package/dist/lib/secrets/filestore.d.ts +0 -222
  204. package/dist/lib/secrets/filestore.js +0 -1099
  205. package/dist/lib/secrets/headless.d.ts +0 -43
  206. package/dist/lib/secrets/headless.js +0 -56
  207. package/dist/lib/secrets/icloud-import.d.ts +0 -79
  208. package/dist/lib/secrets/icloud-import.js +0 -206
  209. package/dist/lib/secrets/index.d.ts +0 -451
  210. package/dist/lib/secrets/index.js +0 -1568
  211. package/dist/lib/secrets/install-helper.d.ts +0 -72
  212. package/dist/lib/secrets/install-helper.js +0 -245
  213. package/dist/lib/secrets/lease.d.ts +0 -25
  214. package/dist/lib/secrets/lease.js +0 -44
  215. package/dist/lib/secrets/linux.d.ts +0 -77
  216. package/dist/lib/secrets/linux.js +0 -393
  217. package/dist/lib/secrets/list-filter.d.ts +0 -109
  218. package/dist/lib/secrets/list-filter.js +0 -261
  219. package/dist/lib/secrets/mcp.d.ts +0 -93
  220. package/dist/lib/secrets/mcp.js +0 -211
  221. package/dist/lib/secrets/profiles.d.ts +0 -10
  222. package/dist/lib/secrets/profiles.js +0 -13
  223. package/dist/lib/secrets/push.d.ts +0 -133
  224. package/dist/lib/secrets/push.js +0 -273
  225. package/dist/lib/secrets/rc-hygiene.d.ts +0 -63
  226. package/dist/lib/secrets/rc-hygiene.js +0 -143
  227. package/dist/lib/secrets/read-backoff.d.ts +0 -27
  228. package/dist/lib/secrets/read-backoff.js +0 -64
  229. package/dist/lib/secrets/reaper.d.ts +0 -90
  230. package/dist/lib/secrets/reaper.js +0 -243
  231. package/dist/lib/secrets/remote.d.ts +0 -152
  232. package/dist/lib/secrets/remote.js +0 -344
  233. package/dist/lib/secrets/reserved-sync.d.ts +0 -66
  234. package/dist/lib/secrets/reserved-sync.js +0 -147
  235. package/dist/lib/secrets/scope.d.ts +0 -26
  236. package/dist/lib/secrets/scope.js +0 -29
  237. package/dist/lib/secrets/session-store.d.ts +0 -107
  238. package/dist/lib/secrets/session-store.js +0 -342
  239. package/dist/lib/secrets/sync-backend.d.ts +0 -48
  240. package/dist/lib/secrets/sync-backend.js +0 -13
  241. package/dist/lib/secrets/sync-commands.d.ts +0 -21
  242. package/dist/lib/secrets/sync-commands.js +0 -21
  243. package/dist/lib/secrets/sync.d.ts +0 -48
  244. package/dist/lib/secrets/sync.js +0 -237
  245. package/dist/lib/secrets/unlock-hints.d.ts +0 -27
  246. package/dist/lib/secrets/unlock-hints.js +0 -36
  247. package/dist/lib/secrets/usage-db.d.ts +0 -46
  248. package/dist/lib/secrets/usage-db.js +0 -96
  249. package/dist/lib/secrets/vault-age-helper.d.ts +0 -1
  250. package/dist/lib/secrets/vault-age-helper.js +0 -34
  251. package/dist/lib/secrets/vault.d.ts +0 -49
  252. package/dist/lib/secrets/vault.js +0 -399
  253. package/dist/lib/secrets/windows.d.ts +0 -81
  254. package/dist/lib/secrets/windows.js +0 -556
  255. package/scripts/install-helper.js +0 -97
  256. /package/dist/lib/{secrets/sync-passphrase.d.ts → sync-passphrase.d.ts} +0 -0
  257. /package/dist/lib/{secrets/sync-passphrase.js → sync-passphrase.js} +0 -0
@@ -18,7 +18,7 @@ import { isHostPinned, isDevicePinned, managedKnownHostsPath } from '../lib/devi
18
18
  import { ensureDevicesRegistered } from '../lib/devices/sync.js';
19
19
  import { readFleetFile, resolveDesired, emptyTargetsMessage } from '../lib/fleet/manifest.js';
20
20
  import { snapshotAuth } from '../lib/fleet/auth-sync.js';
21
- import { AUTH_BUNDLE_NAME } from '../lib/secrets/bundles.js';
21
+ import { AUTH_BUNDLE_NAME } from '../lib/secrets-policy.js';
22
22
  import { agentIdOf, diffFleet, probeDevice, runFleetApply, pool, sourceHome, expandAllSpecs, rosterNeedsVersions, fleetSecretsBundles, } from '../lib/fleet/apply.js';
23
23
  import { listInstalledVersions } from '../lib/installations/versions.js';
24
24
  /** Version of the running agents-cli — the fleet target version. */
@@ -1,8 +1,12 @@
1
1
  /**
2
2
  * Shared `mint` subcommand registered on both `agents auth` (the ticket
3
- * surface) and `agents accounts` (the credential noun). Implementation lives
4
- * in lib/auth-mint.ts so neither command file owns the seed path.
3
+ * surface) and `agents accounts` (the credential noun). Hidden on both:
4
+ * `agents accounts add <harness> [name]` / `agents accounts login <harness>#<name>`
5
+ * is the taught path. Implementation lives in lib/auth-mint.ts so neither
6
+ * command file owns the seed path.
5
7
  */
6
8
  import type { Command } from 'commander';
7
9
  import { type MintDriveHooks } from '../lib/auth-mint.js';
8
- export declare function registerMintCommand(parent: Command, testHooks?: MintDriveHooks): Command;
10
+ export declare function registerMintCommand(parent: Command, testHooks?: MintDriveHooks, opts?: {
11
+ hidden?: boolean;
12
+ }): Command;
@@ -1,8 +1,16 @@
1
+ /**
2
+ * Shared `mint` subcommand registered on both `agents auth` (the ticket
3
+ * surface) and `agents accounts` (the credential noun). Hidden on both:
4
+ * `agents accounts add <harness> [name]` / `agents accounts login <harness>#<name>`
5
+ * is the taught path. Implementation lives in lib/auth-mint.ts so neither
6
+ * command file owns the seed path.
7
+ */
1
8
  import chalk from 'chalk';
2
9
  import { setHelpSections } from '../lib/help.js';
3
10
  import { readStdinSync, runOrDie } from '../lib/format.js';
4
11
  import { isInteractiveTerminal } from './utils.js';
5
12
  import { getMintFlow, listMintableHarnesses, mintAndSeed, } from '../lib/auth-mint.js';
13
+ const MINT_POINTER = 'agents accounts add <harness> [name] / agents accounts login <harness>#<name>';
6
14
  function collect(value, previous) {
7
15
  return [...previous, value];
8
16
  }
@@ -28,9 +36,10 @@ function printResult(result, json) {
28
36
  }
29
37
  }
30
38
  }
31
- export function registerMintCommand(parent, testHooks) {
39
+ export function registerMintCommand(parent, testHooks, opts) {
40
+ const hidden = opts?.hidden ?? true;
32
41
  const mint = parent
33
- .command('mint <harness>')
42
+ .command('mint <harness>', { hidden })
34
43
  .description('Mint a long-lived setup-token and seed it as a named account')
35
44
  .option('--account <name-or-email>', 'Named account to create/rotate, or the account email')
36
45
  .option('--email <email>', 'Email that keys the reserved auth bundle when --account is a name')
@@ -78,19 +87,22 @@ export function registerMintCommand(parent, testHooks) {
78
87
  printResult(result, json);
79
88
  }, { json });
80
89
  });
90
+ if (hidden) {
91
+ mint.hook('preAction', () => {
92
+ console.error(chalk.gray(`'agents ${parent.name()} mint' is a hidden alias; use \`${MINT_POINTER}\` — still works this release.`));
93
+ });
94
+ }
81
95
  setHelpSections(mint, {
82
- examples: `agents accounts mint claude
83
- agents accounts mint claude --account ada@example.com
84
- agents accounts mint claude --account work --email ada@example.com
96
+ examples: `agents accounts add claude work
97
+ agents accounts login claude#work
85
98
  agents accounts mint claude --token-stdin < token.txt
86
99
  agents accounts mint claude --code AUTHCODE --json
87
- agents accounts mint claude --fleet
88
- agents auth mint claude --account ada@example.com --json`,
89
- notes: `Claude is the only harness with an interactive long-lived setup-token (\`claude setup-token\`, ~1 year, shareable across machines). The command drives that flow in a PTY, opens the authorize URL, captures a well-formed sk-ant-oat01- token (refusing the #1767 TTY-banner capture), and seeds:
100
+ agents accounts mint claude --fleet`,
101
+ notes: `Hidden alias of the worker-credential step that \`agents accounts add <harness> [name]\` (mint during add) and \`agents accounts login <harness>#<name>\` (re-mint) now own. Claude is the only harness with an interactive long-lived setup-token (\`claude setup-token\`, ~1 year, shareable across machines). The command drives that flow in a PTY, opens the authorize URL, captures a well-formed sk-ant-oat01- token (refusing the #1767 TTY-banner capture), and seeds:
90
102
  1. a named provider account (policy never) for \`agents run --account\` / \`agents accounts sync\`
91
103
  2. the reserved FILE-BASED auth bundle keyed per email, which usage/probe reads
92
104
  Native rotating OAuth is never copied. Other harnesses fail loud: device-code login is \`agents fleet login\`; API keys are \`agents accounts add\`.
93
- Already have a token? \`--token-stdin\` skips the browser dance. \`agents auth mint\` is the same command as \`agents accounts mint\`.`,
105
+ Already have a token? \`--token-stdin\` skips the browser dance. \`agents auth mint\` and \`agents accounts mint\` are the same hidden command.`,
94
106
  });
95
107
  return mint;
96
108
  }
@@ -105,7 +105,6 @@ export function registerAuthCommand(program) {
105
105
  setHelpSections(auth, {
106
106
  examples: `agents auth login # device-code sign-in via your browser
107
107
  agents auth whoami # who this machine is signed in as
108
- agents auth mint claude # mint a Claude setup-token into a named account
109
108
  agents auth space create "Design Team" # start a space
110
109
  agents auth space invite ada@example.com # add a teammate
111
110
  agents auth logout # clear this machine only`,
@@ -113,7 +112,7 @@ agents auth logout # clear this machine only`,
113
112
  Sign-in is Google-only and opens a Phoenix-branded page; the CLI never sees a password.
114
113
  The session lives in this machine's agents state dir, so logging out here signs out nothing else.
115
114
  Point at a different backend with PHOENIX_ID_BASE (defaults to the production service).
116
- \`agents auth mint\` is the harness setup-token mint (same command as \`agents accounts mint\`); it does not sign you into Phoenix ID.`,
115
+ Harness worker credentials are minted by \`agents accounts add <harness> [name]\` / \`agents accounts login <harness>#<name>\`, not by this command.`,
117
116
  });
118
117
  auth
119
118
  .command('login')
@@ -127,7 +126,7 @@ Point at a different backend with PHOENIX_ID_BASE (defaults to the production se
127
126
  const json = !!o.json || !!command.optsWithGlobals().json;
128
127
  return runOrDie(() => whoami(json), { json });
129
128
  });
130
- registerMintCommand(auth);
129
+ registerMintCommand(auth, undefined, { hidden: true });
131
130
  auth
132
131
  .command('logout')
133
132
  .description("Clear this machine's session (no other device is affected)")
@@ -8,7 +8,7 @@ import { declaringDevices, migrateCentralBrowserProfiles, profileKind } from '..
8
8
  import { resolveActor } from '../lib/actor.js';
9
9
  import { loginsForProfile, profilesLoggedInto, serviceForUrl, loginsWithAccountsForProfile, accountsForProfile, credKeysForService, AUTH_SIGNATURES, } from '../lib/browser/login-detection.js';
10
10
  import { parseSecretRef } from '../lib/browser/secret-ref.js';
11
- import { readAndResolveBundleEnv, bundleExists, readBundle, describeBundle } from '../lib/secrets/bundles.js';
11
+ import { readAndResolveBundleEnv, bundleExists, readBundle } from '../lib/secrets-client.js';
12
12
  import { findBrowserPath, getPortOccupant, getProcessUserDataDir, isLauncherScript, listInstalledBrowsers } from '../lib/browser/chrome.js';
13
13
  import { listProfileCacheDirs, removeProfileCache, listAllProfileSnapshots, buildProfilePrunePlan, pruneProfiles, PRUNE_REASON_TEXT, identityLoopbackMismatch, } from '../lib/browser/runtime-state.js';
14
14
  import { DEFAULT_VIEWPORT, parseWindowSize, parseWindowPosition } from '../lib/browser/devices.js';
@@ -452,16 +452,16 @@ function registerBrowserUseCommand(browser) {
452
452
  * keys exist, '-' when not, or '(unknown)' if the metadata read would prompt or
453
453
  * fails — the view must never block on Touch ID.
454
454
  */
455
- function credAvailability(bundleName, service) {
455
+ async function credAvailability(bundleName, service) {
456
456
  if (!bundleName)
457
457
  return '-';
458
458
  const keys = credKeysForService(service);
459
459
  if (!keys)
460
460
  return '-';
461
461
  try {
462
- if (!bundleExists(bundleName))
462
+ if (!(await bundleExists(bundleName)))
463
463
  return '-';
464
- const present = new Set(describeBundle(readBundle(bundleName)).map((e) => e.key));
464
+ const present = new Set(Object.keys((await readBundle(bundleName)).vars));
465
465
  return present.has(keys.user) && present.has(keys.pass) ? bundleName : '-';
466
466
  }
467
467
  catch {
@@ -590,7 +590,7 @@ function registerProfilesCommands(browser) {
590
590
  console.log(name.padEnd(20) +
591
591
  r.service.padEnd(12) +
592
592
  (r.username ?? '(unknown)').padEnd(32) +
593
- credAvailability(p.secrets, r.service));
593
+ (await credAvailability(p.secrets, r.service)));
594
594
  first = false;
595
595
  }
596
596
  }
@@ -712,7 +712,7 @@ function registerProfilesCommands(browser) {
712
712
  }
713
713
  // Warn (don't fail) if the declared secrets bundle doesn't exist yet — it
714
714
  // may be created later, but a typo should surface now.
715
- if (opts.secrets && !bundleExists(opts.secrets)) {
715
+ if (opts.secrets && !(await bundleExists(opts.secrets))) {
716
716
  console.error(`warning: secrets bundle "${opts.secrets}" does not exist yet. Create it with: agents secrets create ${opts.secrets}`);
717
717
  }
718
718
  });
@@ -847,7 +847,7 @@ function registerProfilesCommands(browser) {
847
847
  else {
848
848
  console.log(`Updated ${name} on ${machineId()}: ${result.changed.join(', ')}`);
849
849
  }
850
- if (patch.secrets && !bundleExists(patch.secrets)) {
850
+ if (patch.secrets && !(await bundleExists(patch.secrets))) {
851
851
  console.error(`warning: secrets bundle "${patch.secrets}" does not exist yet. Create it with: agents secrets create ${patch.secrets}`);
852
852
  }
853
853
  });
@@ -957,7 +957,7 @@ function registerProfilesCommands(browser) {
957
957
  const lines = [];
958
958
  for (const service of Object.keys(AUTH_SIGNATURES)) {
959
959
  const isActive = active.includes(service);
960
- const creds = credAvailability(profile.secrets, service);
960
+ const creds = await credAvailability(profile.secrets, service);
961
961
  if (!isActive && creds === '-')
962
962
  continue; // nothing interesting to show
963
963
  const status = isActive
@@ -2661,12 +2661,12 @@ function registerTaskCommands(browser) {
2661
2661
  console.error(`--secret must be <bundle>/<KEY>, got: ${opts.secret}`);
2662
2662
  process.exit(1);
2663
2663
  }
2664
- if (!bundleExists(parsed.bundle)) {
2664
+ if (!(await bundleExists(parsed.bundle))) {
2665
2665
  console.error(`Secrets bundle "${parsed.bundle}" not found.`);
2666
2666
  process.exit(1);
2667
2667
  }
2668
2668
  try {
2669
- const { env } = readAndResolveBundleEnv(parsed.bundle, { caller: 'browser type', keys: [parsed.key], keyMode: 'storage', agentOnly: true });
2669
+ const { env } = await readAndResolveBundleEnv(parsed.bundle, { caller: 'browser type', keys: [parsed.key], keyMode: 'storage', agentOnly: true });
2670
2670
  if (!(parsed.key in env)) {
2671
2671
  console.error(`Key "${parsed.key}" not in bundle "${parsed.bundle}".`);
2672
2672
  process.exit(1);
@@ -2,9 +2,11 @@
2
2
  * `agents daemon` — runtime, hosted services, and failure visibility for the
3
3
  * always-on daemon (RUSH-2354).
4
4
  *
5
- * The daemon holds the routines scheduler, the secrets broker, the browser IPC
6
- * server, and the watchdog pass — but until this command group existed it had
7
- * no user-facing surface: no way to see it, restart it, or turn it off.
5
+ * The daemon holds the routines scheduler, the browser IPC server, and the
6
+ * watchdog pass — but until this command group existed it had no user-facing
7
+ * surface: no way to see it, restart it, or turn it off. The secrets broker
8
+ * moved out of this daemon entirely with the standalone `secrets` engine
9
+ * (PHNX-3989 OWN-1); `agents daemon status` only probes its reachability.
8
10
  * `daemon.ts` (the runtime) has always implemented every mechanism this file
9
11
  * wires up; nothing here is new machinery, only the missing CLI surface.
10
12
  *
@@ -16,6 +18,8 @@
16
18
  */
17
19
  import type { Command } from 'commander';
18
20
  import { type DaemonServiceId } from '../lib/daemon-services.js';
21
+ /** Parse a `ps -o etime=` value (`[[dd-]hh:]mm:ss`) into elapsed seconds. */
22
+ export declare function parseEtimeToSeconds(raw: string): number | null;
19
23
  /** Elapsed wall-clock seconds since `pid` started, or null if unavailable (best-effort, POSIX only). */
20
24
  export declare function uptimeSeconds(pid: number): number | null;
21
25
  /** One row of the roster every registered daemon service — supervisor-managed or legacy — renders in `agents daemon services`. */
@@ -2,9 +2,11 @@
2
2
  * `agents daemon` — runtime, hosted services, and failure visibility for the
3
3
  * always-on daemon (RUSH-2354).
4
4
  *
5
- * The daemon holds the routines scheduler, the secrets broker, the browser IPC
6
- * server, and the watchdog pass — but until this command group existed it had
7
- * no user-facing surface: no way to see it, restart it, or turn it off.
5
+ * The daemon holds the routines scheduler, the browser IPC server, and the
6
+ * watchdog pass — but until this command group existed it had no user-facing
7
+ * surface: no way to see it, restart it, or turn it off. The secrets broker
8
+ * moved out of this daemon entirely with the standalone `secrets` engine
9
+ * (PHNX-3989 OWN-1); `agents daemon status` only probes its reachability.
8
10
  * `daemon.ts` (the runtime) has always implemented every mechanism this file
9
11
  * wires up; nothing here is new machinery, only the missing CLI surface.
10
12
  *
@@ -21,7 +23,7 @@ import * as path from 'path';
21
23
  import { setHelpSections } from '../lib/help.js';
22
24
  import { getDaemonStatus, isDaemonRunning, isDaemonWedged, readDaemonLog, startDaemon, stopDaemon, signalDaemonReload, findSurvivingStateDirDaemons, getDaemonLogPath, isDaemonAutostartCircuitOpen, } from '../lib/daemon/daemon.js';
23
25
  import { getConfigValue, setConfigValue, isDaemonEnabled } from '../lib/device-config.js';
24
- import { readSubsystemHealth, readAllSubsystemHealth, SUBSYSTEM_SECRETS_BROKER, SUBSYSTEM_BROWSER_IPC, SUBSYSTEM_DAEMON_START, } from '../lib/daemon-health.js';
26
+ import { readSubsystemHealth, readAllSubsystemHealth, SUBSYSTEM_BROWSER_IPC, SUBSYSTEM_DAEMON_START, } from '../lib/daemon-health.js';
25
27
  import { DAEMON_SERVICE_IDS, listDaemonServiceStates, setDaemonServiceEnabled, getDaemonServicesConfigPath, queueDaemonServiceRestart, } from '../lib/daemon-services.js';
26
28
  import { listJobs, getLatestRun } from '../lib/scheduling/routines.js';
27
29
  import { JobScheduler } from '../lib/scheduler.js';
@@ -30,7 +32,6 @@ import { parseDuration } from '../lib/hooks/cache.js';
30
32
  import { registerFunnelCommand } from './funnel.js';
31
33
  import { DEFAULT_WEBHOOK_PORT, DEFAULT_WEBHOOK_RATE_LIMIT, addHostedReceiver, getDaemonWebhooksConfigPath, hostedReceiverPort, readDaemonWebhooksConfig, removeHostedReceiver, } from '../lib/daemon-webhooks.js';
32
34
  import { parseFunnelPort } from '../lib/funnel.js';
33
- import { parseEtimeToSeconds } from '../lib/secrets/reaper.js';
34
35
  /**
35
36
  * Provably absent, or null when unknowable.
36
37
  *
@@ -256,6 +257,19 @@ function registryScopedDuplicates(processes, ownerPid) {
256
257
  const registered = new Set(findSurvivingStateDirDaemons(exclude));
257
258
  return processes.filter((p) => registered.has(p.pid));
258
259
  }
260
+ /** Parse a `ps -o etime=` value (`[[dd-]hh:]mm:ss`) into elapsed seconds. */
261
+ export function parseEtimeToSeconds(raw) {
262
+ const m = raw.match(/^(?:(\d+)-)?(?:(\d+):)?(\d+):(\d+)$/);
263
+ if (!m)
264
+ return null;
265
+ const days = m[1] ? parseInt(m[1], 10) : 0;
266
+ const hours = m[2] ? parseInt(m[2], 10) : 0;
267
+ const mins = parseInt(m[3], 10);
268
+ const secs = parseInt(m[4], 10);
269
+ if ([days, hours, mins, secs].some(isNaN))
270
+ return null;
271
+ return ((days * 24 + hours) * 60 + mins) * 60 + secs;
272
+ }
259
273
  /** Elapsed wall-clock seconds since `pid` started, or null if unavailable (best-effort, POSIX only). */
260
274
  export function uptimeSeconds(pid) {
261
275
  if (process.platform === 'win32')
@@ -264,7 +278,7 @@ export function uptimeSeconds(pid) {
264
278
  // `-o etimes=` is a GNU/procps keyword macOS/BSD `ps` rejects with a
265
279
  // non-zero exit (`ps: etimes: keyword not found`), so `agents daemon status`
266
280
  // errored out entirely on macOS. `etime` (`[[dd-]hh:]mm:ss`) is the portable
267
- // POSIX field; `parseEtimeToSeconds` (shared with the keychain reaper) parses it.
281
+ // POSIX field; `parseEtimeToSeconds` above parses it.
268
282
  const out = execFileSync('ps', ['-o', 'etime=', '-p', String(pid)], { encoding: 'utf-8' }).trim();
269
283
  return parseEtimeToSeconds(out);
270
284
  }
@@ -281,18 +295,18 @@ function humanDuration(seconds) {
281
295
  return `${Math.round(seconds / 3600)}h`;
282
296
  return `${Math.round(seconds / 86400)}d`;
283
297
  }
298
+ /** Reachability probe only — the daemon does not host, supervise, or take over the broker (OWN-1). */
284
299
  async function probeSecretsBroker() {
285
- const record = readSubsystemHealth(SUBSYSTEM_SECRETS_BROKER);
286
300
  try {
287
- const { agentPing, agentStatus, secretsBrokerSocketPath } = await import('../lib/secrets/agent.js');
301
+ const { agentPing, agentStatus } = await import('../lib/secrets-client.js');
288
302
  const ping = await agentPing();
289
303
  if (!ping.reachable)
290
- return { reachable: false, socketPath: secretsBrokerSocketPath(), heldBundles: null, record };
304
+ return { reachable: false, socketPath: null, heldBundles: null, record: null };
291
305
  const entries = await agentStatus();
292
- return { reachable: true, socketPath: secretsBrokerSocketPath(), heldBundles: entries.length, record };
306
+ return { reachable: true, socketPath: null, heldBundles: entries.length, record: null };
293
307
  }
294
308
  catch {
295
- return { reachable: false, socketPath: null, heldBundles: null, record };
309
+ return { reachable: false, socketPath: null, heldBundles: null, record: null };
296
310
  }
297
311
  }
298
312
  async function probeBrowserIPC() {
@@ -732,8 +746,8 @@ function registerWebhooksSubcommand(parent) {
732
746
  notes: `
733
747
  The bundle must hold GITHUB_WEBHOOK_SECRET and/or LINEAR_WEBHOOK_SECRET
734
748
  ('agents secrets add <bundle> LINEAR_WEBHOOK_SECRET'). The daemon reads it
735
- through the secrets broker, so a hosted receiver needs no
736
- AGENTS_SECRETS_PASSPHRASE and no nohup. A LOCKED bundle fails that receiver
749
+ headlessly through the standalone 'secrets' CLI, so a hosted receiver needs
750
+ no AGENTS_SECRETS_PASSPHRASE and no nohup. A LOCKED bundle fails that receiver
737
751
  loud in 'agents daemon logs' rather than binding unverified ingress.
738
752
 
739
753
  Port is the identity: a second 'add' on the same port edits that receiver.
@@ -831,7 +845,7 @@ function runWebhooksList(json) {
831
845
  export function registerDaemonCommand(program) {
832
846
  const cmd = program
833
847
  .command('daemon')
834
- .description('The always-on daemon: secrets broker, browser IPC, watchdog, and the routines scheduler. Bare `agents daemon` shows status.')
848
+ .description('The always-on daemon: browser IPC, watchdog, and the routines scheduler. Bare `agents daemon` shows status.')
835
849
  .option('--json', 'Emit as JSON')
836
850
  .action(async (opts, command) => {
837
851
  await runStatus({ json: command.optsWithGlobals().json === true });
@@ -860,12 +874,12 @@ export function registerDaemonCommand(program) {
860
874
  agents daemon services
861
875
 
862
876
  # Toggle or restart a service live — applies without a daemon restart
863
- # for supervisor-managed services (secrets-broker, browser-ipc,
864
- # account-state, session-index, monitors' off-transition, watchdog,
865
- # device-probe, self-heal, keychain-reap, state-dir-check)
866
- agents daemon services disable secrets-broker
867
- agents daemon services enable secrets-broker
868
- agents daemon services restart secrets-broker
877
+ # for supervisor-managed services (browser-ipc, account-state,
878
+ # session-index, monitors' off-transition, watchdog, device-probe,
879
+ # self-heal, state-dir-check)
880
+ agents daemon services disable browser-ipc
881
+ agents daemon services enable browser-ipc
882
+ agents daemon services restart browser-ipc
869
883
 
870
884
  # Host a signed webhook receiver here, supervised and restarted on crash
871
885
  agents daemon webhooks add --secrets-bundle linear-webhook
@@ -1006,17 +1020,18 @@ export function registerDaemonCommand(program) {
1006
1020
  agents daemon services
1007
1021
 
1008
1022
  # Machine-readable — additive: also carries the pre-existing
1009
- # secretsBroker/browserIpc hosted-socket fields
1023
+ # secretsBroker (reachability-only probe, PHNX-3989)/browserIpc
1024
+ # hosted-socket fields
1010
1025
  agents daemon services --json
1011
1026
 
1012
1027
  # Just the enable/disable metadata, no health probe
1013
1028
  agents daemon services list
1014
1029
 
1015
1030
  # Toggle or restart live — no daemon restart for a supervisor-managed
1016
- # service (secrets-broker, browser-ipc, account-state, session-index)
1017
- agents daemon services disable secrets-broker
1018
- agents daemon services enable secrets-broker
1019
- agents daemon services restart secrets-broker
1031
+ # service (browser-ipc, account-state, session-index)
1032
+ agents daemon services disable browser-ipc
1033
+ agents daemon services enable browser-ipc
1034
+ agents daemon services restart browser-ipc
1020
1035
  `,
1021
1036
  notes: `
1022
1037
  A service disabled at daemon boot is normally not registered on the
@@ -24,7 +24,7 @@ import { resolveAgentTargets, AgentSpecError } from '../lib/agent-spec/index.js'
24
24
  import { loadManifest, isStale } from '../lib/staleness/index.js';
25
25
  import { diffVersionResources, DOCTOR_ALL_KINDS, } from '../lib/doctor-diff.js';
26
26
  import { inspectDuplicateVersionHooks } from '../lib/hooks/install.js';
27
- import { inspectReservedAuthBundle } from '../lib/secrets/bundles.js';
27
+ import { inspectReservedAuthBundle } from '../lib/secrets-policy.js';
28
28
  import { isVersionIsolated } from '../lib/installations/versions.js';
29
29
  import { computeDrift, checkSyncStatus, countOrphans, computeSourceBehind } from '../lib/drift.js';
30
30
  import { readAuthHealthCache, summarizeHostAuth } from '../lib/auth-health.js';
@@ -35,13 +35,39 @@ import { listCliStatus, listCliStatusAsync } from '../lib/cli-resources.js';
35
35
  import { setHelpSections } from '../lib/help.js';
36
36
  import { getEffectiveExecutionPolicy } from '../lib/platform/winpath.js';
37
37
  import { auditWindowsSshEnrollment, diagnoseWindowsSshFailure } from '../lib/devices/windows-ssh-enrollment.js';
38
- import { scanUserRcFiles, masterPassphraseInEnv } from '../lib/secrets/rc-hygiene.js';
38
+ import { scanUserRcFilesSync, masterPassphraseInEnvSync, isSecretsTransportError, } from '../lib/secrets-client.js';
39
39
  import { terminalWidth, truncateToWidth, stringWidth, padToWidth } from '../lib/session/width.js';
40
40
  import { readRepoBehindMarkers } from '../lib/auto-pull.js';
41
41
  import { detectAgentsBinaryShadows } from '../lib/binary-shadow.js';
42
42
  import * as fs from 'fs';
43
43
  const AGENT_NAMES = Object.fromEntries(ALL_AGENT_IDS.map((id) => [id, AGENTS[id].name]));
44
44
  // ─── overview mode (no target) ────────────────────────────────────────────────
45
+ // doctor is the umbrella diagnostic and must never crash on one subsystem
46
+ // (§Diagnostic command taxonomy). The standalone secrets CLI being absent from
47
+ // PATH is a routine, expected state on a box that hasn't installed it — not a
48
+ // reason to abort the whole report — so these two checks degrade to "nothing to
49
+ // report" on a transport error and let every other finding still print. A data
50
+ // error the standalone actually answered with must still surface.
51
+ function scanUserRcFiles() {
52
+ try {
53
+ return scanUserRcFilesSync();
54
+ }
55
+ catch (error) {
56
+ if (isSecretsTransportError(error))
57
+ return [];
58
+ throw error;
59
+ }
60
+ }
61
+ function masterPassphraseInEnv() {
62
+ try {
63
+ return masterPassphraseInEnvSync();
64
+ }
65
+ catch (error) {
66
+ if (isSecretsTransportError(error))
67
+ return false;
68
+ throw error;
69
+ }
70
+ }
45
71
  function collapseWhitespace(s) {
46
72
  return s.replace(/\s+/g, ' ').trim();
47
73
  }
@@ -843,6 +843,23 @@ agents run auto --device yosemite-s0 "fix the flaky test" # pin the device
843
843
  console.error(chalk.red(`Invalid account picker target: ${agentSpec}. Use agents run <agent>@.`));
844
844
  process.exit(1);
845
845
  }
846
+ // Peel `#name` off before --device dispatch so the selector rides the hop
847
+ // unchanged and the peer resolves ITS slot (PHNX-3940 T5). The later local
848
+ // parse is idempotent when the values match.
849
+ {
850
+ const labelParts = normalizedAgentSpec.split('#');
851
+ if (labelParts.length > 2 || labelParts[1] === '') {
852
+ console.error(chalk.red(`Invalid account label in '${normalizedAgentSpec}'.`));
853
+ process.exit(1);
854
+ }
855
+ const specAccountLabel = labelParts[1];
856
+ if (specAccountLabel && options.account && specAccountLabel !== options.account) {
857
+ console.error(chalk.red(`Account '${specAccountLabel}' from the agent spec conflicts with --account '${options.account}'.`));
858
+ process.exit(1);
859
+ }
860
+ if (specAccountLabel)
861
+ options.account = specAccountLabel;
862
+ }
846
863
  // Hard-deprecated harnesses cannot be run — point the user at the successor.
847
864
  const runBaseAgentName = normalizedAgentSpec.split('#')[0].split('@')[0];
848
865
  const runBaseAgentId = resolveAgentName(runBaseAgentName);
@@ -1130,11 +1147,11 @@ agents run auto --device yosemite-s0 "fix the flaky test" # pin the device
1130
1147
  // with an actionable hint instead of hard-failing the run (F5).
1131
1148
  if (netMode === 'tailscale') {
1132
1149
  const { pickTailscaleBundleFromList } = await import('../lib/crabbox/cli.js');
1133
- const { listBundles } = await import('../lib/secrets/bundles.js');
1150
+ const { listBundles } = await import('../lib/secrets-client.js');
1134
1151
  let hasKey = !!process.env.CRABBOX_TAILSCALE_AUTH_KEY;
1135
1152
  if (!hasKey) {
1136
1153
  try {
1137
- hasKey = !!pickTailscaleBundleFromList(listBundles());
1154
+ hasKey = !!pickTailscaleBundleFromList(await listBundles());
1138
1155
  }
1139
1156
  catch {
1140
1157
  hasKey = false;
@@ -1800,12 +1817,16 @@ agents run auto --device yosemite-s0 "fix the flaky test" # pin the device
1800
1817
  }
1801
1818
  process.exit(resumeExit);
1802
1819
  }
1803
- const [{ buildExecCommand, parseExecEnv, execAgent, runWithFallback, normalizeMode, resolveMode, implicitModeFor, headlessPlanStallCommand, nativeResume, resolveInteractive, inferredInteractiveWithoutTty }, { ALL_AGENT_IDS, ACCOUNT_INSPECTION_AGENT_IDS, agentLabel, supportsAccountInspection }, { profileExists, readProfile, resolveProfileForRun }, { readAndResolveBundleEnv, describeBundle, assertRemoteBundleFlagsUnsupported }, { splitBundleRef, resolveHostSshTarget, remoteResolveEnv }, { getConfiguredRunStrategy, normalizeRunStrategy, resolveRunVersion, rotationFailoverChain, shouldArmRotationFailover, RUN_STRATEGIES, collectHarnessCandidates, pickHarnessWeighted, classifyHarnessCandidates, formatHarnessPickBanner, formatNoHealthyHarnessError, formatNoHealthyAccountError, formatNoVerifiedUsageError, signInRecoverableCandidates }, { getGlobalDefault, getVersionHomePath, resolveVersion, resolveVersionAlias, ensureAgentRunnable }, { buildDiscoveredPlugin, loadPluginManifest, syncPluginToVersion }, { parseWorkflowFrontmatter, resolveWorkflowRef, resolveAllowedSubagents, pruneStaleWorkflowSubagents, ensureSubagentDispatchTool }, { resolveRunDefaults }, { getMcpServersByName, buildWorkflowMcpConfig }, { supports, capableAgents }, { shareRuntimeEnv },] = await Promise.all([
1820
+ const [{ buildExecCommand, parseExecEnv, execAgent, runWithFallback, normalizeMode, resolveMode, implicitModeFor, headlessPlanStallCommand, nativeResume, resolveInteractive, inferredInteractiveWithoutTty }, { ALL_AGENT_IDS, ACCOUNT_INSPECTION_AGENT_IDS, agentLabel, supportsAccountInspection }, { profileExists, readProfile, resolveProfileForRun },
1821
+ // Engine DATA ops resolve through the standalone `secrets` process client
1822
+ // (PHNX-3989); the agents-owned `bundle@host` policy helpers stay in agents-cli.
1823
+ { readAndResolveBundleEnv, describeBundle, remoteResolveEnv }, { assertRemoteBundleFlagsUnsupported, splitBundleRef, resolveSecretsContextForRun }, { resolveHostSshTarget }, { getConfiguredRunStrategy, normalizeRunStrategy, resolveRunVersion, rotationFailoverChain, shouldArmRotationFailover, RUN_STRATEGIES, collectHarnessCandidates, pickHarnessWeighted, classifyHarnessCandidates, formatHarnessPickBanner, formatNoHealthyHarnessError, formatNoHealthyAccountError, formatNoVerifiedUsageError, signInRecoverableCandidates }, { getGlobalDefault, getVersionHomePath, resolveVersion, resolveVersionAlias, ensureAgentRunnable }, { buildDiscoveredPlugin, loadPluginManifest, syncPluginToVersion }, { parseWorkflowFrontmatter, resolveWorkflowRef, resolveAllowedSubagents, pruneStaleWorkflowSubagents, ensureSubagentDispatchTool }, { resolveRunDefaults }, { getMcpServersByName, buildWorkflowMcpConfig }, { supports, capableAgents }, { shareRuntimeEnv },] = await Promise.all([
1804
1824
  import('../lib/exec.js'),
1805
1825
  import('../lib/agents.js'),
1806
1826
  import('../lib/profiles.js'),
1807
- import('../lib/secrets/bundles.js'),
1808
- import('../lib/secrets/remote.js'),
1827
+ import('../lib/secrets-client.js'),
1828
+ import('../lib/secrets-policy.js'),
1829
+ import('../lib/hosts/credential-transport.js'),
1809
1830
  import('../lib/accounting/rotate.js'),
1810
1831
  import('../lib/installations/versions.js'),
1811
1832
  import('../lib/plugins/plugins.js'),
@@ -1844,6 +1865,7 @@ agents run auto --device yosemite-s0 "fix the flaky test" # pin the device
1844
1865
  let profileEnv;
1845
1866
  let accountEnv;
1846
1867
  let accountConfigVersion;
1868
+ let execHome;
1847
1869
  let profileProvider;
1848
1870
  let fromProfile = false;
1849
1871
  let profileName;
@@ -2169,6 +2191,10 @@ agents run auto --device yosemite-s0 "fix the flaky test" # pin the device
2169
2191
  if (!selected)
2170
2192
  return;
2171
2193
  version = selected.version;
2194
+ if (selected.nativeAccount)
2195
+ options.account = selected.nativeAccount;
2196
+ if (selected.slotDir)
2197
+ execHome = selected.slotDir;
2172
2198
  if (!options.quiet) {
2173
2199
  const identity = selected.accountLabel || 'signed-in account';
2174
2200
  process.stderr.write(chalk.gray(`[agents] selected ${identity} · ${agent}@${selected.version} for this run\n`));
@@ -2204,35 +2230,49 @@ agents run auto --device yosemite-s0 "fix the flaky test" # pin the device
2204
2230
  process.exit(1);
2205
2231
  }
2206
2232
  if (spawnAccount.kind === 'native') {
2207
- // A native login is owned by the harness and read from its own home —
2208
- // it cannot be forwarded to another machine, and no secret/env is
2209
- // injected. Fail closed for a remote target.
2233
+ // `#name` / `--account` on a --device run is forwarded unchanged at
2234
+ // dispatch time; the peer resolves ITS slot. This block is local-only.
2210
2235
  const remoteTarget = options.host || options.device;
2211
- if (remoteTarget) {
2212
- console.error(chalk.red(`Account '${spawnAccount.name}' is a device-local ${spawnAccount.agent} login and cannot be forwarded to '${remoteTarget}'. Sign in on that device and name the login there.`));
2213
- process.exit(1);
2214
- }
2215
- const { CONFIG_ENV_ISOLATED_AGENTS } = await import('../lib/installations/shims.js');
2216
- if (!CONFIG_ENV_ISOLATED_AGENTS.includes(agent)) {
2217
- console.error(chalk.red(`${agent} cannot select native accounts independently of its installed version.`));
2218
- process.exit(1);
2219
- }
2220
- const { resolveAccountVersion } = await import('../lib/accounting/rotate.js');
2221
- const { nativeAccountHome } = await import('../lib/account-registry.js');
2222
- const { readMeta } = await import('../lib/state.js');
2223
- accountConfigVersion = await resolveAccountVersion(agent, spawnAccount.identityKey, nativeAccountHome(spawnAccount.id, readMeta())) ?? undefined;
2224
- if (!accountConfigVersion) {
2225
- console.error(chalk.red(`No installed ${spawnAccount.agent} version is signed in as the identity labeled '${spawnAccount.name}'. Sign in as that identity, or label a different account.`));
2226
- process.exit(1);
2236
+ if (!remoteTarget) {
2237
+ const { adoptedConfigPointsAtHome, adoptedSymlinkMismatchError, isSymlinkAdoptedHarness, resolveNativeSpawnHome, symlinkAdoptedAccountError, } = await import('../lib/exec-account-home.js');
2238
+ const { readMeta } = await import('../lib/state.js');
2239
+ const meta = readMeta();
2240
+ if (isSymlinkAdoptedHarness(agent)) {
2241
+ const defaultName = meta.accounts?.defaults?.[agent];
2242
+ if (spawnAccount.name !== defaultName) {
2243
+ console.error(chalk.red(symlinkAdoptedAccountError(agent, spawnAccount.name, defaultName)));
2244
+ process.exit(1);
2245
+ }
2246
+ }
2247
+ try {
2248
+ const resolved = await resolveNativeSpawnHome(agent, spawnAccount, meta);
2249
+ if (isSymlinkAdoptedHarness(agent) && !adoptedConfigPointsAtHome(agent, resolved.execHome)) {
2250
+ console.error(chalk.red(adoptedSymlinkMismatchError(agent, spawnAccount.name, resolved.execHome)));
2251
+ process.exit(1);
2252
+ }
2253
+ execHome = resolved.execHome;
2254
+ if (resolved.source === 'legacy-home') {
2255
+ accountConfigVersion = resolved.label;
2256
+ // A leftover version-labeled home is both the spawn HOME and
2257
+ // the account's installation — the pre-T5 `resolveAccountVersion`
2258
+ // behavior. An explicit `@<label>` still controls the binary.
2259
+ if (!version && !fromProfile && resolved.label)
2260
+ version = resolved.label;
2261
+ }
2262
+ }
2263
+ catch (err) {
2264
+ console.error(chalk.red(err.message));
2265
+ process.exit(1);
2266
+ }
2267
+ // Slot / provisioned: binary comes from the one managed install unless `@<label>` pins it.
2268
+ if (!version && !fromProfile) {
2269
+ const { ensureHarnessInstallation } = await import('../lib/installations/store.js');
2270
+ const { installation } = await ensureHarnessInstallation(agent);
2271
+ version = installation.label;
2272
+ }
2273
+ if (!options.quiet)
2274
+ process.stderr.write(chalk.gray(`[agents] account '${spawnAccount.name}' · ${agent}\n`));
2227
2275
  }
2228
- // An account-only run executes the account's own stable installation.
2229
- // This keeps its model/mode configuration and binary update policy
2230
- // together. An explicit installation or profile still controls the
2231
- // executable independently; configVersion continues to select auth.
2232
- if (!version && !fromProfile)
2233
- version = accountConfigVersion;
2234
- if (!options.quiet)
2235
- process.stderr.write(chalk.gray(`[agents] account '${spawnAccount.name}' · ${agent}\n`));
2236
2276
  }
2237
2277
  else {
2238
2278
  accountEnv = spawnAccount.env;
@@ -2713,10 +2753,12 @@ agents run auto --device yosemite-s0 "fix the flaky test" # pin the device
2713
2753
  authedViaSetupToken = resolveClaudeSetupToken(getVersionHomePath('claude', version)) !== null;
2714
2754
  }
2715
2755
  if (!info.signedIn && !authedViaSetupToken) {
2716
- const { connectSupported } = await import('../lib/accounts/connect.js');
2717
- const hint = connectSupported(agent)
2718
- ? `agents accounts connect ${agent}${spawnAccount?.kind === 'native' ? ` ${spawnAccount.name}` : ''}`
2719
- : loginHint(agent);
2756
+ const { addSupported } = await import('../lib/accounts/add.js');
2757
+ const hint = spawnAccount?.kind === 'native'
2758
+ ? `agents accounts login ${agent}#${spawnAccount.name}`
2759
+ : addSupported(agent)
2760
+ ? `agents accounts add ${agent} <name>`
2761
+ : loginHint(agent);
2720
2762
  process.stderr.write(chalk.yellow(`${agent} looks logged out — sign in with: ${hint}. Launching anyway...\n`));
2721
2763
  }
2722
2764
  }
@@ -2805,6 +2847,11 @@ agents run auto --device yosemite-s0 "fix the flaky test" # pin the device
2805
2847
  ? options.secretsKeys.split(',').map((k) => k.trim()).filter(Boolean)
2806
2848
  : undefined;
2807
2849
  let secretsEnv = {};
2850
+ // Resource-profile scoping (CTX-1): agents-cli computes which bundle
2851
+ // names the active profile allows and forwards it as the client's
2852
+ // `allowedBundles` on every local resolution — the standalone has no
2853
+ // concept of a profile. undefined (no active profile) is full trust.
2854
+ const secretsContext = await resolveSecretsContextForRun(agent);
2808
2855
  for (const bundleRef of options.secrets) {
2809
2856
  try {
2810
2857
  const { bundle: bundleName, host } = splitBundleRef(bundleRef);
@@ -2822,7 +2869,7 @@ agents run auto --device yosemite-s0 "fix the flaky test" # pin the device
2822
2869
  secretsEnv = { ...secretsEnv, ...bundleEnv };
2823
2870
  }
2824
2871
  else {
2825
- const { bundle, env: bundleEnv } = readAndResolveBundleEnv(bundleName, {
2872
+ const { bundle, env: bundleEnv } = await readAndResolveBundleEnv(bundleName, {
2826
2873
  caller: `agent ${agent}`,
2827
2874
  agent,
2828
2875
  keys: secretsKeysSubset,
@@ -2834,8 +2881,8 @@ agents run auto --device yosemite-s0 "fix the flaky test" # pin the device
2834
2881
  // isHeadlessSecretsContext() left `--interactive` launches (the watchdog's
2835
2882
  // `agents run auto --interactive`) able to prompt, piling up helper sheets.
2836
2883
  agentOnly: true,
2837
- });
2838
- const entries = describeBundle(bundle);
2884
+ }, secretsContext);
2885
+ const entries = await describeBundle(bundle, secretsContext);
2839
2886
  const counts = {};
2840
2887
  for (const e of entries) {
2841
2888
  counts[e.kind] = (counts[e.kind] || 0) + 1;
@@ -2881,6 +2928,7 @@ agents run auto --device yosemite-s0 "fix the flaky test" # pin the device
2881
2928
  harnessName: profileName,
2882
2929
  version,
2883
2930
  configVersion: accountConfigVersion,
2931
+ execHome,
2884
2932
  prompt,
2885
2933
  interactive: options.interactive || forceInteractive,
2886
2934
  mode: requestedMode,