@phnx-labs/agents-cli 1.20.73 → 1.20.76

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 (209) hide show
  1. package/CHANGELOG.md +989 -0
  2. package/README.md +36 -10
  3. package/dist/bin/agents +0 -0
  4. package/dist/commands/apply.js +21 -3
  5. package/dist/commands/attach.d.ts +10 -0
  6. package/dist/commands/attach.js +41 -0
  7. package/dist/commands/computer.js +3 -3
  8. package/dist/commands/detach-core.d.ts +46 -0
  9. package/dist/commands/detach-core.js +61 -0
  10. package/dist/commands/detach.d.ts +5 -0
  11. package/dist/commands/detach.js +171 -0
  12. package/dist/commands/doctor.js +31 -1
  13. package/dist/commands/exec.js +31 -3
  14. package/dist/commands/export.d.ts +11 -0
  15. package/dist/commands/export.js +215 -0
  16. package/dist/commands/feed.d.ts +7 -1
  17. package/dist/commands/feed.js +47 -4
  18. package/dist/commands/go.d.ts +9 -2
  19. package/dist/commands/go.js +16 -6
  20. package/dist/commands/harness.d.ts +14 -0
  21. package/dist/commands/harness.js +145 -0
  22. package/dist/commands/import.js +87 -9
  23. package/dist/commands/inspect.js +5 -2
  24. package/dist/commands/profiles.d.ts +20 -0
  25. package/dist/commands/profiles.js +68 -21
  26. package/dist/commands/repo.js +14 -0
  27. package/dist/commands/routines.js +15 -2
  28. package/dist/commands/secrets.js +44 -21
  29. package/dist/commands/sessions-browser.js +1 -1
  30. package/dist/commands/sessions-export.js +19 -5
  31. package/dist/commands/sessions-migrate.d.ts +29 -0
  32. package/dist/commands/sessions-migrate.js +596 -0
  33. package/dist/commands/sessions-picker.js +16 -1
  34. package/dist/commands/sessions.d.ts +36 -13
  35. package/dist/commands/sessions.js +210 -40
  36. package/dist/commands/setup-computer.js +2 -2
  37. package/dist/commands/setup.js +6 -1
  38. package/dist/commands/ssh.js +71 -35
  39. package/dist/commands/status.js +10 -2
  40. package/dist/commands/versions.js +51 -11
  41. package/dist/commands/view.d.ts +5 -0
  42. package/dist/commands/view.js +99 -31
  43. package/dist/commands/watchdog.d.ts +7 -2
  44. package/dist/commands/watchdog.js +73 -57
  45. package/dist/index.js +59 -7
  46. package/dist/lib/activity.d.ts +14 -4
  47. package/dist/lib/activity.js +17 -5
  48. package/dist/lib/actor.d.ts +50 -0
  49. package/dist/lib/actor.js +166 -0
  50. package/dist/lib/agent-spec/provider.js +2 -1
  51. package/dist/lib/agent-spec/resolve.js +19 -5
  52. package/dist/lib/agent-spec/types.d.ts +9 -1
  53. package/dist/lib/agents.d.ts +56 -0
  54. package/dist/lib/agents.js +133 -7
  55. package/dist/lib/cloud/codex.d.ts +2 -0
  56. package/dist/lib/cloud/codex.js +14 -3
  57. package/dist/lib/cloud/session-index.d.ts +32 -0
  58. package/dist/lib/cloud/session-index.js +58 -0
  59. package/dist/lib/cloud/store.d.ts +7 -0
  60. package/dist/lib/cloud/store.js +25 -0
  61. package/dist/lib/config-transfer.d.ts +16 -0
  62. package/dist/lib/config-transfer.js +60 -0
  63. package/dist/lib/daemon.js +18 -2
  64. package/dist/lib/devices/connect.d.ts +15 -1
  65. package/dist/lib/devices/connect.js +15 -1
  66. package/dist/lib/devices/fleet.d.ts +21 -0
  67. package/dist/lib/devices/fleet.js +26 -0
  68. package/dist/lib/devices/health-report.d.ts +38 -0
  69. package/dist/lib/devices/health-report.js +214 -0
  70. package/dist/lib/devices/health.js +4 -1
  71. package/dist/lib/devices/reachability.d.ts +31 -0
  72. package/dist/lib/devices/reachability.js +40 -0
  73. package/dist/lib/devices/registry.d.ts +33 -0
  74. package/dist/lib/devices/registry.js +37 -0
  75. package/dist/lib/devices/resolve-target.d.ts +15 -27
  76. package/dist/lib/devices/resolve-target.js +63 -102
  77. package/dist/lib/devices/sync.d.ts +18 -0
  78. package/dist/lib/devices/sync.js +23 -1
  79. package/dist/lib/devices/tailscale.d.ts +3 -0
  80. package/dist/lib/devices/tailscale.js +1 -0
  81. package/dist/lib/events.d.ts +1 -1
  82. package/dist/lib/exec.d.ts +66 -0
  83. package/dist/lib/exec.js +195 -6
  84. package/dist/lib/export.d.ts +72 -0
  85. package/dist/lib/export.js +269 -0
  86. package/dist/lib/feed-post.d.ts +63 -0
  87. package/dist/lib/feed-post.js +204 -0
  88. package/dist/lib/fleet/apply.d.ts +32 -2
  89. package/dist/lib/fleet/apply.js +97 -10
  90. package/dist/lib/fleet/types.d.ts +11 -0
  91. package/dist/lib/fs-walk.d.ts +13 -0
  92. package/dist/lib/fs-walk.js +16 -7
  93. package/dist/lib/hooks.d.ts +1 -0
  94. package/dist/lib/hooks.js +156 -0
  95. package/dist/lib/hosts/dispatch.d.ts +7 -0
  96. package/dist/lib/hosts/dispatch.js +2 -0
  97. package/dist/lib/hosts/passthrough.js +2 -0
  98. package/dist/lib/hosts/reconnect.d.ts +78 -0
  99. package/dist/lib/hosts/reconnect.js +127 -0
  100. package/dist/lib/hosts/registry.d.ts +75 -14
  101. package/dist/lib/hosts/registry.js +205 -30
  102. package/dist/lib/hosts/remote-cmd.js +1 -0
  103. package/dist/lib/hosts/run-target.d.ts +13 -5
  104. package/dist/lib/hosts/run-target.js +26 -10
  105. package/dist/lib/hosts/session-index.d.ts +18 -1
  106. package/dist/lib/hosts/session-index.js +37 -2
  107. package/dist/lib/hosts/session-marker.d.ts +33 -0
  108. package/dist/lib/hosts/session-marker.js +51 -0
  109. package/dist/lib/hosts/tasks.d.ts +8 -4
  110. package/dist/lib/import.d.ts +25 -0
  111. package/dist/lib/import.js +107 -2
  112. package/dist/lib/isolation-boundary-report.d.ts +10 -0
  113. package/dist/lib/isolation-boundary-report.js +35 -0
  114. package/dist/lib/menubar/install-menubar.d.ts +20 -0
  115. package/dist/lib/menubar/install-menubar.js +45 -1
  116. package/dist/lib/migrate.d.ts +16 -0
  117. package/dist/lib/migrate.js +36 -0
  118. package/dist/lib/models.d.ts +27 -0
  119. package/dist/lib/models.js +54 -1
  120. package/dist/lib/overdue.d.ts +7 -2
  121. package/dist/lib/overdue.js +18 -11
  122. package/dist/lib/picker.js +4 -1
  123. package/dist/lib/platform/process.d.ts +17 -0
  124. package/dist/lib/platform/process.js +70 -0
  125. package/dist/lib/profiles-presets.js +9 -7
  126. package/dist/lib/profiles.d.ts +31 -0
  127. package/dist/lib/profiles.js +70 -0
  128. package/dist/lib/project-resources.js +20 -5
  129. package/dist/lib/pty-server.d.ts +2 -10
  130. package/dist/lib/pty-server.js +4 -38
  131. package/dist/lib/registry.d.ts +1 -1
  132. package/dist/lib/registry.js +48 -8
  133. package/dist/lib/rotate.d.ts +18 -0
  134. package/dist/lib/rotate.js +28 -0
  135. package/dist/lib/routines.d.ts +26 -0
  136. package/dist/lib/routines.js +19 -2
  137. package/dist/lib/runner.js +104 -7
  138. package/dist/lib/secrets/agent.d.ts +66 -1
  139. package/dist/lib/secrets/agent.js +154 -90
  140. package/dist/lib/secrets/bundles.d.ts +34 -0
  141. package/dist/lib/secrets/bundles.js +179 -19
  142. package/dist/lib/secrets/index.d.ts +15 -1
  143. package/dist/lib/secrets/index.js +21 -3
  144. package/dist/lib/secrets/rc-hygiene.d.ts +55 -0
  145. package/dist/lib/secrets/rc-hygiene.js +154 -0
  146. package/dist/lib/secrets/remote.d.ts +7 -4
  147. package/dist/lib/secrets/remote.js +7 -4
  148. package/dist/lib/secrets/sync-commands.d.ts +21 -0
  149. package/dist/lib/secrets/sync-commands.js +21 -0
  150. package/dist/lib/self-update.d.ts +23 -2
  151. package/dist/lib/self-update.js +86 -5
  152. package/dist/lib/session/active.d.ts +114 -7
  153. package/dist/lib/session/active.js +323 -52
  154. package/dist/lib/session/cloud.js +2 -1
  155. package/dist/lib/session/db.d.ts +48 -0
  156. package/dist/lib/session/db.js +238 -14
  157. package/dist/lib/session/detached.d.ts +30 -0
  158. package/dist/lib/session/detached.js +92 -0
  159. package/dist/lib/session/discover.d.ts +465 -2
  160. package/dist/lib/session/discover.js +1648 -468
  161. package/dist/lib/session/fork.js +2 -1
  162. package/dist/lib/session/hook-sessions.d.ts +43 -0
  163. package/dist/lib/session/hook-sessions.js +135 -0
  164. package/dist/lib/session/linear.d.ts +6 -0
  165. package/dist/lib/session/linear.js +55 -0
  166. package/dist/lib/session/migrate-targets.d.ts +65 -0
  167. package/dist/lib/session/migrate-targets.js +94 -0
  168. package/dist/lib/session/migrations.d.ts +37 -0
  169. package/dist/lib/session/migrations.js +60 -0
  170. package/dist/lib/session/parse.d.ts +18 -0
  171. package/dist/lib/session/parse.js +130 -30
  172. package/dist/lib/session/pid-registry.d.ts +34 -2
  173. package/dist/lib/session/pid-registry.js +49 -2
  174. package/dist/lib/session/render.d.ts +7 -2
  175. package/dist/lib/session/render.js +22 -10
  176. package/dist/lib/session/short-id.d.ts +17 -0
  177. package/dist/lib/session/short-id.js +20 -0
  178. package/dist/lib/session/state.js +6 -2
  179. package/dist/lib/shims.d.ts +27 -1
  180. package/dist/lib/shims.js +88 -6
  181. package/dist/lib/sqlite.d.ts +3 -2
  182. package/dist/lib/sqlite.js +27 -4
  183. package/dist/lib/staleness/detectors/commands.js +1 -1
  184. package/dist/lib/staleness/detectors/workflows.js +13 -2
  185. package/dist/lib/staleness/writers/commands.js +7 -7
  186. package/dist/lib/staleness/writers/workflows.d.ts +4 -2
  187. package/dist/lib/startup/command-registry.d.ts +2 -0
  188. package/dist/lib/startup/command-registry.js +5 -0
  189. package/dist/lib/state.d.ts +1 -1
  190. package/dist/lib/state.js +15 -36
  191. package/dist/lib/teams/agents.d.ts +19 -10
  192. package/dist/lib/teams/agents.js +40 -39
  193. package/dist/lib/types.d.ts +48 -2
  194. package/dist/lib/uninstall.js +1 -45
  195. package/dist/lib/usage.d.ts +33 -1
  196. package/dist/lib/usage.js +172 -12
  197. package/dist/lib/versions.d.ts +22 -2
  198. package/dist/lib/versions.js +98 -10
  199. package/dist/lib/watchdog/log.d.ts +43 -0
  200. package/dist/lib/watchdog/log.js +69 -0
  201. package/dist/lib/watchdog/routine.d.ts +44 -0
  202. package/dist/lib/watchdog/routine.js +69 -0
  203. package/dist/lib/watchdog/runner.d.ts +51 -7
  204. package/dist/lib/watchdog/runner.js +239 -64
  205. package/dist/lib/watchdog/watchdog.d.ts +1 -1
  206. package/dist/lib/watchdog/watchdog.js +31 -16
  207. package/dist/lib/workflows.d.ts +16 -0
  208. package/dist/lib/workflows.js +110 -1
  209. package/package.json +1 -1
