@phnx-labs/agents-cli 1.22.114 → 1.22.116

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 (243) hide show
  1. package/CHANGELOG.md +245 -0
  2. package/README.md +22 -102
  3. package/dist/browser.js +0 -0
  4. package/dist/cli/command-registry.js +0 -7
  5. package/dist/commands/accounts.d.ts +3 -77
  6. package/dist/commands/accounts.js +13 -366
  7. package/dist/commands/audit.js +1 -1
  8. package/dist/commands/auth.js +0 -2
  9. package/dist/commands/browser.d.ts +53 -0
  10. package/dist/commands/browser.js +210 -9
  11. package/dist/commands/doctor.d.ts +2 -3
  12. package/dist/commands/doctor.js +0 -11
  13. package/dist/commands/events.d.ts +1 -3
  14. package/dist/commands/events.js +4 -7
  15. package/dist/commands/exec.d.ts +0 -1
  16. package/dist/commands/exec.js +214 -148
  17. package/dist/commands/feed-watch.js +52 -8
  18. package/dist/commands/feed.js +65 -14
  19. package/dist/commands/focus.js +1 -1
  20. package/dist/commands/go.d.ts +0 -17
  21. package/dist/commands/go.js +2 -19
  22. package/dist/commands/logs.js +1 -1
  23. package/dist/commands/mcp.js +8 -83
  24. package/dist/commands/memory.js +4 -47
  25. package/dist/commands/message.js +4 -4
  26. package/dist/commands/plugins.js +1 -93
  27. package/dist/commands/repo.js +0 -44
  28. package/dist/commands/resume.js +4 -1
  29. package/dist/commands/secrets-passthrough.js +2 -2
  30. package/dist/commands/send.d.ts +4 -4
  31. package/dist/commands/send.js +6 -51
  32. package/dist/commands/sessions-backup-setup.js +1 -1
  33. package/dist/commands/sessions-picker.d.ts +11 -0
  34. package/dist/commands/sessions-picker.js +88 -7
  35. package/dist/commands/sessions-resume.js +0 -1
  36. package/dist/commands/sessions-share.d.ts +5 -7
  37. package/dist/commands/sessions-share.js +98 -49
  38. package/dist/commands/sessions.d.ts +21 -2
  39. package/dist/commands/sessions.js +158 -11
  40. package/dist/commands/setup-browser.js +18 -2
  41. package/dist/commands/setup-computer.js +20 -6
  42. package/dist/commands/setup-secrets.d.ts +2 -2
  43. package/dist/commands/setup-secrets.js +24 -4
  44. package/dist/commands/setup-term.d.ts +24 -0
  45. package/dist/commands/setup-term.js +70 -0
  46. package/dist/commands/setup-terminal.d.ts +3 -0
  47. package/dist/commands/setup-terminal.js +22 -0
  48. package/dist/commands/setup.d.ts +1 -1
  49. package/dist/commands/setup.js +29 -11
  50. package/dist/commands/skills.js +0 -8
  51. package/dist/commands/ssh.d.ts +6 -0
  52. package/dist/commands/ssh.js +93 -302
  53. package/dist/commands/sync.js +14 -5
  54. package/dist/commands/teams.js +1 -1
  55. package/dist/commands/traces.js +1 -1
  56. package/dist/lib/accounting/rotate.d.ts +63 -1
  57. package/dist/lib/accounting/rotate.js +56 -0
  58. package/dist/lib/accounts/add.d.ts +0 -5
  59. package/dist/lib/accounts/add.js +3 -9
  60. package/dist/lib/accounts/slots.js +32 -2
  61. package/dist/lib/answer-router.d.ts +11 -2
  62. package/dist/lib/answer-router.js +26 -2
  63. package/dist/lib/artifacts-client.d.ts +20 -0
  64. package/dist/lib/artifacts-client.js +46 -0
  65. package/dist/lib/auth-mint.d.ts +5 -4
  66. package/dist/lib/auth-mint.js +6 -5
  67. package/dist/lib/browser/drivers/arc.d.ts +1 -1
  68. package/dist/lib/browser/runtime-state.d.ts +55 -0
  69. package/dist/lib/browser/runtime-state.js +99 -18
  70. package/dist/lib/browser/service.d.ts +10 -0
  71. package/dist/lib/browser/service.js +229 -37
  72. package/dist/lib/browser/types.d.ts +18 -0
  73. package/dist/lib/cli-resources.js +3 -1
  74. package/dist/lib/cloud/dispatch.js +1 -1
  75. package/dist/lib/cloudflare/creds.d.ts +10 -0
  76. package/dist/lib/cloudflare/creds.js +46 -0
  77. package/dist/lib/cloudflare/provision.d.ts +35 -0
  78. package/dist/lib/cloudflare/provision.js +144 -0
  79. package/dist/lib/computer/sessions-list.d.ts +55 -0
  80. package/dist/lib/computer/sessions-list.js +168 -1
  81. package/dist/lib/config-keys.d.ts +1 -1
  82. package/dist/lib/config-keys.js +5 -0
  83. package/dist/lib/daemon/daemon.js +8 -1
  84. package/dist/lib/daemon/feed-stream-service.d.ts +23 -0
  85. package/dist/lib/daemon/feed-stream-service.js +40 -0
  86. package/dist/lib/daemon-services.d.ts +1 -1
  87. package/dist/lib/daemon-services.js +5 -0
  88. package/dist/lib/device-config.js +61 -0
  89. package/dist/lib/devices/connect.d.ts +49 -5
  90. package/dist/lib/devices/connect.js +169 -21
  91. package/dist/lib/devices/doctor-findings.js +2 -6
  92. package/dist/lib/feed/answer.d.ts +153 -4
  93. package/dist/lib/feed/answer.js +716 -105
  94. package/dist/lib/feed/envelope.d.ts +79 -0
  95. package/dist/lib/feed/envelope.js +23 -0
  96. package/dist/lib/feed/events.d.ts +6 -0
  97. package/dist/lib/feed/events.js +8 -0
  98. package/dist/lib/feed/feed.d.ts +61 -1
  99. package/dist/lib/feed/feed.js +226 -14
  100. package/dist/lib/feed/hub-server.d.ts +141 -0
  101. package/dist/lib/feed/hub-server.js +586 -0
  102. package/dist/lib/feed/hub.d.ts +95 -0
  103. package/dist/lib/feed/hub.js +255 -0
  104. package/dist/lib/feed/pr-status.d.ts +8 -0
  105. package/dist/lib/feed/pr-status.js +9 -1
  106. package/dist/lib/feed/tool-activity.d.ts +108 -0
  107. package/dist/lib/feed/tool-activity.js +313 -0
  108. package/dist/lib/feed/tools.d.ts +198 -0
  109. package/dist/lib/feed/tools.js +265 -0
  110. package/dist/lib/feed/watch.d.ts +50 -50
  111. package/dist/lib/feed/watch.js +147 -16
  112. package/dist/lib/feed-outcome.d.ts +1 -1
  113. package/dist/lib/feed-outcome.js +9 -2
  114. package/dist/lib/feed-policy.js +9 -3
  115. package/dist/lib/fleet/auth-sync.d.ts +2 -55
  116. package/dist/lib/fleet/auth-sync.js +2 -89
  117. package/dist/lib/format.d.ts +1 -1
  118. package/dist/lib/format.js +1 -1
  119. package/dist/lib/git.d.ts +0 -16
  120. package/dist/lib/git.js +0 -58
  121. package/dist/lib/harness-auth-capabilities.js +7 -2
  122. package/dist/lib/helper-versions.js +1 -1
  123. package/dist/lib/hosts/dispatch.d.ts +20 -1
  124. package/dist/lib/hosts/dispatch.js +52 -30
  125. package/dist/lib/hosts/remote-cmd.d.ts +72 -1
  126. package/dist/lib/hosts/remote-cmd.js +151 -10
  127. package/dist/lib/hosts/remote-cmd.test-fixture.d.ts +2 -0
  128. package/dist/lib/hosts/remote-cmd.test-fixture.js +22 -0
  129. package/dist/lib/mailbox.d.ts +12 -0
  130. package/dist/lib/mailbox.js +16 -2
  131. package/dist/lib/mcp.js +17 -11
  132. package/dist/lib/menubar/snapshot.d.ts +51 -0
  133. package/dist/lib/menubar/snapshot.js +42 -3
  134. package/dist/lib/open-url.js +2 -2
  135. package/dist/lib/probe.d.ts +4 -1
  136. package/dist/lib/probe.js +5 -2
  137. package/dist/lib/projects.d.ts +23 -0
  138. package/dist/lib/projects.js +78 -0
  139. package/dist/lib/pwsh.d.ts +33 -0
  140. package/dist/lib/pwsh.js +56 -0
  141. package/dist/lib/redact.d.ts +8 -0
  142. package/dist/lib/redact.js +11 -0
  143. package/dist/lib/refresh.d.ts +6 -2
  144. package/dist/lib/refresh.js +92 -72
  145. package/dist/lib/secrets-cli.d.ts +11 -0
  146. package/dist/lib/secrets-cli.js +30 -0
  147. package/dist/lib/secrets-client.js +3 -2
  148. package/dist/lib/session/active.d.ts +1 -0
  149. package/dist/lib/session/active.js +8 -0
  150. package/dist/lib/session/db.d.ts +67 -3
  151. package/dist/lib/session/db.js +381 -126
  152. package/dist/lib/session/detached.d.ts +7 -0
  153. package/dist/lib/session/detached.js +29 -0
  154. package/dist/lib/session/prompt.d.ts +23 -7
  155. package/dist/lib/session/prompt.js +46 -8
  156. package/dist/lib/session/remote/peer-stream.d.ts +24 -2
  157. package/dist/lib/session/remote/peer-stream.js +33 -6
  158. package/dist/lib/session/remote/remote-list.d.ts +20 -0
  159. package/dist/lib/session/remote/remote-list.js +22 -6
  160. package/dist/lib/session/remote/watch.d.ts +12 -0
  161. package/dist/lib/session/remote/watch.js +9 -0
  162. package/dist/lib/session/remote-preview-cache.d.ts +29 -0
  163. package/dist/lib/session/remote-preview-cache.js +373 -0
  164. package/dist/lib/session/sync/backend.d.ts +3 -3
  165. package/dist/lib/session/sync/backend.js +3 -3
  166. package/dist/lib/session/sync/provision.d.ts +1 -1
  167. package/dist/lib/session/sync/provision.js +2 -2
  168. package/dist/lib/session/tail.d.ts +50 -0
  169. package/dist/lib/session/tail.js +219 -0
  170. package/dist/lib/sessions-client.js +0 -3
  171. package/dist/lib/setup-tool-install.d.ts +3 -0
  172. package/dist/lib/setup-tool-install.js +27 -0
  173. package/dist/lib/setup-tool-status.d.ts +22 -0
  174. package/dist/lib/setup-tool-status.js +220 -0
  175. package/dist/lib/share-runtime.d.ts +11 -0
  176. package/dist/lib/share-runtime.js +63 -0
  177. package/dist/lib/signin-badge.d.ts +19 -4
  178. package/dist/lib/signin-badge.js +29 -11
  179. package/dist/lib/smart-launch.d.ts +1 -5
  180. package/dist/lib/smart-launch.js +3 -11
  181. package/dist/lib/ssh-exec.d.ts +44 -0
  182. package/dist/lib/ssh-exec.js +119 -0
  183. package/dist/lib/startup/command-registry.d.ts +6 -4
  184. package/dist/lib/startup/command-registry.js +10 -7
  185. package/dist/lib/state.js +2 -2
  186. package/dist/lib/storage/selection.d.ts +2 -2
  187. package/dist/lib/storage/selection.js +2 -2
  188. package/dist/lib/sync-umbrella.d.ts +5 -0
  189. package/dist/lib/sync-umbrella.js +18 -10
  190. package/dist/lib/term-driver.d.ts +24 -0
  191. package/dist/lib/term-driver.js +36 -0
  192. package/dist/lib/terminal/index.d.ts +1 -1
  193. package/dist/lib/terminal/index.js +1 -1
  194. package/dist/lib/terminal/inject.d.ts +38 -0
  195. package/dist/lib/terminal/inject.js +55 -9
  196. package/dist/lib/terminal/transport.d.ts +15 -5
  197. package/dist/lib/terminal/transport.js +61 -11
  198. package/dist/lib/traces/backend.d.ts +1 -2
  199. package/dist/lib/traces/backend.js +1 -2
  200. package/dist/lib/traces/provision.d.ts +1 -1
  201. package/dist/lib/traces/provision.js +2 -2
  202. package/dist/lib/types.d.ts +8 -6
  203. package/package.json +2 -3
  204. package/dist/commands/artifacts-setup.d.ts +0 -53
  205. package/dist/commands/artifacts-setup.js +0 -161
  206. package/dist/commands/artifacts.d.ts +0 -18
  207. package/dist/commands/artifacts.js +0 -58
  208. package/dist/commands/attach.d.ts +0 -12
  209. package/dist/commands/attach.js +0 -86
  210. package/dist/commands/auth-mint.d.ts +0 -12
  211. package/dist/commands/auth-mint.js +0 -108
  212. package/dist/commands/reconnect.d.ts +0 -46
  213. package/dist/commands/reconnect.js +0 -115
  214. package/dist/commands/share.d.ts +0 -293
  215. package/dist/commands/share.js +0 -1424
  216. package/dist/lib/fleet/remote-login.d.ts +0 -170
  217. package/dist/lib/fleet/remote-login.js +0 -568
  218. package/dist/lib/share/analytics.d.ts +0 -13
  219. package/dist/lib/share/analytics.js +0 -45
  220. package/dist/lib/share/backend.d.ts +0 -120
  221. package/dist/lib/share/backend.js +0 -176
  222. package/dist/lib/share/capture.d.ts +0 -31
  223. package/dist/lib/share/capture.js +0 -174
  224. package/dist/lib/share/config.d.ts +0 -72
  225. package/dist/lib/share/config.js +0 -211
  226. package/dist/lib/share/delete.d.ts +0 -123
  227. package/dist/lib/share/delete.js +0 -173
  228. package/dist/lib/share/html.d.ts +0 -20
  229. package/dist/lib/share/html.js +0 -88
  230. package/dist/lib/share/http-error.d.ts +0 -53
  231. package/dist/lib/share/http-error.js +0 -65
  232. package/dist/lib/share/og.d.ts +0 -26
  233. package/dist/lib/share/og.js +0 -84
  234. package/dist/lib/share/provision.d.ts +0 -127
  235. package/dist/lib/share/provision.js +0 -285
  236. package/dist/lib/share/publish.d.ts +0 -379
  237. package/dist/lib/share/publish.js +0 -818
  238. package/dist/lib/share/worker-template.d.ts +0 -27
  239. package/dist/lib/share/worker-template.js +0 -2424
  240. package/dist/lib/storage/index.d.ts +0 -14
  241. package/dist/lib/storage/index.js +0 -14
  242. package/dist/lib/storage/visibility.d.ts +0 -82
  243. package/dist/lib/storage/visibility.js +0 -99
