@phnx-labs/agents-cli 1.22.83 → 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 (291) 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/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/sessions-trace.d.ts +11 -0
  28. package/dist/commands/sessions-trace.js +71 -0
  29. package/dist/commands/setup-accounts.d.ts +3 -2
  30. package/dist/commands/setup-accounts.js +4 -4
  31. package/dist/commands/setup-secrets.d.ts +16 -21
  32. package/dist/commands/setup-secrets.js +43 -224
  33. package/dist/commands/ssh.js +2 -2
  34. package/dist/commands/sync.js +1 -1
  35. package/dist/commands/update.d.ts +10 -0
  36. package/dist/commands/update.js +43 -40
  37. package/dist/commands/versions.d.ts +11 -11
  38. package/dist/commands/versions.js +131 -83
  39. package/dist/commands/view.d.ts +1 -9
  40. package/dist/commands/view.js +41 -55
  41. package/dist/commands/webhook.js +5 -5
  42. package/dist/commands/workflows.js +2 -1
  43. package/dist/index.d.ts +12 -12
  44. package/dist/index.js +13 -42
  45. package/dist/lib/account-capabilities.d.ts +1 -1
  46. package/dist/lib/account-capabilities.js +1 -1
  47. package/dist/lib/account-catalog.d.ts +121 -6
  48. package/dist/lib/account-catalog.js +421 -7
  49. package/dist/lib/account-registry.d.ts +8 -30
  50. package/dist/lib/account-registry.js +36 -79
  51. package/dist/lib/account-schema.d.ts +1 -1
  52. package/dist/lib/account-schema.js +1 -1
  53. package/dist/lib/accounting/account-pool-collect.js +16 -4
  54. package/dist/lib/accounting/rotate.d.ts +20 -7
  55. package/dist/lib/accounting/rotate.js +89 -13
  56. package/dist/lib/accounting/usage.js +11 -16
  57. package/dist/lib/accounts/add.d.ts +138 -0
  58. package/dist/lib/accounts/add.js +651 -0
  59. package/dist/lib/accounts/migrate.d.ts +117 -0
  60. package/dist/lib/accounts/migrate.js +536 -0
  61. package/dist/lib/accounts/slots.d.ts +13 -0
  62. package/dist/lib/accounts/slots.js +100 -0
  63. package/dist/lib/agent-spec/agents.d.ts +16 -0
  64. package/dist/lib/agent-spec/agents.js +32 -4
  65. package/dist/lib/app-bundle-install.js +5 -4
  66. package/dist/lib/auth-health.d.ts +3 -0
  67. package/dist/lib/auth-health.js +11 -0
  68. package/dist/lib/auth-mint.d.ts +52 -16
  69. package/dist/lib/auth-mint.js +117 -39
  70. package/dist/lib/browser/chrome.d.ts +1 -1
  71. package/dist/lib/browser/chrome.js +5 -5
  72. package/dist/lib/byok-usage.js +3 -3
  73. package/dist/lib/claude-account-token.d.ts +47 -3
  74. package/dist/lib/claude-account-token.js +172 -50
  75. package/dist/lib/cloud/antigravity.js +4 -4
  76. package/dist/lib/cloud/cursor.js +4 -4
  77. package/dist/lib/crabbox/cli.d.ts +1 -1
  78. package/dist/lib/crabbox/cli.js +6 -6
  79. package/dist/lib/crabbox/runtimes.d.ts +3 -3
  80. package/dist/lib/crabbox/runtimes.js +5 -5
  81. package/dist/lib/daemon/account-state-daemon-service.d.ts +37 -1
  82. package/dist/lib/daemon/account-state-daemon-service.js +199 -3
  83. package/dist/lib/daemon/auth-sync-service.js +28 -1
  84. package/dist/lib/daemon/daemon.d.ts +6 -16
  85. package/dist/lib/daemon/daemon.js +52 -97
  86. package/dist/lib/daemon/daemon.test-fixture.d.ts +7 -9
  87. package/dist/lib/daemon/daemon.test-fixture.js +22 -40
  88. package/dist/lib/daemon/harness-update-service.d.ts +1 -1
  89. package/dist/lib/daemon/harness-update-service.js +1 -1
  90. package/dist/lib/daemon/runner.d.ts +15 -3
  91. package/dist/lib/daemon/runner.js +49 -24
  92. package/dist/lib/daemon-health.d.ts +1 -2
  93. package/dist/lib/daemon-health.js +7 -6
  94. package/dist/lib/daemon-services.d.ts +1 -1
  95. package/dist/lib/daemon-services.js +1 -11
  96. package/dist/lib/daemon-ticks.js +23 -2
  97. package/dist/lib/daemon-webhooks.d.ts +8 -7
  98. package/dist/lib/daemon-webhooks.js +10 -9
  99. package/dist/lib/device-config.js +1 -1
  100. package/dist/lib/devices/doctor-findings.d.ts +1 -1
  101. package/dist/lib/devices/harness-inventory.d.ts +39 -0
  102. package/dist/lib/devices/harness-inventory.js +126 -4
  103. package/dist/lib/doctor-diff.js +2 -1
  104. package/dist/lib/exec-account-home.d.ts +38 -0
  105. package/dist/lib/exec-account-home.js +164 -0
  106. package/dist/lib/exec.d.ts +30 -0
  107. package/dist/lib/exec.js +101 -31
  108. package/dist/lib/feed/activity-stream.d.ts +60 -0
  109. package/dist/lib/feed/activity-stream.js +271 -0
  110. package/dist/lib/feed/activity.d.ts +7 -0
  111. package/dist/lib/feed/activity.js +10 -4
  112. package/dist/lib/feed/watch.d.ts +9 -3
  113. package/dist/lib/feed/watch.js +72 -36
  114. package/dist/lib/fleet/apply.d.ts +2 -2
  115. package/dist/lib/fleet/apply.js +4 -4
  116. package/dist/lib/fleet/auth-sync.js +4 -3
  117. package/dist/lib/fleet-shared-repo-sync.js +6 -9
  118. package/dist/lib/fleet-shared-state.d.ts +6 -0
  119. package/dist/lib/harness/adapter.d.ts +7 -7
  120. package/dist/lib/harness/adapter.js +10 -6
  121. package/dist/lib/harness/adapters/grok.js +8 -3
  122. package/dist/lib/harness/adapters/muse.js +1 -1
  123. package/dist/lib/harness/adapters/opencode.js +12 -3
  124. package/dist/lib/harness-auth-capabilities.d.ts +61 -0
  125. package/dist/lib/harness-auth-capabilities.js +52 -0
  126. package/dist/lib/helper-versions.d.ts +6 -4
  127. package/dist/lib/helper-versions.js +5 -4
  128. package/dist/lib/hosts/credential-transport.d.ts +10 -0
  129. package/dist/lib/hosts/credential-transport.js +37 -0
  130. package/dist/lib/hosts/dispatch.d.ts +7 -0
  131. package/dist/lib/hosts/dispatch.js +13 -6
  132. package/dist/lib/identity/client.d.ts +3 -3
  133. package/dist/lib/identity/client.js +3 -3
  134. package/dist/lib/installations/index.d.ts +1 -1
  135. package/dist/lib/installations/index.js +1 -1
  136. package/dist/lib/installations/migrate.js +9 -0
  137. package/dist/lib/installations/resolve.js +1 -1
  138. package/dist/lib/installations/shims.d.ts +15 -0
  139. package/dist/lib/installations/shims.js +69 -0
  140. package/dist/lib/installations/store.d.ts +43 -0
  141. package/dist/lib/installations/store.js +75 -7
  142. package/dist/lib/installations/versions.js +1 -1
  143. package/dist/lib/menubar/install-menubar.d.ts +3 -3
  144. package/dist/lib/menubar/install-menubar.js +3 -3
  145. package/dist/lib/native-accounts.d.ts +35 -0
  146. package/dist/lib/native-accounts.js +41 -0
  147. package/dist/lib/net-close.d.ts +11 -0
  148. package/dist/lib/net-close.js +25 -0
  149. package/dist/lib/openclaw-keychain.js +27 -1
  150. package/dist/lib/profiles.js +22 -9
  151. package/dist/lib/project-resources.js +1 -1
  152. package/dist/lib/reserved-stores.d.ts +53 -0
  153. package/dist/lib/reserved-stores.js +120 -0
  154. package/dist/lib/secrets-client.d.ts +191 -0
  155. package/dist/lib/secrets-client.js +710 -0
  156. package/dist/lib/secrets-policy.d.ts +222 -0
  157. package/dist/lib/secrets-policy.js +483 -0
  158. package/dist/lib/secrets-types.d.ts +188 -0
  159. package/dist/lib/secrets-types.js +24 -0
  160. package/dist/lib/service-manifest.js +3 -4
  161. package/dist/lib/session/active.d.ts +38 -4
  162. package/dist/lib/session/active.js +36 -4
  163. package/dist/lib/session/bash-command.js +10 -0
  164. package/dist/lib/session/db.d.ts +65 -2
  165. package/dist/lib/session/db.js +178 -18
  166. package/dist/lib/session/mirror.d.ts +5 -0
  167. package/dist/lib/session/mirror.js +166 -0
  168. package/dist/lib/session/parse.d.ts +49 -0
  169. package/dist/lib/session/parse.js +324 -30
  170. package/dist/lib/session/prompt.d.ts +93 -2
  171. package/dist/lib/session/prompt.js +266 -15
  172. package/dist/lib/session/remote/peer-stream.d.ts +47 -0
  173. package/dist/lib/session/remote/peer-stream.js +142 -0
  174. package/dist/lib/session/remote/watch.d.ts +1 -1
  175. package/dist/lib/session/remote/watch.js +41 -42
  176. package/dist/lib/session/session-cache.d.ts +9 -0
  177. package/dist/lib/session/session-cache.js +40 -2
  178. package/dist/lib/session/sync/config.js +2 -2
  179. package/dist/lib/session/timeline-pass.d.ts +129 -0
  180. package/dist/lib/session/timeline-pass.js +323 -0
  181. package/dist/lib/session/timeline.d.ts +182 -0
  182. package/dist/lib/session/timeline.js +636 -0
  183. package/dist/lib/session/types.d.ts +155 -1
  184. package/dist/lib/sha256-asset.d.ts +11 -16
  185. package/dist/lib/sha256-asset.js +11 -16
  186. package/dist/lib/share/config.js +14 -5
  187. package/dist/lib/signin-badge.d.ts +26 -0
  188. package/dist/lib/signin-badge.js +42 -0
  189. package/dist/lib/staleness/detectors/workflows.js +3 -105
  190. package/dist/lib/staleness/writers/workflows.js +2 -1
  191. package/dist/lib/state.d.ts +7 -4
  192. package/dist/lib/state.js +18 -7
  193. package/dist/lib/summarizer/pass.d.ts +2 -0
  194. package/dist/lib/summarizer/pass.js +14 -1
  195. package/dist/lib/summarizer/summarize.d.ts +7 -0
  196. package/dist/lib/summarizer/summarize.js +3 -0
  197. package/dist/lib/sync-umbrella.js +2 -2
  198. package/dist/lib/teams/agents.js +1 -1
  199. package/dist/lib/types.d.ts +74 -33
  200. package/dist/lib/view-types.d.ts +6 -3
  201. package/dist/lib/workflows-registry.d.ts +71 -0
  202. package/dist/lib/workflows-registry.js +280 -0
  203. package/dist/lib/workflows.d.ts +21 -21
  204. package/dist/lib/workflows.js +12 -327
  205. package/package.json +2 -3
  206. package/scripts/postinstall.js +4 -29
  207. package/dist/commands/secrets-import.d.ts +0 -18
  208. package/dist/commands/secrets-import.js +0 -74
  209. package/dist/commands/secrets-migrate.d.ts +0 -25
  210. package/dist/commands/secrets-migrate.js +0 -334
  211. package/dist/commands/secrets-rotate-passphrase.d.ts +0 -17
  212. package/dist/commands/secrets-rotate-passphrase.js +0 -96
  213. package/dist/commands/secrets-sync.d.ts +0 -11
  214. package/dist/commands/secrets-sync.js +0 -153
  215. package/dist/commands/secrets-vault.d.ts +0 -10
  216. package/dist/commands/secrets-vault.js +0 -130
  217. package/dist/commands/secrets.d.ts +0 -212
  218. package/dist/commands/secrets.js +0 -3030
  219. package/dist/lib/accounts/connect.d.ts +0 -170
  220. package/dist/lib/accounts/connect.js +0 -383
  221. package/dist/lib/daemon/keychain-reap-service.d.ts +0 -17
  222. package/dist/lib/daemon/keychain-reap-service.js +0 -32
  223. package/dist/lib/daemon/secrets-broker-service.d.ts +0 -21
  224. package/dist/lib/daemon/secrets-broker-service.js +0 -51
  225. package/dist/lib/secrets/agent.d.ts +0 -358
  226. package/dist/lib/secrets/agent.js +0 -1291
  227. package/dist/lib/secrets/audit.d.ts +0 -46
  228. package/dist/lib/secrets/audit.js +0 -101
  229. package/dist/lib/secrets/bundles.d.ts +0 -290
  230. package/dist/lib/secrets/bundles.js +0 -1547
  231. package/dist/lib/secrets/download-keychain.d.ts +0 -47
  232. package/dist/lib/secrets/download-keychain.js +0 -70
  233. package/dist/lib/secrets/drivers/rush.d.ts +0 -14
  234. package/dist/lib/secrets/drivers/rush.js +0 -90
  235. package/dist/lib/secrets/fallback.d.ts +0 -48
  236. package/dist/lib/secrets/fallback.js +0 -48
  237. package/dist/lib/secrets/filestore.d.ts +0 -222
  238. package/dist/lib/secrets/filestore.js +0 -1099
  239. package/dist/lib/secrets/headless.d.ts +0 -43
  240. package/dist/lib/secrets/headless.js +0 -56
  241. package/dist/lib/secrets/icloud-import.d.ts +0 -79
  242. package/dist/lib/secrets/icloud-import.js +0 -206
  243. package/dist/lib/secrets/index.d.ts +0 -451
  244. package/dist/lib/secrets/index.js +0 -1568
  245. package/dist/lib/secrets/install-helper.d.ts +0 -72
  246. package/dist/lib/secrets/install-helper.js +0 -245
  247. package/dist/lib/secrets/lease.d.ts +0 -25
  248. package/dist/lib/secrets/lease.js +0 -44
  249. package/dist/lib/secrets/linux.d.ts +0 -77
  250. package/dist/lib/secrets/linux.js +0 -393
  251. package/dist/lib/secrets/list-filter.d.ts +0 -109
  252. package/dist/lib/secrets/list-filter.js +0 -261
  253. package/dist/lib/secrets/mcp.d.ts +0 -93
  254. package/dist/lib/secrets/mcp.js +0 -211
  255. package/dist/lib/secrets/profiles.d.ts +0 -10
  256. package/dist/lib/secrets/profiles.js +0 -13
  257. package/dist/lib/secrets/push.d.ts +0 -133
  258. package/dist/lib/secrets/push.js +0 -273
  259. package/dist/lib/secrets/rc-hygiene.d.ts +0 -63
  260. package/dist/lib/secrets/rc-hygiene.js +0 -143
  261. package/dist/lib/secrets/read-backoff.d.ts +0 -27
  262. package/dist/lib/secrets/read-backoff.js +0 -64
  263. package/dist/lib/secrets/reaper.d.ts +0 -90
  264. package/dist/lib/secrets/reaper.js +0 -243
  265. package/dist/lib/secrets/remote.d.ts +0 -152
  266. package/dist/lib/secrets/remote.js +0 -344
  267. package/dist/lib/secrets/reserved-sync.d.ts +0 -66
  268. package/dist/lib/secrets/reserved-sync.js +0 -147
  269. package/dist/lib/secrets/scope.d.ts +0 -26
  270. package/dist/lib/secrets/scope.js +0 -29
  271. package/dist/lib/secrets/session-store.d.ts +0 -107
  272. package/dist/lib/secrets/session-store.js +0 -342
  273. package/dist/lib/secrets/sync-backend.d.ts +0 -48
  274. package/dist/lib/secrets/sync-backend.js +0 -13
  275. package/dist/lib/secrets/sync-commands.d.ts +0 -21
  276. package/dist/lib/secrets/sync-commands.js +0 -21
  277. package/dist/lib/secrets/sync.d.ts +0 -48
  278. package/dist/lib/secrets/sync.js +0 -237
  279. package/dist/lib/secrets/unlock-hints.d.ts +0 -27
  280. package/dist/lib/secrets/unlock-hints.js +0 -36
  281. package/dist/lib/secrets/usage-db.d.ts +0 -46
  282. package/dist/lib/secrets/usage-db.js +0 -96
  283. package/dist/lib/secrets/vault-age-helper.d.ts +0 -1
  284. package/dist/lib/secrets/vault-age-helper.js +0 -34
  285. package/dist/lib/secrets/vault.d.ts +0 -49
  286. package/dist/lib/secrets/vault.js +0 -399
  287. package/dist/lib/secrets/windows.d.ts +0 -81
  288. package/dist/lib/secrets/windows.js +0 -556
  289. package/scripts/install-helper.js +0 -97
  290. /package/dist/lib/{secrets/sync-passphrase.d.ts → sync-passphrase.d.ts} +0 -0
  291. /package/dist/lib/{secrets/sync-passphrase.js → sync-passphrase.js} +0 -0