@@ -4,13 +4,13 @@ import { visibleWidth, termLink } from '../lib/format.js';
4
4
  import ora from 'ora';
5
5
  import * as fs from 'fs';
6
6
  import * as path from 'path';
7
- import { AGENTS, ALL_AGENT_IDS, accountDisplayLabel, getAllCliStates, getAccountInfo, resolveAgentName, formatAgentError, agentLabel, colorAgent, } from '../lib/agents.js';
7
+ import { AGENTS, ALL_AGENT_IDS, accountDisplayLabel, getAllCliStates, getUnmanagedCliState, getAccountInfo, resolveAgentName, formatAgentError, agentLabel, colorAgent, } from '../lib/agents.js';
8
8
  import { loginHint } from '../lib/signin-badge.js';
9
9
  import { machineId } from '../lib/machine-id.js';
10
10
  import { authCacheKey, formatCheckedAge, readAuthHealthCache } from '../lib/auth-health.js';
11
11
  import { agentReportsUsage, deriveUsageStatusFromSnapshot, formatUsageSection, formatUsageSummary, formatUsageStatusBadge, getUsageInfoForIdentity, getUsageInfoByIdentity, getUsageLookupKey, } from '../lib/usage.js';
12
12
  import { readManifest } from '../lib/manifest.js';