@@ -514,7 +514,8 @@ async function runUmbrella(opts, quiet, outLog, errLog, json = false) {
514
514
  // into. Any explicit flag, --yes, or --json keeps the non-interactive path.
515
515
  // --json is a machine consumer (and the fleet fan-out injects it), so never
516
516
  // open a picker under it.
517
- const anyExplicitFlag = !!(opts.repos || opts.secrets || opts.cloud || opts.local);
517
+ const kindSelection = parseKindSelection(opts);
518
+ const anyExplicitFlag = !!(opts.repos || opts.secrets || opts.cloud || opts.local || kindSelection);
518
519
  if (!quiet && !json && !opts.yes && !anyExplicitFlag && isInteractiveTerminal()) {
519
520
  await runInteractiveReconcile(opts, outLog, errLog);
520
521
  return;
@@ -524,7 +525,10 @@ async function runUmbrella(opts, quiet, outLog, errLog, json = false) {
524
525
  repos: opts.repos,
525
526
  secrets: opts.secrets,
526
527
  cloud: opts.cloud,
527
- local: opts.local,
528
+ // A resource selector is a local reconcile unless the caller explicitly
529
+ // asks to fetch repos/secrets first. This keeps replacements such as
530
+ // `sync --mcp demo` scoped like the retired resource verb.
531
+ local: opts.local || (!!kindSelection && !opts.repos && !opts.secrets && !opts.cloud),
528
532
  };
529
533
  // Same chokepoint as `agents secrets push/pull` — prefers AGENTS_SYNC_PASSPHRASE,
530
534
  // falls back to the deprecated master-key name with a single warning.
@@ -541,6 +545,8 @@ async function runUmbrella(opts, quiet, outLog, errLog, json = false) {
541
545
  passphrase,
542
546
  // quiet under --json so refresh() cannot pollute the JSON stdout the fleet parses.
543
547
  quiet: quiet || json,
548
+ selection: kindSelection,
549
+ allowExecSurfaces: !!opts.allowExecSurfaces,
544
550
  log: (msg) => { if (!quiet && !json)
545
551
  outLog(chalk.gray(` ${msg}`)); },
546
552
  });
@@ -548,20 +554,22 @@ async function runUmbrella(opts, quiet, outLog, errLog, json = false) {
548
554
  // we act on central as converged by this sync rather than a stale pre-pull
549
555
  // copy that could re-claim a profile a peer already migrated. Skipped under
550
556
  // --cloud (fetch-only, no local reconcile).
551
- if (!opts.cloud)
557
+ if (!opts.cloud && !kindSelection)
552
558
  evictCentralBrowserProfilesForSync(quiet, json, outLog, errLog);
553
559
  // Post-reconcile verification (PHNX-3186): re-read every version the reconcile
554
560
  // wrote into and confirm it now matches source. Without this the umbrella
555
561
  // printed `✓ sync: reconciled` unconditionally, the exact false-success the
556
562
  // ticket reports. Residual drift downgrades the line and sets a non-zero exit.
557
- const residual = result.reconciled
563
+ const residual = result.reconciled && !kindSelection
558
564
  ? verifyReconciled(result.reconciledVersions.map((r) => ({ agent: r.agent, version: r.version })), cwd)
559
565
  : [];
560
566
  // Post-reconcile repair, machine-wide: no agent scope → heal every installed
561
567
  // agent, re-wire hooks the diff left behind, and repair managed hook runtime
562
568
  // shims. The stale-CLI purge runs ONLY with `--prune-clis` (never automatic).
563
569
  // Skipped under --cloud (fetch-only: nothing was reconciled to repair).
564
- const repair = opts.cloud ? null : await repairAfterSync({ cwd, pruneClis: !!opts.pruneClis });
570
+ const repair = opts.cloud || kindSelection
571
+ ? null
572
+ : await repairAfterSync({ cwd, pruneClis: !!opts.pruneClis });
565
573
  // A repair that left something for a human (an unresolvable shim, a failed
566
574
  // rewire/purge) is a non-zero outcome, same as the deleted `doctor --fix`.
567
575
  const repairFailed = repair !== null && repairHadFailures(repair);
@@ -578,6 +586,7 @@ async function runUmbrella(opts, quiet, outLog, errLog, json = false) {
578
586
  secrets: result.secrets,
579
587
  devices: result.devices,
580
588
  reconciled: result.reconciled,
589
+ selection: kindSelection ?? null,
581
590
  declined: result.declined,
582
591
  residualDrift: residual,
583
592
  repair: repair ? repairAfterSyncJson(repair) : null,
@@ -8,7 +8,7 @@ import { mailboxDir, enqueue } from '../lib/mailbox.js';
8
8
  import { resolveProvider } from '../lib/cloud/registry.js';
9
9
  import { emit } from '../lib/feed/events.js';
10
10
  import { maybeShowStarNudge } from '../lib/star-nudge.js';
11
- import { shareRuntimeEnv } from '../lib/share/config.js';
11
+ import { shareRuntimeEnv } from '../lib/share-runtime.js';
12
12
  import { runSupervisor } from '../lib/teams/supervisor.js';
13
13
  import { debug } from '../lib/teams/debug.js';
14
14
  import { runPrWatch, DEFAULT_MAX_WAVES, } from '../lib/teams/pr-watch.js';
@@ -3,7 +3,7 @@ import { setHelpSections } from '../lib/help.js';
3
3
  import { readSyncLedger, syncTraces } from '../lib/traces/sync.js';
4
4
  import { managedTracesBaseUrl, resolveTracesBackend } from '../lib/traces/backend.js';
5
5
  import { showUrl } from '../lib/open-url.js';
6
- import { DEFAULT_CF_BUNDLE, readCloudflareCreds } from '../lib/share/config.js';
6
+ import { DEFAULT_CF_BUNDLE, readCloudflareCreds } from '../lib/cloudflare/creds.js';
7
7
  import { PHOENIX_ID_BASE } from '../lib/identity/client.js';
8
8
  import { DEFAULT_BUCKET_NAME, DEFAULT_WORKER_NAME } from '../lib/traces/config.js';
9
9
  import { DEFAULT_TRACES_DOMAIN } from '../lib/traces/backend.js';
@@ -4,7 +4,7 @@
4
4
  * Detects which installed versions have expired credentials and rotates
5
5
  * authentication tokens so users maintain active sessions across version switches.
6
6
  */
7
- import type { AgentId, RunStrategy } from '../types.js';
7
+ import { type AgentId, type RunStrategy } from '../types.js';
8
8
  import type { FallbackEntry } from '../exec.js';
9
9
  import { PROJECTION_HORIZON_MIN, capacityWeight } from './capacity.js';
10
10
  import { type AccountInfo, type CredentialPresence } from '../agents.js';
@@ -549,6 +549,68 @@ export declare const DEFAULT_ROTATION_FAILOVER_LIMIT = 3;
549
549
  * completely unchanged.
550
550
  */
551
551
  export declare function rotationFailoverChain(rotation: RotateResult | null, pickedVersion: string, limit?: number): FallbackEntry[];
552
+ /**
553
+ * Whether a run SHAPE may take the preflight alternate-harness handoff
554
+ * ({@link preflightFallbackHandoff}) — the sibling of
555
+ * {@link shouldArmRotationFailover}, and load-bearing for the same reason.
556
+ *
557
+ * The handoff consumes the entry it uses from the `--fallback` spec, and the
558
+ * canonical `--fallback` validation runs LATER in the run command: it rejects a
559
+ * chain on an interactive, `--acp`, `--loop`, or `--resume-checkpoint` run, and
560
+ * requires a prompt. Handing off first on one of those shapes would empty the
561
+ * spec, leave that guard nothing to reject, and let the run proceed as the
562
+ * alternate harness — an interactive Codex launch from
563
+ * `agents run claude --interactive --fallback codex`, which the CLI refuses
564
+ * today. Gating the shape here keeps the original validation intact: an
565
+ * ineligible shape never switches, so the spec survives and still fails there
566
+ * with its own message.
567
+ *
568
+ * `resume` and `workflowScoped` are excluded for their own reasons: a resume is
569
+ * bound to the session's OWN harness, and a workflow's tool/MCP scoping is
570
+ * claude-only, so switching harness would silently drop the declared sandbox —
571
+ * the fail-open `runWithFallback` only warns about.
572
+ *
573
+ * Pure so the matrix is unit-testable without invoking the run command.
574
+ */
575
+ export interface PreflightHandoffContext {
576
+ hasPrompt: boolean;
577
+ interactive: boolean;
578
+ acp: boolean;
579
+ loop: boolean;
580
+ resumeCheckpoint: boolean;
581
+ resume: boolean;
582
+ workflowScoped: boolean;
583
+ }
584
+ export declare function preflightHandoffEligible(ctx: PreflightHandoffContext): boolean;
585
+ /**
586
+ * The permitted alternate harness to launch when EVERY account of the primary
587
+ * agent is already exhausted at PREFLIGHT (PHNX-3999 F19).
588
+ *
589
+ * `runWithFallback` only reaches a `--fallback` entry after the primary has
590
+ * actually RUN and hit a rate limit. When the primary's accounts are throttled
591
+ * before the run starts, version resolution reports `exhausted` and the run used
592
+ * to exit — so the alternate a user configured for exactly this case never fired.
593
+ * This resolves the first usable alternate from the same `--fallback` spec, and
594
+ * returns the remainder of the spec so the alternate becomes the primary and is
595
+ * not also listed as its own fallback.
596
+ *
597
+ * Deliberately NOT a second parser: an entry this cannot resolve exactly — a
598
+ * typo, an alias the canonical parse would fuzzy-match, the primary itself —
599
+ * makes the WHOLE spec yield `null`, so the run reaches the one canonical
600
+ * `--fallback` parse and fails (or corrects) there with the message it already
601
+ * has. `--fallback typoo,codex` must never silently launch codex while dropping
602
+ * `typoo`.
603
+ *
604
+ * Returns `null` when there is no spec, when any entry is not an exact agent id
605
+ * or names the primary, or when the primary is only sign-in-recoverable rather
606
+ * than throttled — a logged-out account is fixed by logging in, not by silently
607
+ * running a different harness.
608
+ */
609
+ export declare function preflightFallbackHandoff(spec: string | undefined, primary: AgentId, exhausted: RotateCandidate[]): {
610
+ agent: AgentId;
611
+ version?: string;
612
+ remainingSpec?: string;
613
+ } | null;
552
614
  /**
553
615
  * Whether a run is eligible to have a mid-run rate-limit failover chain armed
554
616
  * (issue #348). Failover injects synthesized `FallbackEntry`s into the same
@@ -6,6 +6,7 @@
6
6
  */
7
7
  import * as fs from 'fs';
8
8
  import * as path from 'path';
9
+ import { isAgentId } from '../types.js';
9
10
  import { PROJECTION_HORIZON_MIN, capacityWeight } from './capacity.js';
10
11
  import { accountDisplayLabel, getAccountInfo, credentialPresence, ALL_AGENT_IDS, } from '../agents.js';
11
12
  import { readMeta, getHelpersDir } from '../state.js';
@@ -1206,6 +1207,61 @@ export function rotationFailoverChain(rotation, pickedVersion, limit = DEFAULT_R
1206
1207
  }
1207
1208
  return chain;
1208
1209
  }
1210
+ export function preflightHandoffEligible(ctx) {
1211
+ return (ctx.hasPrompt &&
1212
+ !ctx.interactive &&
1213
+ !ctx.acp &&
1214
+ !ctx.loop &&
1215
+ !ctx.resumeCheckpoint &&
1216
+ !ctx.resume &&
1217
+ !ctx.workflowScoped);
1218
+ }
1219
+ /**
1220
+ * The permitted alternate harness to launch when EVERY account of the primary
1221
+ * agent is already exhausted at PREFLIGHT (PHNX-3999 F19).
1222
+ *
1223
+ * `runWithFallback` only reaches a `--fallback` entry after the primary has
1224
+ * actually RUN and hit a rate limit. When the primary's accounts are throttled
1225
+ * before the run starts, version resolution reports `exhausted` and the run used
1226
+ * to exit — so the alternate a user configured for exactly this case never fired.
1227
+ * This resolves the first usable alternate from the same `--fallback` spec, and
1228
+ * returns the remainder of the spec so the alternate becomes the primary and is
1229
+ * not also listed as its own fallback.
1230
+ *
1231
+ * Deliberately NOT a second parser: an entry this cannot resolve exactly — a
1232
+ * typo, an alias the canonical parse would fuzzy-match, the primary itself —
1233
+ * makes the WHOLE spec yield `null`, so the run reaches the one canonical
1234
+ * `--fallback` parse and fails (or corrects) there with the message it already
1235
+ * has. `--fallback typoo,codex` must never silently launch codex while dropping
1236
+ * `typoo`.
1237
+ *
1238
+ * Returns `null` when there is no spec, when any entry is not an exact agent id
1239
+ * or names the primary, or when the primary is only sign-in-recoverable rather
1240
+ * than throttled — a logged-out account is fixed by logging in, not by silently
1241
+ * running a different harness.
1242
+ */
1243
+ export function preflightFallbackHandoff(spec, primary, exhausted) {
1244
+ if (!spec)
1245
+ return null;
1246
+ if (exhausted.length === 0)
1247
+ return null;
1248
+ if (signInRecoverableCandidates(exhausted).length > 0)
1249
+ return null;
1250
+ const entries = spec.split(',').map((e) => e.trim()).filter(Boolean);
1251
+ if (entries.length === 0)
1252
+ return null;
1253
+ const parsed = entries.map((entry) => entry.split('@'));
1254
+ // Every entry has to be exactly resolvable, or this defers entirely.
1255
+ if (parsed.some(([name]) => !isAgentId(name) || name === primary))
1256
+ return null;
1257
+ const [name, version] = parsed[0];
1258
+ const remaining = entries.slice(1);
1259
+ return {
1260
+ agent: name,
1261
+ version: version || undefined,
1262
+ remainingSpec: remaining.length > 0 ? remaining.join(',') : undefined,
1263
+ };
1264
+ }
1209
1265
  export function shouldArmRotationFailover(ctx) {
1210
1266
  return (ctx.hasRotation &&
1211
1267
  ctx.hasVersion &&
@@ -131,8 +131,3 @@ export declare function runAdd(agent: AgentId, name: string | undefined, opts: A
131
131
  * that IS how such a box logs in.
132
132
  */
133
133
  export declare function runLogin(agent: AgentId, name: string, opts: AddOptions, runners?: AddRunners): Promise<AddResult>;
134
- /** Back-compat alias for the retired `connect` module surface (one release). */
135
- export declare const runConnect: typeof runAdd;
136
- export declare const connectSupported: typeof addSupported;
137
- export declare const connectRefusal: typeof addRefusal;
138
- export declare const connectWorkerRefusal: typeof addWorkerRefusal;
@@ -15,8 +15,7 @@ import { ensureSlot, readSlots, recordSlot, slotDir } from './slots.js';
15
15
  export { LOGIN_INVOCATIONS };
16
16
  /**
17
17
  * `agents accounts add <harness> [name]` / `agents accounts login <harness>#<name>`
18
- * — the account onboarding front door (PHNX-3940, track T4; successor of the
19
- * `connect` module, kept as the `runConnect` alias for one release).
18
+ * — the account onboarding front door (PHNX-3940, track T4).
20
19
  *
21
20
  * An account is a credential SLOT, not an installation: the harness has ONE
22
21
  * managed installation (`ensureHarnessInstallation`, label `main`); every
@@ -90,11 +89,11 @@ export function workerProvisioningHint(agent) {
90
89
  parts.push(env === 'provider' ? 'a provider API key collected by add (--api-key)' : `the ${env} collected by add (--api-key)`);
91
90
  }
92
91
  else if (kind.startsWith('per-device')) {
93
- parts.push(`per-device login on each worker (--per-device / agents fleet login ${agent})`);
92
+ parts.push(`per-device login on each worker (--per-device; run \`agents run ${agent} --device <box>\` there and complete its native login)`);
94
93
  }
95
94
  }
96
95
  if (parts.length === 0)
97
- return `${agent} has no portable credential — it logs in per box (agents fleet login ${agent})`;
96
+ return `${agent} has no portable credential — it logs in per box (run \`agents run ${agent} --device <box>\` on the worker and complete its native login)`;
98
97
  return parts.join(', or ');
99
98
  }
100
99
  /**
@@ -542,11 +541,6 @@ async function _runLoginLocked(agent, name, opts, lock, runners) {
542
541
  warnings,
543
542
  };
544
543
  }
545
- /** Back-compat alias for the retired `connect` module surface (one release). */
546
- export const runConnect = runAdd;
547
- export const connectSupported = addSupported;
548
- export const connectRefusal = addRefusal;
549
- export const connectWorkerRefusal = addWorkerRefusal;
550
544
  /** Real runners: the install engine, an inherited-stdio login spawn, identity read, and the mint drive. */
551
545
  async function defaultAddRunners() {
552
546
  const [store, agentsMod, execMod] = await Promise.all([
@@ -13,7 +13,8 @@
13
13
  */
14
14
  import * as fs from 'node:fs';
15
15
  import * as path from 'node:path';
16
- import { agentConfigDirName } from '../agents.js';
16
+ import { AGENTS, agentConfigDirName } from '../agents.js';
17
+ import { createLink } from '../platform/links.js';
17
18
  import { harnessAuth, harnessWorkerIsPerDevice } from '../harness-auth-capabilities.js';
18
19
  import { installSessionTrackerHookSync } from '../hooks/install.js';
19
20
  import { getGlobalDefault, getVersionHomePath, listInstalledVersions } from '../installations/store.js';
@@ -64,7 +65,36 @@ function projectResources(harness, version, destHome, fromHome) {
64
65
  if (!writer)
65
66
  continue;
66
67
  if (kind === 'rules') {
67
- writer.write({ version, versionHome: destHome, selection: { preset: 'default' }, cwd });
68
+ // Symlink to the version home's rules instead of composing a duplicate.
69
+ // Harnesses whose shim pins a separate config dir (e.g. CLAUDE_CONFIG_DIR
70
+ // → account slot) cause the harness to load rules from both the version
71
+ // home (~/.claude/) and the slot, doubling ~5 K tokens per session.
72
+ // A symlink gives both paths the same inode so the harness can dedup.
73
+ const cap = AGENTS[harness].capabilities.rules;
74
+ if (typeof cap !== 'object')
75
+ continue;
76
+ const srcFile = path.join(fromHome, agentConfigDirName(harness), cap.file);
77
+ if (!fs.existsSync(srcFile)) {
78
+ writer.write({ version, versionHome: destHome, selection: { preset: 'default' }, cwd });
79
+ continue;
80
+ }
81
+ const destFile = path.join(destHome, agentConfigDirName(harness), cap.file);
82
+ fs.mkdirSync(path.dirname(destFile), { recursive: true });
83
+ try {
84
+ const st = fs.lstatSync(destFile);
85
+ if (st.isSymbolicLink() || st.isFile())
86
+ fs.unlinkSync(destFile);
87
+ }
88
+ catch { /* did not exist */ }
89
+ try {
90
+ createLink(srcFile, destFile);
91
+ }
92
+ catch (err) {
93
+ if (err.code === 'EEXIST') {
94
+ continue;
95
+ }
96
+ throw err;
97
+ }
68
98
  continue;
69
99
  }
70
100
  const names = getDetector(kind, harness)?.list({ version, versionHome: fromHome, cwd }) ?? [];
@@ -10,7 +10,7 @@
10
10
  * This module picks the delivery mechanism from (open feed block × session
11
11
  * liveness × runtime rail). Pure — unit-testable without a live terminal.
12
12
  */
13
- import type { OpenBlock, BlockOption } from './feed/feed.js';
13
+ import { type OpenBlock, type BlockOption } from './feed/feed.js';
14
14
  import type { ActiveSession } from './session/active.js';
15
15
  import type { InjectTarget } from './terminal/index.js';
16
16
  export type AnswerRouteKind = 'mailbox' | 'tmux' | 'iterm' | 'resume' | 'refuse';
@@ -67,7 +67,16 @@ export declare function keystrokesForAnswer(answer: string, options?: Array<Pick
67
67
  };
68
68
  /** True when the session is waiting on user input (parked on a question/plan). */
69
69
  export declare function isParkedOnInput(session: ActiveSession | null | undefined): boolean;
70
- /** True when an open feed block still needs an answer. */
70
+ /**
71
+ * True when an open feed block still needs an answer.
72
+ *
73
+ * Openness comes from {@link deriveBlockState}, not from `block.answer` being
74
+ * truthy: a PENDING claim records the answer while deliberately leaving the
75
+ * block `open` (feed.ts `recordAnswer`'s two-phase mode), so reading the field
76
+ * directly would call a claimed-but-undelivered block "closed" — and a retry of
77
+ * a stranded claim would then re-route a parked headless agent to the mailbox it
78
+ * will never drain (PHNX-3999).
79
+ */
71
80
  export declare function isOpenQuestionBlock(block: OpenBlock | null | undefined): boolean;
72
81
  /**
73
82
  * Pick the delivery mechanism for one answer.
@@ -1,3 +1,16 @@
1
+ /**
2
+ * Parked-agent answer router (RUSH-1474).
3
+ *
4
+ * `agents message` historically only enqueued into the mailbox (delivered at the
5
+ * next PreToolUse). That steers a *running* agent between tool calls but never
6
+ * unblocks a *parked* agent:
7
+ * - interactive TUI open on AskUserQuestion (needs keystrokes, not context)
8
+ * - headless run waiting on input (no next tool call; needs resume)
9
+ *
10
+ * This module picks the delivery mechanism from (open feed block × session
11
+ * liveness × runtime rail). Pure — unit-testable without a live terminal.
12
+ */
13
+ import { deriveBlockState } from './feed/feed.js';
1
14
  import { addressabilityRecoveryHint } from './terminal/resolve.js';
2
15
  import { injectTargetFromReplyRail } from './session/inject.js';
3
16
  /** The Escape keystroke as its raw control byte — what a tmux/iterm rail reads as a real Escape. */
@@ -64,11 +77,22 @@ export function isParkedOnInput(session) {
64
77
  }
65
78
  return false;
66
79
  }
67
- /** True when an open feed block still needs an answer. */
80
+ /**
81
+ * True when an open feed block still needs an answer.
82
+ *
83
+ * Openness comes from {@link deriveBlockState}, not from `block.answer` being
84
+ * truthy: a PENDING claim records the answer while deliberately leaving the
85
+ * block `open` (feed.ts `recordAnswer`'s two-phase mode), so reading the field
86
+ * directly would call a claimed-but-undelivered block "closed" — and a retry of
87
+ * a stranded claim would then re-route a parked headless agent to the mailbox it
88
+ * will never drain (PHNX-3999).
89
+ */
68
90
  export function isOpenQuestionBlock(block) {
69
91
  if (!block)
70
92
  return false;
71
- if (block.answer || block.parkedAt || block.continuedAt || block.defaultedAt)
93
+ if (block.parkedAt || block.continuedAt || block.defaultedAt)
94
+ return false;
95
+ if (deriveBlockState(block) !== 'open')
72
96
  return false;
73
97
  return (block.questions?.length ?? 0) > 0;
74
98
  }
@@ -0,0 +1,20 @@
1
+ export declare const ARTIFACTS_INSTALL_HINT = "npm i -g @phnx-labs/artifacts-cli";
2
+ export declare class ArtifactsClientError extends Error {
3
+ readonly code: 'ARTIFACTS_BIN_MISSING';
4
+ constructor(code: 'ARTIFACTS_BIN_MISSING', message: string);
5
+ }
6
+ /**
7
+ * Resolve the standalone `artifacts` executable. `ARTIFACTS_BIN` wins so a dev
8
+ * build can be driven without touching PATH.
9
+ *
10
+ * Resolution uses `findInPath`, which skips `~/.agents/.cache/shims` — the same
11
+ * load-bearing skip the secrets/computer clients rely on, so a leftover alias
12
+ * shim that execs back into this CLI can never be picked as the engine.
13
+ */
14
+ export declare function resolveArtifactsBin(): string;
15
+ /** How to invoke the resolved binary: a `.js` entrypoint runs through this
16
+ * runtime, a real executable is spawned directly. */
17
+ export declare function invocation(bin: string): {
18
+ command: string;
19
+ prefix: string[];
20
+ };
@@ -0,0 +1,46 @@
1
+ // Thin client for the standalone `artifacts` CLI (`@phnx-labs/artifacts-cli`).
2
+ //
3
+ // The artifact render + share ENGINE lives in artifacts-cli, not this repo
4
+ // (PHNX-3992, mirroring the secrets/computer extractions). agents-cli calls it
5
+ // only where a `sessions` convenience wants to publish (`agents sessions share`).
6
+ // A missing executable fails loud with install guidance — there is no fallback
7
+ // to a retired in-repo engine.
8
+ import { findInPath } from './agent-spec/agents.js';
9
+ export const ARTIFACTS_INSTALL_HINT = 'npm i -g @phnx-labs/artifacts-cli';
10
+ export class ArtifactsClientError extends Error {
11
+ code;
12
+ constructor(code, message) {
13
+ super(message);
14
+ this.code = code;
15
+ this.name = 'ArtifactsClientError';
16
+ }
17
+ }
18
+ let cachedBin = null;
19
+ /**
20
+ * Resolve the standalone `artifacts` executable. `ARTIFACTS_BIN` wins so a dev
21
+ * build can be driven without touching PATH.
22
+ *
23
+ * Resolution uses `findInPath`, which skips `~/.agents/.cache/shims` — the same
24
+ * load-bearing skip the secrets/computer clients rely on, so a leftover alias
25
+ * shim that execs back into this CLI can never be picked as the engine.
26
+ */
27
+ export function resolveArtifactsBin() {
28
+ if (cachedBin)
29
+ return cachedBin;
30
+ const explicit = process.env.ARTIFACTS_BIN?.trim();
31
+ const resolved = explicit && explicit.length > 0 ? explicit : findInPath('artifacts');
32
+ if (!resolved) {
33
+ throw new ArtifactsClientError('ARTIFACTS_BIN_MISSING', 'The standalone `artifacts` CLI was not found. Install it with:\n' +
34
+ ` ${ARTIFACTS_INSTALL_HINT}\n` +
35
+ 'or point $ARTIFACTS_BIN at its executable.');
36
+ }
37
+ cachedBin = resolved;
38
+ return resolved;
39
+ }
40
+ /** How to invoke the resolved binary: a `.js` entrypoint runs through this
41
+ * runtime, a real executable is spawned directly. */
42
+ export function invocation(bin) {
43
+ if (/\.[mc]?js$/.test(bin))
44
+ return { command: process.execPath, prefix: [bin] };
45
+ return { command: bin, prefix: [] };
46
+ }
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * First-class setup-token mint + seed (PHNX-2364).
3
3
  *
4
- * Closes the mint-auth manual recipe: drive `claude setup-token` through the
5
- * same injectable term driver `agents fleet login` uses, capture a well-formed
4
+ * Closes the mint-auth manual recipe: drive `claude setup-token` through an
5
+ * injectable term driver (`lib/term-driver.ts`), capture a well-formed
6
6
  * `sk-ant-oat01-…` token (the #1767 ANSI-banner guard), and seed BOTH:
7
7
  *
8
8
  * 1. a named provider account (`agents accounts add` shape, policy never)
@@ -16,7 +16,7 @@
16
16
  import type { AgentId, Meta } from './types.js';
17
17
  import { type CredentialAccount } from './account-registry.js';
18
18
  import { type StorableCredentialKind } from './secrets-policy.js';
19
- import { type DriveOptions, type TermDriver } from './fleet/remote-login.js';
19
+ import { type DriveOptions, type TermDriver } from './term-driver.js';
20
20
  /** Well-formed Claude setup-token as it appears inside a TTY blob. */
21
21
  export declare const CLAUDE_SETUP_TOKEN_CAPTURE_RE: RegExp;
22
22
  export interface MintFlow {
@@ -37,7 +37,8 @@ export interface MintFlow {
37
37
  * key, COLLECTED via `--api-key` or a prompt by `accounts add` (never derived
38
38
  * from OAuth). The api-key entries describe collection only — they have no
39
39
  * mint argv, and `mintAndSeed` refuses them with the `accounts add` pointer.
40
- * Native device-code login (kimi/antigravity) stays on `agents fleet login`.
40
+ * A token-less harness (kimi/antigravity) has no portable credential and logs
41
+ * in per box (run it there and complete its native login).
41
42
  * The apiKeyEnv values are pinned to HARNESS_AUTH's `api-key:<ENV>` worker
42
43
  * kinds by auth-mint.test.ts — keep them in lockstep.
43
44
  */
@@ -5,7 +5,7 @@ import { bundleBackendSync, isSecretsClientError, bundleExists, bundleExistsSync
5
5
  import { assertStorableCredentialKind, reservedStoreName, } from './secrets-policy.js';
6
6
  import { getBinaryPath, getGlobalDefault, getVersionHomePath, listInstalledVersions } from './installations/versions.js';
7
7
  import { shellQuote } from './ssh-exec.js';
8
- import { defaultTermDriver, } from './fleet/remote-login.js';
8
+ import { defaultTermDriver, } from './term-driver.js';
9
9
  import { showUrl } from './open-url.js';
10
10
  import { loadDevices } from './devices/registry.js';
11
11
  import { isSelfHost } from './devices/self-host.js';
@@ -21,7 +21,8 @@ const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
21
21
  * key, COLLECTED via `--api-key` or a prompt by `accounts add` (never derived
22
22
  * from OAuth). The api-key entries describe collection only — they have no
23
23
  * mint argv, and `mintAndSeed` refuses them with the `accounts add` pointer.
24
- * Native device-code login (kimi/antigravity) stays on `agents fleet login`.
24
+ * A token-less harness (kimi/antigravity) has no portable credential and logs
25
+ * in per box (run it there and complete its native login).
25
26
  * The apiKeyEnv values are pinned to HARNESS_AUTH's `api-key:<ENV>` worker
26
27
  * kinds by auth-mint.test.ts — keep them in lockstep.
27
28
  */
@@ -63,7 +64,7 @@ export function unmintableMessage(harness) {
63
64
  return [
64
65
  `Cannot mint a setup-token for '${harness}'.`,
65
66
  `Interactive setup-token mint is implemented for: ${listMintableHarnesses().join(', ')}.`,
66
- `Device-code native login is \`agents fleet login --agent ${harness}\`.`,
67
+ `A token-less harness like '${harness}' logs in per box — run it on that device and complete its native login.`,
67
68
  `API-key / pasted-token accounts use \`agents accounts add <name> --provider <provider> --auth api-key\`.`,
68
69
  ].join(' ');
69
70
  }
@@ -534,7 +535,7 @@ async function syncMintedBundles(accountName, device) {
534
535
  const accountPush = await pushBundleToHost(accountName, device, {
535
536
  remoteBackend,
536
537
  force: true,
537
- operation: 'accounts mint --fleet',
538
+ operation: 'accounts login --fleet',
538
539
  policyNever: true,
539
540
  agentOnly: false,
540
541
  literalValues: {
@@ -550,7 +551,7 @@ async function syncMintedBundles(accountName, device) {
550
551
  const authPush = await pushBundleToHost(AUTH_BUNDLE, device, {
551
552
  remoteBackend: 'file',
552
553
  force: true,
553
- operation: 'accounts mint --fleet',
554
+ operation: 'accounts login --fleet',
554
555
  policyNever: true,
555
556
  agentOnly: true,
556
557
  });
@@ -7,7 +7,7 @@ interface ArcNativeTab extends ArcNativeTabRef {
7
7
  url: string;
8
8
  title: string;
9
9
  }
10
- interface ArcEnumeratedSpace {
10
+ export interface ArcEnumeratedSpace {
11
11
  windowId: string;
12
12
  spaceId: string;
13
13
  spaceTitle: string;
@@ -144,10 +144,59 @@ export declare function listAllProfileSnapshots(): ProfileSnapshot[];
144
144
  * alone — we'd rather leak than wrongly kill a user-owned process that
145
145
  * happens to share metadata.
146
146
  */
147
+ /**
148
+ * Clean up what a DEAD daemon genuinely left behind — which is not the browser.
149
+ *
150
+ * A live local browser outliving its daemon is the NORMAL case, not an orphan.
151
+ * `BrowserService.shutdown` deliberately closes only CDP and leaves the browser
152
+ * running, and on macOS the spawn is detached so the daemon's own positive-pid
153
+ * `killTree` reaches the daemon alone. This reaper used to read "the recorded
154
+ * `daemonPid` has exited" as "everything it recorded is garbage" and SIGTERM the
155
+ * browser — so replacing the daemon (a menubar activation, an upgrade, a crash
156
+ * restart) killed a browser the user was looking at, and `clearProfileRuntime`
157
+ * then removed the `port`/meta that {@link readProfileRuntime} needs, so even a
158
+ * survivor could not be re-attached and a second browser would launch beside it.
159
+ *
160
+ * The three cases are now decided independently:
161
+ *
162
+ * - **A live local browser/electron is preserved untouched** — not signalled, not
163
+ * cleared, and its `daemonPid` deliberately NOT rewritten. Ownership is adopted
164
+ * only after an identity-checked attach succeeds (`adoptProfileRuntimeOwner`,
165
+ * called from the reuse branch in `BrowserService.connectProfile`), so nothing
166
+ * claims a browser it has not proven it can talk to. Re-running this is a no-op.
167
+ * - **A dead browser record is cleared**, with no signal sent: there is nothing
168
+ * alive to signal, and the stale pid/port would otherwise mislead the next
169
+ * attach.
170
+ * - **A stale tunnel IS reaped.** A local `ssh -L` cannot outlive its purpose, so
171
+ * a dead owner does make it an orphan. This is the one thing the old code was
172
+ * right about; it just shared a code path with the browser.
173
+ *
174
+ * The accepted trade: a genuinely leaked browser from a long-dead daemon is no
175
+ * longer force-killed here. Idle/abandoned cleanup has its own owner in
176
+ * `browser/hygiene.ts` (session-dead + idle), which the daemon's periodic tick and
177
+ * `agents browser prune` both call — that is the right place for it, because it can
178
+ * tell "idle and abandoned" from "in use right now" and this function cannot.
179
+ */
147
180
  export declare function reapOrphanedProcesses(): {
148
181
  reaped: number;
149
182
  details: string[];
150
183
  };
184
+ /**
185
+ * Take ownership of a runtime record after an identity-checked attach succeeded.
186
+ *
187
+ * Called from the reuse branch in `BrowserService.connectProfile`, which has just
188
+ * probed `/json/version` and run `verifyBrowserIdentity` against the profile's
189
+ * expected browser family — so by this point the process on the recorded port is
190
+ * proven to be ours. Only then may a new service claim it.
191
+ *
192
+ * Rewrites `daemonPid` ALONE. Every other field — `pid`, `port`, `command`,
193
+ * `userDataDir`, `spawnedAt`, `kind`, `tunnelPid` — is the original launch's
194
+ * metadata and stays exactly as recorded: this is an ownership handover, not a
195
+ * re-registration, and rewriting the rest would lose the provenance the reaper and
196
+ * the hygiene pass both read. A record that has since disappeared is left alone
197
+ * rather than recreated from a partial view.
198
+ */
199
+ export declare function adoptProfileRuntimeOwner(profileName: string, daemonPid?: number): boolean;
151
200
  /**
152
201
  * True when ANY cache dir belonging to this profile still has a live browser or
153
202
  * SSH tunnel, or an open task. The composite naming (`<name>@<endpoint>`) means
@@ -233,4 +282,10 @@ export declare function buildProfilePrunePlan(): Promise<PrunePlan>;
233
282
  * went, matching what `--dry-run` printed.
234
283
  */
235
284
  export declare function pruneProfiles(plan: PrunePlan): Promise<PrunePlan>;
285
+ /**
286
+ * The executable/image name the live `pid` is running, or null if it can't be
287
+ * determined. The process-listing API differs per OS: Windows has no `ps`, so
288
+ * we query `tasklist` (CSV image name in column 1); POSIX uses `ps -o comm=`.
289
+ */
290
+ export declare function liveProcessCommand(pid: number): string | null;
236
291
  export {};