@@ -1,19 +1,23 @@
1
1
  /**
2
- * The four config-dir env keys a harness pins to its version home. Every
3
- * per-harness branch in the old buildExecEnv deleted the ones it does NOT set,
4
- * so its own config pointer never leaks into a foreign harness's invocation.
5
- * (XDG_CONFIG_HOME / XDG_DATA_HOME — Muse/Cursor — are deliberately absent: the
6
- * old code sets them but never strips them, and this preserves that exactly.)
2
+ * Config-dir env keys a harness pins to its slot / version home. Every
3
+ * per-harness branch in buildExecEnv deletes the ones it does NOT set, so a
4
+ * slot never inherits another account's dir (PHNX-3940 T5). GROK_HOME,
5
+ * OPENCODE_CONFIG_DIR, and the XDG pair were previously omitted, which let a
6
+ * parent agent's pin leak into a sibling slot.
7
7
  */
8
8
  export const CONFIG_DIR_ENV_KEYS = [
9
9
  'CLAUDE_CONFIG_DIR',
10
10
  'CODEX_HOME',
11
11
  'COPILOT_HOME',
12
12
  'KIMI_CODE_HOME',
13
+ 'GROK_HOME',
14
+ 'OPENCODE_CONFIG_DIR',
15
+ 'XDG_CONFIG_HOME',
16
+ 'XDG_DATA_HOME',
13
17
  ];