13
- import { listInstalledVersions, listInstalledVersionDirs, getGlobalDefault, getVersionHomePath, getVersionDir, getAvailableResources, getActuallySyncedResources, getNewResources, getProjectOnlyResources, hasNewResources, promptNewResourceSelection, syncResourcesToVersion, removeVersion, printTrashFooter, reconcileStaleLatestForAgent, isGlobalBinaryAgent, getLiveVersion, } from '../lib/versions.js';
13
+ import { listInstalledVersions, listInstalledVersionDirs, getGlobalDefault, getVersionHomePath, getVersionDir, getAvailableResources, getActuallySyncedResources, getNewResources, getProjectOnlyResources, hasNewResources, promptNewResourceSelection, syncResourcesToVersion, removeVersion, printTrashFooter, reconcileStaleLatestForAgent, isGlobalBinaryAgent, getLiveVersion, isVersionIsolated, getIsolatedDefault, } from '../lib/versions.js';
14
14
  import { ensureVersionedAliasCurrent, removeShim, } from '../lib/shims.js';
15
15
  import { getAgentResources } from '../lib/resources.js';
16
16
  import { resolveVersionFilter, AgentSpecError } from '../lib/agent-spec/index.js';
@@ -23,6 +23,7 @@ import { getCentralRulesFileName } from '../lib/rules/rules.js';
23
23
  import { composeRulesFromState } from '../lib/rules/compose.js';
24
24
  import { getConfiguredRunStrategy } from '../lib/rotate.js';
25
25
  import { resolveRunDefaults } from '../lib/run-defaults.js';
26
+ import { resolveConfiguredModel } from '../lib/models.js';
26
27
  import { listProfiles, profileSummary } from '../lib/profiles.js';
27
28
  import { loadManifest, isStale } from '../lib/staleness/index.js';
28
29
  import { confirm } from '@inquirer/prompts';