14
18
  /**
15
19
  * Strip every config-dir env key except the one(s) this harness sets. `keep=[]`
16
- * (the default / no-config-dir harness) deletes all four — the old `else` arm.
20
+ * (the default / no-config-dir harness) deletes all of them — the old `else` arm.
17
21
  */
18
22
  export function stripForeignConfigDir(result, keep = []) {
19
23
  for (const key of CONFIG_DIR_ENV_KEYS) {
@@ -1,8 +1,13 @@
1
- // Grok has no exec-time config-dir env pin (buildExecEnv's `else` strips the
2
- // foreign keys — the registry fallback preserves that). It DOES relocate its
3
- // whole config tree via GROK_HOME in the generated shim.
1
+ import * as path from 'path';
2
+ import { stripForeignConfigDir } from '../adapter.js';
4
3
  export const grokAdapter = {
5
4
  id: 'grok',
5
+ applyExecConfigEnv(result, ctx) {
6
+ if (ctx.versionHome) {
7
+ result.GROK_HOME = path.join(ctx.versionHome, '.grok');
8
+ }
9
+ stripForeignConfigDir(result, ['GROK_HOME']);
10
+ },
6
11
  shimConfigEnvBash() {
7
12
  return `
8
13
  # Grok Build uses GROK_HOME to isolate its entire configuration tree
@@ -13,7 +13,7 @@ export const museAdapter = {
13
13
  result.XDG_CONFIG_HOME = path.join(ctx.versionHome, '.config');
14
14
  result.XDG_DATA_HOME = path.join(ctx.versionHome, '.local', 'share');
15
15
  }
16
- stripForeignConfigDir(result);
16
+ stripForeignConfigDir(result, ['XDG_CONFIG_HOME', 'XDG_DATA_HOME']);
17
17
  },
18
18
  shimConfigEnvBash() {
19
19
  return `
@@ -1,8 +1,17 @@
1
- // OpenCode has no exec-time config-dir env pin (buildExecEnv's `else` strips the
2
- // foreign keys — the registry fallback preserves that). It reads config-directory
3
- // resources from OPENCODE_CONFIG_DIR, pinned in the generated shim.
1
+ import * as path from 'path';
2
+ import { stripForeignConfigDir } from '../adapter.js';
3
+ // OpenCode reads plugins/agents/commands from OPENCODE_CONFIG_DIR and auth from
4
+ // $XDG_DATA_HOME/opencode (HARNESS_AUTH.slotEnv). Pin both at the slot / version
5
+ // home so two accounts in one install never share a credential (PHNX-3940 T5).
4
6
  export const opencodeAdapter = {
5
7
  id: 'opencode',
8
+ applyExecConfigEnv(result, ctx) {
9
+ if (ctx.versionHome) {
10
+ result.OPENCODE_CONFIG_DIR = path.join(ctx.versionHome, '.config', 'opencode');
11
+ result.XDG_DATA_HOME = path.join(ctx.versionHome, '.local', 'share');
12
+ }
13
+ stripForeignConfigDir(result, ['OPENCODE_CONFIG_DIR', 'XDG_DATA_HOME']);
14
+ },
6
15
  shimConfigEnvBash() {
7
16
  return `
8
17
  # OpenCode reads plugins, agents, commands, and other config-directory
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Per-harness auth capability table (PHNX-3940).
3
+ *
4
+ * One row per `ALL_AGENT_IDS` entry. Governs `accounts add` / connect login
5
+ * argv, identity strength, the durable worker credential (if any), and the
6
+ * config-dir env that pins a slot. Completeness is pinned by the test against
7
+ * `ALL_AGENT_IDS` — a new harness without a row is a type error and a failing
8
+ * test, never a silent skip.
9
+ *
10
+ * Values are taken from evidence-harness-auth.md (2026-09-06) and the adapters
11
+ * under `lib/harness/adapters/`. `LOGIN_INVOCATIONS` is the connect-era subset
12
+ * (claude, codex) derived from this table so existing connect callers keep
13
+ * working until T4 folds connect into `accounts add`.
14
+ */
15
+ import type { AgentId } from './types.js';
16
+ export type HarnessIdentityKind = 'strong' | 'email' | 'opaque';
17
+ /**
18
+ * Durable worker credential, or `none` when the harness must log in per box.
19
+ * Codex is both: an API key (portable, bills the API) OR a per-device
20
+ * ChatGPT-plan device-auth login (the plan seat; never stored in the reserved
21
+ * store because it is a rotating session).
22
+ */
23
+ export type HarnessWorkerKind = 'setup-token' | 'none' | `api-key:${string}` | `per-device${'' | `:${string}`}`;
24
+ export type HarnessWorker = HarnessWorkerKind | HarnessWorkerKind[];
25
+ export interface HarnessAuthCapability {
26
+ /** argv after the harness binary to start native login, or null when there is no finite login command. */
27
+ login: string[] | null;
28
+ /** argv to probe login status, or null when the CLI has no status command. */
29
+ status: string[] | null;
30
+ identity: HarnessIdentityKind;
31
+ worker: HarnessWorker;
32
+ /** Config-dir env that pins a slot, or null when isolation is HOME-swap only. */
33
+ slotEnv: string | null;
34
+ }
35
+ export declare const HARNESS_AUTH: Record<AgentId, HarnessAuthCapability>;
36
+ export declare function harnessAuth(agent: AgentId): HarnessAuthCapability;
37
+ /** Worker kinds as a flat list (codex's dual path is two entries). */
38
+ export declare function harnessWorkerKinds(agent: AgentId): HarnessWorkerKind[];
39
+ /**
40
+ * True when this harness has no portable worker credential and must log in
41
+ * per box. `worker: 'none'` (kimi, antigravity, …) and a sole `per-device…`
42
+ * kind both count; a dual path like Codex (API key OR device-auth) does not.
43
+ */
44
+ export declare function harnessWorkerIsPerDevice(agent: AgentId): boolean;
45
+ /**
46
+ * Native-login invocation per harness. Only harnesses with a REAL, finite login
47
+ * COMMAND that connect currently drives — connect fails clearly for anything
48
+ * else rather than faking a flow that never signs the user in. Verified against
49
+ * the installed CLIs (PHNX-3940): `claude auth login --help` → "Sign in to your
50
+ * Anthropic account" with `--email`; `codex login` drives the OAuth flow.
51
+ * Args are the `HARNESS_AUTH.login` values for the same ids.
52
+ */
53
+ export interface LoginInvocation {
54
+ /** argv passed to the installed binary to start the native login. */
55
+ args: string[];
56
+ /** Flag that pre-fills the login email (appended as `[emailFlag, email]`), when supported. */
57
+ emailFlag?: string;
58
+ /** One-line hint shown before the login flow takes over. */
59
+ hint?: string;
60
+ }
61
+ export declare const LOGIN_INVOCATIONS: Partial<Record<AgentId, LoginInvocation>>;
@@ -0,0 +1,52 @@
1
+ export const HARNESS_AUTH = {
2
+ claude: { login: ['auth', 'login'], status: ['auth', 'status'], identity: 'strong', worker: 'setup-token', slotEnv: 'CLAUDE_CONFIG_DIR' },
3
+ codex: { login: ['login'], status: ['login', 'status'], identity: 'strong', worker: ['api-key:OPENAI_API_KEY', 'per-device:device-auth'], slotEnv: 'CODEX_HOME' },
4
+ grok: { login: ['login'], status: null, identity: 'strong', worker: 'api-key:XAI_API_KEY', slotEnv: 'GROK_HOME' },
5
+ // auth.json has no email claim; identity is the sorted provider-id join
6
+ // (`resolveOpenCodeAccountId`). NATIVE_ACCOUNT_CAPABILITIES.opencode.inspection
7
+ // is already 'opaque'.
8
+ opencode: { login: ['auth', 'login'], status: ['auth', 'list'], identity: 'opaque', worker: 'api-key:provider', slotEnv: 'XDG_DATA_HOME' },
9
+ cursor: { login: ['login'], status: ['status'], identity: 'strong', worker: 'api-key:CURSOR_API_KEY', slotEnv: null },
10
+ // No finite login argv — launch bare `kimi`, then `/login` in the TUI
11
+ // (`loginHint('kimi') === 'kimi'`, fleet/auth-sync `loginCommand: 'kimi'`).
12
+ kimi: { login: null, status: null, identity: 'opaque', worker: 'none', slotEnv: 'KIMI_CODE_HOME' },
13
+ antigravity: { login: null, status: null, identity: 'opaque', worker: 'none', slotEnv: null },
14
+ droid: { login: null, status: null, identity: 'opaque', worker: 'api-key:FACTORY_API_KEY', slotEnv: null },
15
+ gemini: { login: null, status: null, identity: 'email', worker: 'none', slotEnv: null },
16
+ copilot: { login: null, status: null, identity: 'opaque', worker: 'none', slotEnv: 'COPILOT_HOME' },
17
+ openclaw: { login: null, status: null, identity: 'opaque', worker: 'none', slotEnv: null },
18
+ amp: { login: null, status: null, identity: 'opaque', worker: 'none', slotEnv: null },
19
+ goose: { login: null, status: null, identity: 'opaque', worker: 'none', slotEnv: null },
20
+ hermes: { login: null, status: null, identity: 'opaque', worker: 'none', slotEnv: null },
21
+ // Auth lives under $XDG_CONFIG_HOME/muse (adapter pins XDG_CONFIG_HOME +
22
+ // XDG_DATA_HOME). slotEnv names the config pin; T5 must not HOME-swap.
23
+ muse: { login: null, status: null, identity: 'email', worker: 'none', slotEnv: 'XDG_CONFIG_HOME' },
24
+ warp: { login: null, status: null, identity: 'opaque', worker: 'none', slotEnv: null },
25
+ };
26
+ export function harnessAuth(agent) {
27
+ const cap = HARNESS_AUTH[agent];
28
+ if (!cap)
29
+ throw new Error(`No harness-auth capability for '${agent}'.`);
30
+ return cap;
31
+ }
32
+ /** Worker kinds as a flat list (codex's dual path is two entries). */
33
+ export function harnessWorkerKinds(agent) {
34
+ const worker = harnessAuth(agent).worker;
35
+ return Array.isArray(worker) ? worker : [worker];
36
+ }
37
+ /**
38
+ * True when this harness has no portable worker credential and must log in
39
+ * per box. `worker: 'none'` (kimi, antigravity, …) and a sole `per-device…`
40
+ * kind both count; a dual path like Codex (API key OR device-auth) does not.
41
+ */
42
+ export function harnessWorkerIsPerDevice(agent) {
43
+ return harnessWorkerKinds(agent).every((kind) => kind === 'none' || kind.startsWith('per-device'));
44
+ }
45
+ export const LOGIN_INVOCATIONS = {
46
+ claude: {
47
+ args: HARNESS_AUTH.claude.login,
48
+ emailFlag: '--email',
49
+ hint: 'Complete the Claude sign-in in your browser.',
50
+ },
51
+ codex: { args: HARNESS_AUTH.codex.login },
52
+ };
@@ -27,7 +27,7 @@
27
27
  * never be derived from `getCliVersion()`.
28
28
  */
29
29
  /** The helpers that have their own release train. */
30
- export type HelperName = 'menubar' | 'keychain' | 'computer-mac' | 'computer-win';
30
+ export type HelperName = 'menubar' | 'computer-mac' | 'computer-win';
31
31
  /** One helper's release identity. */
32
32
  export interface HelperRelease {
33
33
  /** Tag prefix — the release is `<tagPrefix>/v<version>`. */
@@ -41,9 +41,11 @@ export interface HelperRelease {
41
41
  /**
42
42
  * Floors, by helper.
43
43
  *
44
- * `menubar` and `keychain` start at 1.0.0 — the first builds published under
45
- * their own tags rather than the CLI's. They are not "version 1 of the helper";
46
- * they are version 1 of its independent release train.
44
+ * `menubar` starts at 1.0.0 — the first build published under its own tag
45
+ * rather than the CLI's. It is not "version 1 of the helper"; it is version 1
46
+ * of its independent release train. The keychain helper moved with the
47
+ * standalone `secrets` engine (PHNX-3989) — it downloads and verifies its own
48
+ * helper release now, off this table entirely.
47
49
  */
48
50
  export declare const HELPER_RELEASES: Readonly<Record<HelperName, HelperRelease>>;
49
51
  /** The release tag for one helper at one version, e.g. `menubar/v1.0.0`. */
@@ -29,13 +29,14 @@
29
29
  /**
30
30
  * Floors, by helper.
31
31
  *
32
- * `menubar` and `keychain` start at 1.0.0 — the first builds published under
33
- * their own tags rather than the CLI's. They are not "version 1 of the helper";
34
- * they are version 1 of its independent release train.
32
+ * `menubar` starts at 1.0.0 — the first build published under its own tag
33
+ * rather than the CLI's. It is not "version 1 of the helper"; it is version 1
34
+ * of its independent release train. The keychain helper moved with the
35
+ * standalone `secrets` engine (PHNX-3989) — it downloads and verifies its own
36
+ * helper release now, off this table entirely.
35
37
  */
36
38
  export const HELPER_RELEASES = {
37
39
  menubar: { tagPrefix: 'menubar', floor: '1.0.0' },
38
- keychain: { tagPrefix: 'keychain', floor: '1.0.0' },
39
40
  'computer-mac': { tagPrefix: 'computer-mac', floor: '1.0.0' },
40
41
  // The Windows helper is a bare .exe, not an .app bundle, so it does not share
41
42
  // helper-download.ts's zip/codesign/notarize machinery -- but it has the same
@@ -0,0 +1,10 @@
1
+ /** The host part of an ssh target (`user@host` -> `host`) for known_hosts matching. */
2
+ export declare function hostKeyLookupName(target: string): string;
3
+ /** Refuse durable credential transfer until the destination SSH key is pinned. */
4
+ export declare function assertCredentialTransportHostPinned(target: string, pinned?: boolean): void;
5
+ /**
6
+ * Resolve a `--device` value to an ssh target string for a credential-carrying
7
+ * remote operation. Delegates to the same resolver `run --device` uses; on a
8
+ * miss, treats the value as a raw ssh target and validates it.
9
+ */
10
+ export declare function resolveHostSshTarget(nameOrAlias: string): Promise<string>;
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Credential transport policy for a fleet host (agents-owned, PHNX-3989).
3
+ *
4
+ * Whether a durable credential may leave this device for a given host is
5
+ * agents fleet policy, not a secrets-engine concern: it hangs off the managed
6
+ * `known_hosts` pin and the hosts/devices registry. The standalone `secrets`
7
+ * CLI only carries the bundle once agents-cli has decided the destination is
8
+ * trustworthy, so these guards live here and are passed INTO the transport,
9
+ * never re-implemented on the engine side.
10
+ */
11
+ import { assertValidSshTarget } from '../ssh-exec.js';
12
+ import { resolveHost } from './registry.js';
13
+ import { sshTargetFor } from './types.js';
14
+ import { isHostPinned } from '../devices/known-hosts.js';
15
+ /** The host part of an ssh target (`user@host` -> `host`) for known_hosts matching. */
16
+ export function hostKeyLookupName(target) {
17
+ return target.split('@').pop() ?? target;
18
+ }
19
+ /** Refuse durable credential transfer until the destination SSH key is pinned. */
20
+ export function assertCredentialTransportHostPinned(target, pinned = isHostPinned(hostKeyLookupName(target))) {
21
+ if (pinned)
22
+ return;
23
+ throw new Error(`Refusing to transfer provider credentials to '${target}' before its SSH host key is pinned. ` +
24
+ `Connect once with 'agents ssh ${target}' and verify the host, then retry.`);
25
+ }
26
+ /**
27
+ * Resolve a `--device` value to an ssh target string for a credential-carrying
28
+ * remote operation. Delegates to the same resolver `run --device` uses; on a
29
+ * miss, treats the value as a raw ssh target and validates it.
30
+ */
31
+ export async function resolveHostSshTarget(nameOrAlias) {
32
+ const host = await resolveHost(nameOrAlias);
33
+ if (host)
34
+ return sshTargetFor(host);
35
+ assertValidSshTarget(nameOrAlias);
36
+ return nameOrAlias;
37
+ }
@@ -170,6 +170,13 @@ export interface DispatchOptions {
170
170
  * (remote-cmd.ts) — keep the two in lockstep; run-forwarding.test.ts asserts
171
171
  * the table side.
172
172
  */
173
+ /** Compose `agent[@version][#account]` so the peer resolves ITS slot (PHNX-3940 T5). */
174
+ export declare function runAgentSpecArg(opts: {
175
+ agent: string;
176
+ version?: string;
177
+ account?: string;
178
+ accountPicker?: boolean;
179
+ }): string;
173
180
  export declare function buildRunForwardedArgs(opts: DispatchOptions): string[];
174
181
  export interface InteractiveDispatchOptions {
175
182
  agent: string;
@@ -359,8 +359,19 @@ async function launchDetached(host, target, opts) {
359
359
  * (remote-cmd.ts) — keep the two in lockstep; run-forwarding.test.ts asserts
360
360
  * the table side.
361
361
  */
362
+ /** Compose `agent[@version][#account]` so the peer resolves ITS slot (PHNX-3940 T5). */
363
+ export function runAgentSpecArg(opts) {
364
+ if (opts.accountPicker)
365
+ return `${opts.agent}@`;
366
+ let spec = opts.agent;
367
+ if (opts.version)
368
+ spec += `@${opts.version}`;
369
+ if (opts.account)
370
+ spec += `#${opts.account}`;
371
+ return spec;
372
+ }
362
373
  export function buildRunForwardedArgs(opts) {
363
- const agentArg = opts.version ? `${opts.agent}@${opts.version}` : opts.agent;
374
+ const agentArg = runAgentSpecArg(opts);
364
375
  const args = ['run', agentArg, opts.prompt, '--quiet'];
365
376
  if (opts.mode)
366
377
  args.push('--mode', opts.mode);
@@ -427,11 +438,7 @@ export function buildInteractiveRunForwardedArgs(opts) {
427
438
  if (opts.version && opts.accountPicker) {
428
439
  throw new Error('Interactive host dispatch cannot combine an account picker with a version pin');
429
440
  }
430
- const agentArg = opts.accountPicker
431
- ? `${opts.agent}@`
432
- : opts.version
433
- ? `${opts.agent}@${opts.version}`
434
- : opts.agent;
441
+ const agentArg = runAgentSpecArg(opts);
435
442
  const args = ['run', agentArg];
436
443
  if (opts.prompt && opts.forceInteractive)
437
444
  args.push(opts.prompt);
@@ -10,9 +10,9 @@
10
10
  * here and nothing else.
11
11
  *
12
12
  * The shape mirrors the seams this repo already proved elsewhere —
13
- * `SyncBackend` (`lib/secrets/sync-backend.ts`) and `CloudProvider`
14
- * (`lib/cloud/types.ts`) — so a second identity backend, if one is ever
15
- * needed, is a swap here rather than a sweep across commands.
13
+ * the bounded secrets process client (`lib/secrets-client.ts`) and
14
+ * `CloudProvider` (`lib/cloud/types.ts`) — so a second identity backend, if
15
+ * one is ever needed, is a swap here rather than a sweep across commands.
16
16
  */
17
17
  /**
18
18
  * Where the account backend lives. Config, never a literal at a call site.
@@ -10,9 +10,9 @@
10
10
  * here and nothing else.
11
11
  *
12
12
  * The shape mirrors the seams this repo already proved elsewhere —
13
- * `SyncBackend` (`lib/secrets/sync-backend.ts`) and `CloudProvider`
14
- * (`lib/cloud/types.ts`) — so a second identity backend, if one is ever
15
- * needed, is a swap here rather than a sweep across commands.
13
+ * the bounded secrets process client (`lib/secrets-client.ts`) and
14
+ * `CloudProvider` (`lib/cloud/types.ts`) — so a second identity backend, if
15
+ * one is ever needed, is a swap here rather than a sweep across commands.
16
16
  */
17
17
  import * as fs from 'fs';
18
18
  import * as path from 'path';
@@ -8,7 +8,7 @@
8
8
  * per-installation isolation track consumes exactly this).
9
9
  */
10
10
  export { type Installation, type InstallationRelease, type UpdateOutcome, type UpdateStrategyId, type UpdatePolicy, } from './types.js';
11
- export { createInstallation, listInstallations, recordRelease, } from './store.js';
11
+ export { createInstallation, listInstallations, recordRelease, MANAGED_INSTALLATION_LABEL, resolveManagedInstallation, ensureHarnessInstallation, type EnsureHarnessInstallationResult, } from './store.js';
12
12
  export { describeInstallation, resolveInstallation, type ResolveInstallationOptions, } from './resolve.js';
13
13
  export { selectUpdateStrategy, supportsPinnedUpdate, type UpdateStrategy, } from './strategies.js';
14
14
  export { updateInstallation, type UpdateInstallationOptions } from './update.js';
@@ -7,7 +7,7 @@
7
7
  * it, so the internal split can change without breaking callers (the Cursor
8
8
  * per-installation isolation track consumes exactly this).
9
9
  */
10
- export { createInstallation, listInstallations, recordRelease, } from './store.js';
10
+ export { createInstallation, listInstallations, recordRelease, MANAGED_INSTALLATION_LABEL, resolveManagedInstallation, ensureHarnessInstallation, } from './store.js';
11
11
  export { describeInstallation, resolveInstallation, } from './resolve.js';
12
12
  export { selectUpdateStrategy, supportsPinnedUpdate, } from './strategies.js';
13
13
  export { updateInstallation } from './update.js';
@@ -2366,4 +2366,13 @@ export async function runMigration() {
2366
2366
  catch {
2367
2367
  // best-effort — a migration must never fail because tmux wasn't reachable
2368
2368
  }
2369
+ // PHNX-3940 T7: report leftover per-account installations. Dry-run only —
2370
+ // moving homes requires an explicit `agents accounts migrate --apply`.
2371
+ try {
2372
+ const { reportAccountSlotMigrationOnUpgrade } = await import('../accounts/migrate.js');
2373
+ await reportAccountSlotMigrationOnUpgrade();
2374
+ }
2375
+ catch {
2376
+ // best-effort — a corrupt install must not block the rest of upgrade
2377
+ }
2369
2378
  }
@@ -21,7 +21,7 @@ export class InstallationNotFoundError extends Error {
21
21
  // Nothing installed is a different problem from "your selector missed", and
22
22
  // the remedy differs — say which one it is rather than printing an empty list.
23
23
  super(available.length === 0
24
- ? `No ${AGENTS[agent].name} installations are managed by agents-cli. Install one with: agents add ${agent}@latest`
24
+ ? `No ${AGENTS[agent].name} installations are managed by agents-cli. Install one with: agents add ${agent}`
25
25
  : `No ${AGENTS[agent].name} installation matches '${selector}'. Installed: ${available.map((i) => describeInstallation(i)).join(', ')}`);
26
26
  this.agent = agent;
27
27
  this.selector = selector;
@@ -201,6 +201,21 @@ export declare const CONFIG_ENV_ISOLATED_AGENTS: readonly AgentId[];
201
201
  * separate from the user's real `~/.<agent>`. See {@link CONFIG_ENV_ISOLATED_AGENTS}.
202
202
  */
203
203
  export declare function supportsIsolatedInstall(agent: AgentId): boolean;
204
+ /**
205
+ * Harnesses that isolate by symlink-adopting `~/.<config>` rather than a
206
+ * config-dir env (PHNX-3940 T5). One active slot per device; `accounts default`
207
+ * repoints the adopted symlink under the auth-op lock.
208
+ */
209
+ export declare function isSymlinkAdoptedHarness(agent: AgentId): boolean;
210
+ /**
211
+ * Repoint this harness's adopted `~/.<config>` symlink at `home`'s config dir.
212
+ * No-op for env-isolated harnesses. Fails loud when the adopted path is a real
213
+ * directory rather than a symlink — adopting that is `agents use`, not default.
214
+ */
215
+ export declare function repointAdoptedConfigToHome(agent: AgentId, home: string): {
216
+ success: boolean;
217
+ error?: string;
218
+ };
204
219
  /**
205
220
  * Generate a versioned alias script that directly execs a specific version.
206
221
  * e.g., claude@2.0.65 -> directly runs that version's binary
@@ -10,6 +10,7 @@ import { IS_WINDOWS, prependToWindowsUserPath } from '../platform/index.js';
10
10
  import { getShimsDir, getVersionsDir, getBackupsDir, getHistoryDir, ensureAgentsDir } from '../state.js';
11
11
  export { getShimsDir };
12
12
  import { AGENTS, agentConfigDirName, readAuthAccountIdentity } from '../agents.js';
13
+ import { acquireAuthOperationLock } from '../accounts/auth-operation-lock.js';
13
14
  import { codexHomeShimBash } from '../codex-home.js';
14
15
  import { resolveHarnessAdapter } from '../harness/index.js';
15
16
  import { randomUUID } from 'node:crypto';
@@ -1130,6 +1131,74 @@ export const CONFIG_ENV_ISOLATED_AGENTS = ['claude', 'codex', 'copilot', 'cursor
1130
1131
  export function supportsIsolatedInstall(agent) {
1131
1132
  return CONFIG_ENV_ISOLATED_AGENTS.includes(agent);
1132
1133
  }
1134
+ /**
1135
+ * Harnesses that isolate by symlink-adopting `~/.<config>` rather than a
1136
+ * config-dir env (PHNX-3940 T5). One active slot per device; `accounts default`
1137
+ * repoints the adopted symlink under the auth-op lock.
1138
+ */
1139
+ export function isSymlinkAdoptedHarness(agent) {
1140
+ return !CONFIG_ENV_ISOLATED_AGENTS.includes(agent);
1141
+ }
1142
+ /**
1143
+ * Repoint this harness's adopted `~/.<config>` symlink at `home`'s config dir.
1144
+ * No-op for env-isolated harnesses. Fails loud when the adopted path is a real
1145
+ * directory rather than a symlink — adopting that is `agents use`, not default.
1146
+ */
1147
+ export function repointAdoptedConfigToHome(agent, home) {
1148
+ if (!isSymlinkAdoptedHarness(agent))
1149
+ return { success: true };
1150
+ const lock = acquireAuthOperationLock(agent);
1151
+ try {
1152
+ lock.assertHeld();
1153
+ const configPath = getAgentConfigPath(agent);
1154
+ const configDirName = path.relative(os.homedir(), AGENTS[agent].configDir);
1155
+ const target = path.join(home, configDirName);
1156
+ fs.mkdirSync(target, { recursive: true });
1157
+ try {
1158
+ const stat = fs.lstatSync(configPath);
1159
+ if (stat.isSymbolicLink()) {
1160
+ const current = path.resolve(path.dirname(configPath), fs.readlinkSync(configPath));
1161
+ if (current === path.resolve(target))
1162
+ return { success: true };
1163
+ }
1164
+ else {
1165
+ return {
1166
+ success: false,
1167
+ error: `${configPath} is not a symlink; refusing to replace a real config directory. `
1168
+ + `Run \`agents use ${agent}\` once to adopt it, then \`agents accounts default ${agent}\`.`,
1169
+ };
1170
+ }
1171
+ }
1172
+ catch (err) {
1173
+ if (err.code !== 'ENOENT')
1174
+ throw err;
1175
+ }
1176
+ fs.mkdirSync(path.dirname(configPath), { recursive: true });
1177
+ // Atomic retarget: create temp symlink, rename over existing (same pattern
1178
+ // as switchHomeFileSymlinks). A window of "path missing" would let a
1179
+ // harness process observe neither the old nor the new target.
1180
+ const tmpPath = `${configPath}.agents-tmp-${process.pid}`;
1181
+ try {
1182
+ fs.unlinkSync(tmpPath);
1183
+ }
1184
+ catch { /* leftover from a killed prior swap */ }
1185
+ try {
1186
+ fs.symlinkSync(target, tmpPath, process.platform === 'win32' ? 'junction' : undefined);
1187
+ fs.renameSync(tmpPath, configPath);
1188
+ }
1189
+ catch (err) {
1190
+ try {
1191
+ fs.unlinkSync(tmpPath);
1192
+ }
1193
+ catch { /* rename may have already consumed it */ }
1194
+ throw err;
1195
+ }
1196
+ return { success: true };
1197
+ }
1198
+ finally {
1199
+ lock.release();
1200
+ }
1201
+ }
1133
1202
  /**
1134
1203
  * Generate a versioned alias script that directly execs a specific version.
1135
1204
  * e.g., claude@2.0.65 -> directly runs that version's binary
@@ -60,6 +60,42 @@ export declare function listInstallationLabels(agent: AgentId): string[];
60
60
  * that disappears mid-scan is skipped rather than failing the whole listing.
61
61
  */
62
62
  export declare function listInstallations(agent: AgentId): Installation[];
63
+ /**
64
+ * The label of the single managed installation every harness gets under the
65
+ * account model v2 (PHNX-3940). One managed install per harness per device;
66
+ * accounts live in credential slots beside it, and a second home is an expert
67
+ * `--isolated` copy, never another managed install.
68
+ */
69
+ export declare const MANAGED_INSTALLATION_LABEL = "main";
70
+ /**
71
+ * Resolve the ONE managed installation of a harness, or null when the harness
72
+ * has no managed install yet. The winner is deterministic: the `main` label
73
+ * first, then the recorded global default (the install the user actually
74
+ * launches), then the first remaining non-isolated installation by label.
75
+ * Isolated copies (`agents add --isolated`) never count — they are expert
76
+ * second homes, not the managed install.
77
+ */
78
+ export declare function resolveManagedInstallation(agent: AgentId): Installation | null;
79
+ export interface EnsureHarnessInstallationResult {
80
+ installation: Installation;
81
+ /** True when this call installed the harness just now; false when the managed installation already existed and was reused unchanged. */
82
+ installed: boolean;
83
+ }
84
+ /**
85
+ * The taught entry point for "make this harness runnable" (PHNX-3940): return
86
+ * the harness's single managed installation, installing the current release
87
+ * into the `main` label when the harness is not installed yet. `release` pins
88
+ * the fresh install to a concrete release (recorded as a pinned update policy
89
+ * by `installVersion`); it has no effect when an installation already exists —
90
+ * moving an existing install is `agents update <agent> --to <release>`'s job.
91
+ *
92
+ * Throws (fail loud) when the install fails; never returns a half-installed
93
+ * record.
94
+ */
95
+ export declare function ensureHarnessInstallation(agent: AgentId, opts?: {
96
+ release?: string;
97
+ onProgress?: (message: string) => void;
98
+ }): Promise<EnsureHarnessInstallationResult>;
63
99
  /**
64
100
  * Get the directory where a specific version is installed.
65
101
  */
@@ -154,6 +190,13 @@ export declare function pickCanonicalGlobalBinaryVersion(agent: AgentId, version
154
190
  * For a self-updating global-binary agent (droid) every version dir resolves to
155
191
  * the SAME binary, so this collapses them to a single canonical entry — one
156
192
  * install, one row in `agents view`, never the phantom set of semver dir names.
193
+ *
194
+ * A directory is an installation only when it has a working launch binary.
195
+ * Account credential slots (PHNX-3940, `~/.agents/.history/accounts/**`) are
196
+ * HOME-shaped dirs with no binary and no `installation.json`, so they can
197
+ * never appear here. Record-less pre-frozen installs are reported as-is; their
198
+ * migration belongs to the record-based paths (`listInstallations`), never to
199
+ * this read surface.
157
200
  */
158
201
  export declare function listInstalledVersions(agent: AgentId): string[];
159
202
  /**