@@ -243,9 +244,27 @@ async function showInstalledVersions(filterAgentId, viewOpts) {
243
244
  ? `Checking ${agentLabel(filterAgentId)} agents...`
244
245
  : 'Checking installed agents...';
245
246
  const spinner = ora({ text: spinnerText, isSilent: !process.stdout.isTTY }).start();
246
- const cliStates = await getAllCliStates();
247
- spinner.stop();
248
247
  const agentsToShow = filterAgentId ? [filterAgentId] : ALL_AGENT_IDS;
248
+ // A globally-installed CLI is superseded only by a NORMAL managed version — that
249
+ // is when agents-cli owns the launcher and a "global" row would just be our own
250
+ // shim reported back. `--isolated` promises the opposite: no default, no bare
251
+ // shim, no adopted launcher, the user's own `~/.<agent>` untouched. So an
252
+ // isolated-only install must not make that still-live global CLI disappear from
253
+ // `agents view` — the two are genuinely separate installs and both get listed.
254
+ const hasNonIsolatedVersion = (agentId) => listInstalledVersions(agentId).some((v) => !isVersionIsolated(agentId, v));
255
+ // Every `cliStates` read in this function feeds the "Not Managed by Agents CLI"
256
+ // block, so resolve it the way that block means it: the user's own CLI on PATH.
257
+ // `getCliState` would answer with a version-dir install — including an isolated
258
+ // copy that is deliberately absent from PATH — and print it as "(global)".
259
+ //
260
+ // Resolved only for agents that can actually reach that block. `getCliState`
261
+ // deliberately avoids subprocesses for a version-managed agent, and PATH
262
+ // resolution costs a `<cli> --version` spawn on a cold cache — so probing an
263
+ // agent whose global row is suppressed anyway would be pure added latency.
264
+ const cliStates = Object.fromEntries(await Promise.all(agentsToShow
265
+ .filter((agentId) => !hasNonIsolatedVersion(agentId))
266
+ .map(async (agentId) => [agentId, await getUnmanagedCliState(agentId)])));
267
+ spinner.stop();
249
268
  const showPaths = !!filterAgentId;
250
269
  const profilesByAgent = getProfilesByAgent(filterAgentId);
251
270
  const profileSummaries = [...profilesByAgent.values()].flat();
@@ -276,19 +295,18 @@ async function showInstalledVersions(filterAgentId, viewOpts) {
276
295
  const infoFetches = [];
277
296
  const globalInfoFetches = [];
278
297
  for (const agentId of agentsToShow) {
279
- const versions = listInstalledVersions(agentId);
280
- if (versions.length > 0) {
281
- for (const ver of versions) {
282
- const home = getVersionHomePath(agentId, ver);
283
- infoFetches.push(getAccountInfo(agentId, home).then((info) => ({
284
- agentId,
285
- version: ver,
286
- home,
287
- info,
288
- })));
289
- }
298
+ for (const ver of listInstalledVersions(agentId)) {
299
+ const home = getVersionHomePath(agentId, ver);
300
+ infoFetches.push(getAccountInfo(agentId, home).then((info) => ({
301
+ agentId,
302
+ version: ver,
303
+ home,
304
+ info,
305
+ })));
290
306
  }
291
- else {
307
+ // Mirrors the classification below: fetch the global account whenever the
308
+ // global install will still be rendered (no versions at all, or isolated-only).
309
+ if (!hasNonIsolatedVersion(agentId)) {
292
310
  globalInfoFetches.push(getAccountInfo(agentId).then((info) => ({
293
311
  agentId,
294
312
  cliVersion: cliStates[agentId]?.version || null,
@@ -356,10 +374,12 @@ async function showInstalledVersions(filterAgentId, viewOpts) {
356
374
  if (versions.length > 0) {
357
375
  versionManaged.push(agentId);
358
376
  }
359
- else if (cliState?.installed) {
360
- globallyInstalled.push(agentId);
377
+ if (cliState?.installed) {
378
+ // Isolated-only installs sit alongside the global CLI rather than replacing it.
379
+ if (!hasNonIsolatedVersion(agentId))
380
+ globallyInstalled.push(agentId);
361
381
  }
362
- else if (hasProfiles) {
382
+ else if (versions.length === 0 && hasProfiles) {
363
383
  profileOnly.push(agentId);
364
384
  }
365
385
  }
@@ -376,6 +396,22 @@ async function showInstalledVersions(filterAgentId, viewOpts) {
376
396
  liveVersionByAgent.set(agentId, live);
377
397
  }));
378
398
  const displayVersion = (agentId, dirVersion) => liveVersionByAgent.get(agentId) ?? dirVersion;
399
+ // Uncolored row label, shared by the width pass and the render so padding lines
400
+ // up. An isolated copy is never the global default (installing one deliberately
401
+ // records no default), so the two tags can't collide.
402
+ const versionRowLabel = (agentId, version, globalDefault) => {
403
+ const shown = displayVersion(agentId, version);
404
+ if (version === globalDefault)
405
+ return `${shown} (default)`;
406
+ if (isVersionIsolated(agentId, version)) {
407
+ // The isolated default is what a bare `agents run <agent>` reaches, so it is
408
+ // worth distinguishing from the other isolated copies sitting beside it.
409
+ return getIsolatedDefault(agentId) === version
410
+ ? `${shown} (isolated default)`
411
+ : `${shown} (isolated)`;
412
+ }
413
+ return shown;
414
+ };
379
415
  // Show version-managed agents
380
416
  if (versionManaged.length > 0) {
381
417
  // Calculate column widths across all agents for alignment
@@ -384,13 +420,15 @@ async function showInstalledVersions(filterAgentId, viewOpts) {
384
420
  let maxPlanWidth = 3;
385
421
  let maxUsageWidth = 0;
386
422
  let maxStatusWidth = 0;
423
+ // The configured model sits right after the version, at the same priority.
424
+ // Resolve once here (fs + catalog reads) and reuse in the render loop.
425
+ let maxModelWidth = 0;
426
+ const modelByKey = new Map();
387
427
  for (const agentId of versionManaged) {
388
428
  const versions = listInstalledVersions(agentId);
389
429
  const globalDefault = getGlobalDefault(agentId);
390
430
  for (const v of versions) {
391
- const shown = displayVersion(agentId, v);
392
- const label = v === globalDefault ? `${shown} (default)` : shown;
393
- maxVerLabel = Math.max(maxVerLabel, label.length);
431
+ maxVerLabel = Math.max(maxVerLabel, versionRowLabel(agentId, v, globalDefault).length);
394
432
  const rawInfo = infoMap.get(`${agentId}:${v}`);
395
433
  const info = rawInfo ? mergeCanonical(rawInfo) : undefined;
396
434
  const accountLabel = accountColumnLabel(info);
@@ -398,6 +436,11 @@ async function showInstalledVersions(filterAgentId, viewOpts) {
398
436
  maxEmail = Math.max(maxEmail, accountLabel.length);
399
437
  if (info?.plan)
400
438
  maxPlanWidth = Math.max(maxPlanWidth, info.plan.length);
439
+ const model = resolveConfiguredModel(agentId, v)?.model;
440
+ if (model) {
441
+ modelByKey.set(`${agentId}:${v}`, model);
442
+ maxModelWidth = Math.max(maxModelWidth, model.length);
443
+ }
401
444
  }
402
445
  // Profile rows share these columns with version rows so they line up.
403
446
  for (const profile of profilesByAgent.get(agentId) ?? []) {
@@ -431,7 +474,14 @@ async function showInstalledVersions(filterAgentId, viewOpts) {
431
474
  const globalDefault = getGlobalDefault(agentId);
432
475
  const runStrategy = getConfiguredRunStrategy(agentId);
433
476
  const strategyLabel = chalk.gray(` (${runStrategy})`);
434
- const noDefaultLabel = !globalDefault ? chalk.yellow(' (no default)') : '';
477
+ // `(no default)` is a nudge to go set one. It would read as a contradiction
478
+ // directly above a row tagged `(isolated default)`, and it would be bad
479
+ // advice besides: for an isolated-only agent the pointer below IS how a
480
+ // bare `agents run <agent>` resolves, and setting a global default is
481
+ // precisely what `--isolated` exists to avoid.
482
+ const noDefaultLabel = !globalDefault && !getIsolatedDefault(agentId)
483
+ ? chalk.yellow(' (no default)')
484
+ : '';
435
485
  console.log(` ${chalk.bold(agentLabel(agentId))}${strategyLabel}${noDefaultLabel}`);
436
486
  // Sort versions with default first, then by semver descending
437
487
  const sortedVersions = [...versions].sort((a, b) => {
@@ -443,16 +493,27 @@ async function showInstalledVersions(filterAgentId, viewOpts) {
443
493
  });
444
494
  for (const version of sortedVersions) {
445
495
  const isDefault = version === globalDefault;
496
+ const isolated = !isDefault && isVersionIsolated(agentId, version);
497
+ const isolatedTag = getIsolatedDefault(agentId) === version ? ' (isolated default)' : ' (isolated)';
446
498
  const shown = displayVersion(agentId, version);
447
- const base = isDefault ? `${shown} (default)` : shown;
448
- const padded = base.padEnd(maxVerLabel);
449
- const label = isDefault ? `${shown}${chalk.green(' (default)')}${' '.repeat(maxVerLabel - base.length)}` : padded;
499
+ const base = versionRowLabel(agentId, version, globalDefault);
500
+ const tagPad = ' '.repeat(maxVerLabel - base.length);
501
+ const label = isDefault
502
+ ? `${shown}${chalk.green(' (default)')}${tagPad}`
503
+ : isolated
504
+ ? `${shown}${chalk.gray(isolatedTag)}${tagPad}`
505
+ : base.padEnd(maxVerLabel);
450
506
  const rawInfo = infoMap.get(`${agentId}:${version}`);
451
507
  const vInfo = rawInfo ? mergeCanonical(rawInfo) : undefined;
452
508
  const usageKey = getUsageLookupKey(vInfo);
453
509
  const usageInfo = usageKey ? usageByKey.get(usageKey) : undefined;
454
510
  // Build columns, trimming trailing whitespace when columns are empty
455
511
  const parts = [` ${label}`];
512
+ // Configured model — same priority as the version, right beside it.
513
+ if (maxModelWidth > 0) {
514
+ const model = modelByKey.get(`${agentId}:${version}`) ?? '';
515
+ parts.push(chalk.yellow(model.padEnd(maxModelWidth)));
516
+ }
456
517
  const hasEmail = !!vInfo?.email;
457
518
  const signedIn = !!vInfo?.signedIn;
458
519
  const usageUnavailable = agentReportsUsage(agentId) && signedIn && !usageInfo?.snapshot;
@@ -462,12 +523,11 @@ async function showInstalledVersions(filterAgentId, viewOpts) {
462
523
  // Otherwise it reflects install time (misleading "just now" for fresh installs).
463
524
  const activeStr = vInfo && hasEmail ? formatLastActive(vInfo.lastActive) : '';
464
525
  const hasActive = activeStr.length > 0;
526
+ // The model now has its own column above; keep only the run mode here.
465
527
  const runDefaults = resolveRunDefaults(agentId, version);
466
528
  const runDefaultBits = [];
467
529
  if (runDefaults.mode)
468
530
  runDefaultBits.push(`mode:${runDefaults.mode}`);
469
- if (runDefaults.model)
470
- runDefaultBits.push(`model:${runDefaults.model}`);
471
531
  if (!hasEmail && !hasUsage && !signedIn) {
472
532
  // Installed but never signed in
473
533
  parts.push(chalk.gray('(logged out — log in with: ' + loginHint(agentId) + ')'));
@@ -509,10 +569,12 @@ async function showInstalledVersions(filterAgentId, viewOpts) {
509
569
  // No status badge, no last-active — profiles don't accumulate usage state.
510
570
  for (const profile of profilesByAgent.get(agentId) ?? []) {
511
571
  const nameCol = chalk.cyan(profile.name.padEnd(maxVerLabel));
572
+ // Pad the model column so profile rows line up with version rows.
573
+ const modelPad = maxModelWidth > 0 ? `${' '.repeat(maxModelWidth)} ` : '';
512
574
  const authCol = chalk.gray(profile.auth.padEnd(maxEmail));
513
575
  const usageEquivalent = profileKindAndModel(profile.model, maxPlanWidth);
514
576
  const usagePad = ' '.repeat(Math.max(0, maxUsageWidth - visibleWidth(usageEquivalent)));
515
- console.log(` ${nameCol} ${authCol} ${chalk.gray(usageEquivalent + usagePad)}`);
577
+ console.log(` ${nameCol} ${modelPad}${authCol} ${chalk.gray(usageEquivalent + usagePad)}`);
516
578
  if (showPaths) {
517
579
  console.log(chalk.gray(` ${profile.path}`));
518
580
  }
@@ -580,7 +642,9 @@ async function showInstalledVersions(filterAgentId, viewOpts) {
580
642
  // Profile rows under a globally-installed harness. Use a simpler
581
643
  // alignment here since this section doesn't share column state with
582
644
  // the version-managed block.
583
- const profilesHere = profilesByAgent.get(agentId) ?? [];
645
+ // An isolated-only agent now appears in BOTH blocks; its profiles already
646
+ // rendered under the version-managed one, so don't print them twice.
647
+ const profilesHere = versionManaged.includes(agentId) ? [] : (profilesByAgent.get(agentId) ?? []);
584
648
  if (profilesHere.length > 0) {
585
649
  const nameWidth = Math.max(globalMaxVerLabel, ...profilesHere.map((p) => p.name.length));
586
650
  const authWidth = Math.max(...profilesHere.map((p) => p.auth.length));
@@ -833,9 +897,12 @@ async function showAgentResources(agentId, requestedVersion, filter) {
833
897
  const accountLabel = accountColumnLabel(accountInfo);
834
898
  const emailStr = accountLabel ? chalk.cyan(` ${accountLabel}`) : '';
835
899
  const status = chalk.green(version);
900
+ // Configured model sits right beside the version, same priority (no label).
901
+ const configuredModel = resolveConfiguredModel(agentId, version);
902
+ const modelStr = configuredModel ? chalk.yellow(` ${configuredModel.model}`) : '';
836
903
  const usageStr = formatUsageSummary(usageInfo.snapshot?.plan ?? accountInfo.plan, null);
837
904
  const usagePart = usageStr ? ` ${usageStr}` : '';
838
- console.log(` ${colorAgent(agentId)(AGENTS[agentId].name.padEnd(14))} ${status}${emailStr}${usagePart}`);
905
+ console.log(` ${colorAgent(agentId)(AGENTS[agentId].name.padEnd(14))} ${status}${modelStr}${emailStr}${usagePart}`);
839
906
  const usageLines = formatUsageSection(usageInfo);
840
907
  if (usageLines.length > 0) {
841
908
  console.log();
@@ -1173,6 +1240,7 @@ async function collectAgentsJson(filterAgentId, resourceSections) {
1173
1240
  : [],
1174
1241
  lastActive: info.lastActive ? info.lastActive.toISOString() : null,
1175
1242
  path: getVersionDir(agentId, version),
1243
+ configuredModel: resolveConfiguredModel(agentId, version),
1176
1244
  };
1177
1245
  if (wantResources && resourceSync) {
1178
1246
  entry.resources = collectVersionResources(agentId, home, process.cwd(), cliStates?.[agentId]?.installed ?? false, resourceSections, resourceSync);
@@ -8,10 +8,15 @@
8
8
  *
9
9
  * agents watchdog one tick, dry — prints what it WOULD nudge/skip and why
10
10
  * agents watchdog --nudge one tick, actually injects (explicit opt-in)
11
- * agents watchdog --watch daemon loop (poll every --interval)
11
+ * agents watchdog --watch manual poll loop (dry unless --nudge)
12
12
  * agents watchdog --json machine-readable tick output (for the menu-bar)
13
- * agents watchdog enable|disable flip the global auto-nudge sentinel (default OFF)
13
+ * agents watchdog enable|disable turn the always-on watchdog routine on/off
14
14
  * agents watchdog policy <id> <p> per-session policy: off | keep | handsoff
15
+ *
16
+ * The always-on watchdog is a daemon-fired ROUTINE, not a private sentinel + a
17
+ * hand-rolled loop: `enable` creates/enables a `watchdog` command routine
18
+ * (`agents watchdog --nudge` every couple of minutes) and reloads the daemon;
19
+ * `disable` pauses it. See ../lib/watchdog/routine.ts.
15
20
  */
16
21
  import type { Command } from 'commander';
17
22
  /** Register the `agents watchdog` command tree. */
@@ -8,41 +8,41 @@
8
8
  *
9
9
  * agents watchdog one tick, dry — prints what it WOULD nudge/skip and why
10
10
  * agents watchdog --nudge one tick, actually injects (explicit opt-in)
11
- * agents watchdog --watch daemon loop (poll every --interval)
11
+ * agents watchdog --watch manual poll loop (dry unless --nudge)
12
12
  * agents watchdog --json machine-readable tick output (for the menu-bar)
13
- * agents watchdog enable|disable flip the global auto-nudge sentinel (default OFF)
13
+ * agents watchdog enable|disable turn the always-on watchdog routine on/off
14
14
  * agents watchdog policy <id> <p> per-session policy: off | keep | handsoff
15
+ *
16
+ * The always-on watchdog is a daemon-fired ROUTINE, not a private sentinel + a
17
+ * hand-rolled loop: `enable` creates/enables a `watchdog` command routine
18
+ * (`agents watchdog --nudge` every couple of minutes) and reloads the daemon;
19
+ * `disable` pauses it. See ../lib/watchdog/routine.ts.
15
20
  */
16
21
  import chalk from 'chalk';
17
- import * as fs from 'fs';
18
22
  import * as path from 'path';
19
23
  import { setHelpSections } from '../lib/help.js';
20
24
  import { parseDuration } from '../lib/hooks/cache.js';
21
25
  import { getRuntimeStateDir } from '../lib/state.js';
26
+ import { setJobEnabled } from '../lib/routines.js';
27
+ import { ensureWatchdogRoutine, isWatchdogRoutineEnabled, watchdogRoutineExists, WATCHDOG_ROUTINE_NAME, WATCHDOG_ROUTINE_SCHEDULE, } from '../lib/watchdog/routine.js';
22
28
  import { runWatchdogTick, writePolicySentinel, DEFAULT_THRESHOLDS, } from '../lib/watchdog/runner.js';
23
29
  /** Default state dir the runner and these subcommands share. */
24
30
  function stateDir() {
25
31
  return path.join(getRuntimeStateDir(), 'watchdog');
26
32
  }
27
- /** Global auto-nudge sentinel — present = enabled. Default OFF (opt-in). */
28
- function enabledSentinelPath() {
29
- return path.join(stateDir(), 'enabled');
30
- }
31
- function isGloballyEnabled() {
32
- return fs.existsSync(enabledSentinelPath());
33
- }
34
- function setGloballyEnabled(on) {
35
- const p = enabledSentinelPath();
36
- if (on) {
37
- fs.mkdirSync(path.dirname(p), { recursive: true });
38
- fs.writeFileSync(p, 'enabled\n');
39
- }
40
- else {
41
- try {
42
- fs.rmSync(p);
43
- }
44
- catch { /* already off */ }
33
+ /**
34
+ * (Re)load the daemon so a just-changed routine takes effect without a restart.
35
+ * Best-effort: enabling starts the daemon if it is not running, then SIGHUPs it.
36
+ * Dynamic import keeps daemon.ts's heavy deps off the watchdog command's load path.
37
+ */
38
+ async function reloadDaemonForRoutine(startIfStopped) {
39
+ const { isDaemonRunning, ensureDaemonStarted, signalDaemonReload } = await import('../lib/daemon.js');
40
+ if (isDaemonRunning()) {
41
+ signalDaemonReload();
42
+ return;
45
43
  }
44
+ if (startIfStopped)
45
+ ensureDaemonStarted();
46
46
  }
47
47
  /** Parse a duration flag ("60s", "5m", "1h") to ms, or fall back to `fallbackMs`. */
48
48
  function durationMsOr(raw, fallbackMs) {
@@ -97,7 +97,7 @@ export function registerWatchdogCommand(program) {
97
97
  .command('watchdog')
98
98
  .description('Auto-nudge stalled agent terminals: detect stalls, resolve the exact split, inject "Continue." — no menu-bar needed.')
99
99
  .option('--nudge', 'Actually inject (default is a dry run that only reports what it would do)')
100
- .option('--watch', 'Daemon loop: run a tick every --interval until interrupted')
100
+ .option('--watch', 'Manual poll loop: run a tick every --interval (dry unless --nudge; the always-on path is `watchdog enable`)')
101
101
  .option('--interval <dur>', 'Poll interval in --watch mode (e.g. 30s, 1m)', '30s')
102
102
  .option('--stall <dur>', 'Idle time before a session counts as stalled', humanMs(DEFAULT_THRESHOLDS.stallMs))
103
103
  .option('--cooldown <dur>', 'Minimum time between nudges to the same session', humanMs(DEFAULT_THRESHOLDS.cooldownMs))
@@ -113,12 +113,11 @@ export function registerWatchdogCommand(program) {
113
113
  cooldownMs: durationMsOr(opts.cooldown, DEFAULT_THRESHOLDS.cooldownMs),
114
114
  dormantMs: durationMsOr(opts.dormant, DEFAULT_THRESHOLDS.dormantMs),
115
115
  };
116
- // Injection gate: --nudge is the explicit per-run opt-in; the global sentinel
117
- // enables the automatic daemon. Default OFF: bare `agents watchdog` is dry.
118
- // Re-read the sentinel every tick so a running `--watch` daemon reflects a
119
- // later `agents watchdog enable`/`disable` (or the Swift menu-bar toggle)
120
- // from another shell without a restart.
121
- const computeWillInject = () => opts.nudge === true || (opts.watch === true && isGloballyEnabled());
116
+ // Injection gate: --nudge is the explicit opt-in to actually inject. Bare
117
+ // `agents watchdog` (and `--watch` without `--nudge`) is dry. The always-on
118
+ // path is the daemon routine, which runs `agents watchdog --nudge` so the
119
+ // routine's enabled state IS the on/off switch, not a flag read here.
120
+ const computeWillInject = () => opts.nudge === true;
122
121
  const tickOnce = async (willInject) => runWatchdogTick({
123
122
  nudge: willInject,
124
123
  nudgeText: opts.text,
@@ -137,11 +136,11 @@ export function registerWatchdogCommand(program) {
137
136
  printTick(result, willInject);
138
137
  return;
139
138
  }
140
- // Daemon loop.
139
+ // Manual poll loop (for ad-hoc use; the always-on path is `enable`'s routine).
141
140
  const intervalMs = durationMsOr(opts.interval, 30_000);
142
141
  if (!computeWillInject() && !opts.json) {
143
- console.log(chalk.yellow(`watchdog --watch is DETECT-ONLY (global auto-nudge is ${isGloballyEnabled() ? 'on' : 'off'}). ` +
144
- `Pass --nudge or run 'agents watchdog enable' to inject.`));
142
+ console.log(chalk.yellow(`watchdog --watch is DETECT-ONLY. Pass --nudge to inject, ` +
143
+ `or run 'agents watchdog enable' for the always-on daemon routine.`));
145
144
  }
146
145
  // eslint-disable-next-line no-constant-condition
147
146
  while (true) {
@@ -163,50 +162,67 @@ export function registerWatchdogCommand(program) {
163
162
  # One tick, actually inject "Continue." into stalled+addressable splits
164
163
  agents watchdog --nudge
165
164
 
166
- # Watch loop every 30s, tighter stall threshold
167
- agents watchdog --watch --interval 30s --stall 60s --cooldown 5m
165
+ # Manual watch loop every 30s, tighter stall threshold (ad-hoc; dry unless --nudge)
166
+ agents watchdog --watch --nudge --interval 30s --stall 60s --cooldown 5m
168
167
 
169
168
  # Machine-readable for the menu-bar
170
169
  agents watchdog --json
171
170
 
172
- # Turn on the global auto-nudge (so --watch injects without --nudge)
171
+ # Turn on the ALWAYS-ON watchdog (a daemon-fired routine)
173
172
  agents watchdog enable
174
173
 
175
174
  # Leave one session detected-but-untouched
176
175
  agents watchdog policy <sessionId> handsoff
177
176
  `,
178
177
  notes: `
179
- Decision path (default, deterministic): a session is nudged only when its
180
- transcript tail shows it ANNOUNCED an action but no tool call followed
181
- (promise-without-toolcall) AND it is not waiting on the user. Completions and
182
- open questions are skipped.
178
+ Decision path: a cheap deterministic pre-filter resolves the obvious cases
179
+ (clearly complete -> skip; a clear promise-without-toolcall -> nudge) and
180
+ ESCALATES the judgment-heavy cases -- a session parked on a question, or an
181
+ ambiguous stall -- to a smart brain. The brain drives the agent to finish
182
+ end-to-end when it asked a needless / already-authorized question or paused
183
+ with work left, and leaves it for the human on genuine cases (credentials,
184
+ an irreversible or outward-facing action, a real ambiguous decision, or a
185
+ completed task). The brain is a customizable 'watchdog' workflow (drop a
186
+ WORKFLOW.md in project/user workflows/ to override the prompt + model);
187
+ absent one, the built-in prompt runs via 'agents run --mode plan'. Pass
188
+ --smart to force every stalled candidate through the brain.
183
189
 
184
- Safety gate: a nudge is delivered ONLY when the resolver can name the EXACT
185
- split the agent lives in (tmux / iTerm / IDE terminal). Un-addressable stalls
186
- (e.g. Ghostty with no tmux) are flagged for the menu-bar and SKIPPED never
187
- a guessed or frontmost target.
190
+ Delivery (answer-router): a running agent is steered via its mailbox; a
191
+ parked-on-question agent is answered into its EXACT split -- tmux / iTerm /
192
+ an IDE integrated terminal (VS Codium / Cursor / VS Code) -- or re-entered
193
+ via resume when headless. A parked agent with no addressable rail (e.g.
194
+ Ghostty with no tmux) is flagged for the menu-bar and SKIPPED -- never a
195
+ guessed or frontmost target.
188
196
 
189
- Policy: global auto-nudge defaults OFF (opt-in via 'enable' or --nudge).
190
- Per-session: off (ignore), keep (default), handsoff (detect + flag, never inject).
197
+ Always-on: 'agents watchdog enable' creates + enables a 'watchdog' command
198
+ routine ('${WATCHDOG_ROUTINE_SCHEDULE}' -> agents watchdog --nudge) and reloads the
199
+ daemon; 'disable' pauses it. Inspect it with 'agents routines list'. Defaults
200
+ OFF. Per-session policy: off (ignore), keep (default), handsoff (detect + flag).
191
201
 
192
202
  State (tray-readable): ${path.join('~/.agents/.cache/state/watchdog', '{nudges,flags,last-tick}.json')}
193
203
  `,
194
204
  });
195
- // --- global enable/disable/status -----------------------------------------
205
+ // --- always-on enable/disable/status (backed by the daemon routine) --------
196
206
  cmd.command('enable')
197
- .description('Turn ON global auto-nudge (so `agents watchdog --watch` injects without --nudge).')
198
- .action(() => {
199
- setGloballyEnabled(true);
200
- console.log(chalk.green('watchdog: global auto-nudge ENABLED'));
207
+ .description('Turn ON the always-on watchdog: create/enable the `watchdog` routine and (re)load the daemon.')
208
+ .action(async () => {
209
+ ensureWatchdogRoutine(true);
210
+ await reloadDaemonForRoutine(true);
211
+ console.log(chalk.green(`watchdog: ENABLED — routine '${WATCHDOG_ROUTINE_NAME}' fires ${WATCHDOG_ROUTINE_SCHEDULE} (agents watchdog --nudge)`));
201
212
  });
202
213
  cmd.command('disable')
203
- .description('Turn OFF global auto-nudge (back to detect-only unless --nudge is passed).')
204
- .action(() => {
205
- setGloballyEnabled(false);
206
- console.log(chalk.yellow('watchdog: global auto-nudge DISABLED'));
214
+ .description('Turn OFF the always-on watchdog (pause the `watchdog` routine).')
215
+ .action(async () => {
216
+ if (!watchdogRoutineExists()) {
217
+ console.log(chalk.dim('watchdog: already off (no routine)'));
218
+ return;
219
+ }
220
+ setJobEnabled(WATCHDOG_ROUTINE_NAME, false);
221
+ await reloadDaemonForRoutine(false);
222
+ console.log(chalk.yellow('watchdog: DISABLED (routine paused)'));
207
223
  });
208
224
  cmd.command('status')
209
- .description('Show whether global auto-nudge is on and where state is written.')
225
+ .description('Show whether the always-on watchdog routine is enabled and where state is written.')
210
226
  .option('--json', 'Emit status as JSON (for the menu-bar / scripts)')
211
227
  .action((_opts, command) => {
212
228
  // The parent `watchdog` command also declares --json and greedily parses it
@@ -214,12 +230,12 @@ export function registerWatchdogCommand(program) {
214
230
  // parent, not this subcommand. optsWithGlobals() merges both levels, so we
215
231
  // read it correctly regardless of which command commander bound it to.
216
232
  const json = command.optsWithGlobals().json === true;
217
- const on = isGloballyEnabled();
233
+ const on = isWatchdogRoutineEnabled();
218
234
  if (json) {
219
- console.log(JSON.stringify({ enabled: on, stateDir: stateDir() }));
235
+ console.log(JSON.stringify({ enabled: on, routine: WATCHDOG_ROUTINE_NAME, stateDir: stateDir() }));
220
236
  return;
221
237
  }
222
- console.log(`global auto-nudge: ${on ? chalk.green('ON') : chalk.dim('off')}`);
238
+ console.log(`always-on watchdog: ${on ? chalk.green('ON') : chalk.dim('off')} (routine '${WATCHDOG_ROUTINE_NAME}')`);
223
239
  console.log(`state dir: ${chalk.dim(stateDir())}`);
224
240
  });
225
241
  // --- per-session policy ----------------------------------------------------
package/dist/index.js CHANGED
@@ -26,11 +26,49 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
26
26
  const packageJsonPath = path.join(__dirname, '..', 'package.json');
27
27
  const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
28
28
  const VERSION = packageJson.version;
29
+ import { SYNC_GET_CMD, SYNC_PING_CMD, SYNC_LOCK_CMD } from './lib/secrets/sync-commands.js';
29
30
  if (process.argv[2] === '__vault-age-helper') {
30
31
  const { runVaultAgeHelperCli } = await import('./lib/secrets/vault-age-helper.js');
31
32
  await runVaultAgeHelperCli();
32
33
  process.exit(process.exitCode ?? 0);
33
34
  }
35
+ // Synchronous secrets-broker clients (src/lib/secrets/agent.ts). These are the
36
+ // hot read path: `readAndResolveBundleEnv` is synchronous all the way down, so
37
+ // it can't await a socket round-trip — it spawns one of these and reads the
38
+ // exit code (0 = hit/alive, 3 = miss/down).
39
+ //
40
+ // Intercepted HERE, before commander and before the startup STATEMENTS, for the
41
+ // same reason as __daemon-run and __vault-age-helper above. Everything below
42
+ // this point runs `checkForUpdates()` and `spawnDetachedSync()` on every
43
+ // non-help invocation — so registering these as ordinary hidden subcommands
44
+ // would fire an update check and fork a detached background sync on *every
45
+ // cache hit*, which is both a fork storm on the hot path and a source of stdout
46
+ // writes that could corrupt the JSON payload. Keep them above the line;
47
+ // agent.test.ts asserts this ordering so the block can't drift downward.
48
+ //
49
+ // The tokens are imported from the leaf module sync-commands.ts — the SAME
50
+ // bindings the clients spawn with, so this dispatch and those spawns cannot
51
+ // drift apart. It is a leaf precisely so binding them here is free: importing
52
+ // agent.js would pull the whole secrets graph into every invocation. Matching
53
+ // string literals were the earlier design; a text-scanning test could not
54
+ // reliably catch their divergence, and the divergence is silent — the CLI says
55
+ // "unknown command", the client reads that as "broker down", and every read
56
+ // falls back to a Touch ID prompt.
57
+ //
58
+ // "Before the startup statements", not "before all code": ESM hoists the
59
+ // imports below, so those module bodies evaluate first. They are side-effect
60
+ // free today (no top-level stdout write or spawn) — which is what makes this
61
+ // safe, and worth preserving.
62
+ if (process.argv[2] === SYNC_GET_CMD ||
63
+ process.argv[2] === SYNC_PING_CMD ||
64
+ process.argv[2] === SYNC_LOCK_CMD) {
65
+ const { runAgentGetSync, runAgentPingSync, runAgentLockSync } = await import('./lib/secrets/agent.js');
66
+ const name = process.argv[3] ?? '';
67
+ const code = process.argv[2] === SYNC_GET_CMD ? await runAgentGetSync(name)
68
+ : process.argv[2] === SYNC_PING_CMD ? await runAgentPingSync()
69
+ : await runAgentLockSync(name);
70
+ process.exit(code);
71
+ }
34
72
  import { NPM_PACKAGE_NAME, deriveGlobalPrefix, detectPackageManager, installPackageIntoPrefix, installPackageWithBun, verifyInstalledVersion, refreshAliasShims, downloadVerifiedTarball, } from './lib/self-update.js';
35
73
  // Detect dev/working-tree builds and default the noisy startup steps off.
36
74
  // Three cases trip this:
@@ -55,9 +93,10 @@ if (IS_DEV_BUILD) {
55
93
  // module on each invocation (which loaded the whole ~50-module tree before the
56
94
  // first byte of output), the registry maps a command name to a thunk that
57
95
  // imports only what that command needs. See src/lib/startup/command-registry.ts.
58
- import { COMMAND_LOADERS, LAZY_COMMAND_NAMES, loadView, loadInspect, loadResources, loadFeedback, loadCommands, loadHooks, loadSkills, loadRules, loadMemory, loadPermissions, loadMcp, loadCli, loadSubagents, loadPlugins, loadWorkflows, loadWorktree, loadVersions, loadImport, loadPackages, loadRoutines, loadMonitors, loadRun, loadFork, loadDefaults, loadModels, loadPrune, loadTrash, loadRestore, loadDoctor, loadApply, loadCheck, loadStatus, loadProfiles, loadSecrets, loadLogin, loadWallet, loadHelper, loadMenubar, loadBeta, loadSync, loadLock, loadRefreshRules, loadDrive, loadFactory, loadUsage, loadCost, loadOutput, loadBudget, loadAlias, loadMine, loadPty, loadTmux, loadWatchdog, loadBrowser, loadComputer, loadHosts, loadLogs, loadEvents, loadAudit, loadWebhook, loadFunnel, loadSsh, loadPull, loadPush, loadRepo, loadSetup, loadUninstall, loadShare, loadSend, loadHq, loadFeed, loadActivity, loadMailboxes, } from './lib/startup/command-registry.js';
96
+ import { COMMAND_LOADERS, LAZY_COMMAND_NAMES, loadView, loadInspect, loadResources, loadFeedback, loadCommands, loadHooks, loadSkills, loadRules, loadMemory, loadPermissions, loadMcp, loadCli, loadSubagents, loadPlugins, loadWorkflows, loadWorktree, loadVersions, loadImport, loadExport, loadPackages, loadRoutines, loadMonitors, loadRun, loadFork, loadDefaults, loadModels, loadPrune, loadTrash, loadRestore, loadDoctor, loadApply, loadCheck, loadStatus, loadProfiles, loadHarness, loadSecrets, loadLogin, loadWallet, loadHelper, loadMenubar, loadBeta, loadSync, loadLock, loadRefreshRules, loadDrive, loadFactory, loadUsage, loadCost, loadOutput, loadBudget, loadAlias, loadMine, loadPty, loadTmux, loadWatchdog, loadBrowser, loadComputer, loadHosts, loadLogs, loadEvents, loadAudit, loadWebhook, loadFunnel, loadSsh, loadPull, loadPush, loadRepo, loadSetup, loadUninstall, loadShare, loadSend, loadHq, loadFeed, loadActivity, loadMailboxes, } from './lib/startup/command-registry.js';
59
97
  import { applyGlobalHelpConventions } from './lib/help.js';
60
98
  import { renderWhatsNew } from './lib/whats-new.js';
99
+ import { getCliLaunch } from './lib/cli-entry.js';
61
100
  import { emit, redactArgs } from './lib/events.js';
62
101
  // Transparent shim delegate: the generated Windows `.cmd` shims invoke
63
102
  // `agents __shim <agent>[@version] <raw args>`. Intercept here, before commander
@@ -294,7 +333,7 @@ async function showWhatsNew(fromVersion, toVersion) {
294
333
  const UPDATE_CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000; // 24 hours
295
334
  import { getUpdateCheckPath, getMigratedSentinelPath, getUserAgentsDir, getRuntimeStateDir } from './lib/state.js';
296
335
  import { resolveBrandName, disabledCommandsForActiveBrand } from './lib/brand.js';
297
- import { readUpdateCache, saveUpdateCheck, dismissUpdateVersion, shouldPromptUpgrade, findAgentsCliInstalls, } from './lib/self-update.js';
336
+ import { readUpdateCache, saveUpdateCheck, dismissUpdateVersion, shouldPromptUpgrade, findAgentsCliInstalls, resolveRunningPackageRoot, } from './lib/self-update.js';
298
337
  const UPDATE_CHECK_FILE = getUpdateCheckPath();
299
338
  /**
300
339
  * Warn once when PATH resolves `agents` to a different agents-cli install
@@ -306,7 +345,16 @@ const UPDATE_CHECK_FILE = getUpdateCheckPath();
306
345
  */
307
346
  function maybeWarnMultiInstall() {
308
347
  const sentinel = path.join(getRuntimeStateDir(), 'multi-install-warned');
309
- const runningRoot = path.resolve(__dirname, '..');
348
+ let runningRoot;
349
+ try {
350
+ runningRoot = resolveRunningPackageRoot(__dirname);
351
+ }
352
+ catch {
353
+ // Without a real root for the running copy there is nothing to compare
354
+ // against, and a guess here is exactly what produced the phantom
355
+ // "/$bunfs" install. This warning is advisory — stay silent instead.
356
+ return;
357
+ }
310
358
  const byRoot = new Map();
311
359
  byRoot.set(runningRoot, { version: VERSION, note: 'running' });
312
360
  for (const install of findAgentsCliInstalls(process.env.PATH || '')) {
@@ -370,7 +418,7 @@ function printResolvedPackage(metadata) {
370
418
  console.log(chalk.gray(`Integrity: ${metadata.integrity}`));
371
419
  }
372
420
  async function installResolvedPackage(metadata) {
373
- const packageRoot = path.resolve(__dirname, '..');
421
+ const packageRoot = resolveRunningPackageRoot(__dirname);
374
422
  // Download the published tarball and prove its bytes match the registry
375
423
  // integrity BEFORE installing anything. A `name@version` spec would let the
376
424
  // package manager fetch and install whatever the registry serves with no
@@ -466,9 +514,11 @@ async function promptUpgrade(latestVersion) {
466
514
  console.log();
467
515
  // Re-exec the verified install's entrypoint and exit. PATH lookup of
468
516
  // `agents` could resolve a different copy (dev build, another prefix)
469
- // than the one that was just upgraded.
470
- const entrypoint = path.resolve(__dirname, '..', 'dist', 'index.js');
471
- const result = spawnSync(process.execPath, [entrypoint, ...process.argv.slice(2)], {
517
+ // than the one that was just upgraded. getCliLaunch resolves the JS-vs-
518
+ // standalone shape never hand-roll `[process.execPath, entrypoint]`,
519
+ // which hands the bun virtual entry to a compiled binary as a bogus arg.
520
+ const { command, args } = getCliLaunch(process.argv.slice(2));
521
+ const result = spawnSync(command, args, {
472
522
  stdio: 'inherit',
473
523
  shell: false,
474
524
  });
@@ -730,6 +780,7 @@ async function registerAllEagerCommands() {
730
780
  await reg(loadWorktree);
731
781
  await reg(loadVersions);
732
782
  await reg(loadImport);
783
+ await reg(loadExport);
733
784
  await reg(loadPackages);
734
785
  await reg(loadRoutines);
735
786
  await reg(loadMonitors);
@@ -746,6 +797,7 @@ async function registerAllEagerCommands() {
746
797
  await reg(loadStatus);
747
798
  registerExecAliasCommand(program);
748
799
  await reg(loadProfiles);
800
+ await reg(loadHarness);
749
801
  await reg(loadSecrets);
750
802
  await reg(loadLogin);
751
803
  await reg(loadWallet);