@phnx-labs/agents-cli 1.22.57 → 1.22.59

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 (152) hide show
  1. package/CHANGELOG.md +294 -0
  2. package/README.md +29 -0
  3. package/dist/bootstrap.js +39 -1
  4. package/dist/commands/accounts.js +7 -3
  5. package/dist/commands/apply.js +10 -2
  6. package/dist/commands/fork.d.ts +23 -10
  7. package/dist/commands/fork.js +115 -58
  8. package/dist/commands/monitors.js +198 -23
  9. package/dist/commands/prune.js +5 -3
  10. package/dist/commands/routines.d.ts +8 -0
  11. package/dist/commands/routines.js +57 -3
  12. package/dist/commands/routines.test-fixture.js +5 -0
  13. package/dist/commands/send.d.ts +2 -1
  14. package/dist/commands/send.js +7 -5
  15. package/dist/commands/sessions-picker.d.ts +11 -0
  16. package/dist/commands/sessions-picker.js +16 -0
  17. package/dist/commands/sessions-stats.js +37 -5
  18. package/dist/commands/sessions.js +40 -5
  19. package/dist/commands/share.d.ts +14 -0
  20. package/dist/commands/share.js +43 -2
  21. package/dist/commands/ssh.js +12 -1
  22. package/dist/commands/status.js +1 -1
  23. package/dist/commands/sync.js +83 -7
  24. package/dist/commands/traces.js +7 -0
  25. package/dist/commands/versions.js +12 -4
  26. package/dist/commands/view.js +7 -2
  27. package/dist/index.d.ts +1 -1
  28. package/dist/index.js +6 -1
  29. package/dist/lib/account-registry.d.ts +5 -1
  30. package/dist/lib/account-registry.js +47 -14
  31. package/dist/lib/accounting/capacity.d.ts +18 -7
  32. package/dist/lib/accounting/capacity.js +19 -8
  33. package/dist/lib/accounting/usage-sync.d.ts +29 -1
  34. package/dist/lib/accounting/usage-sync.js +76 -2
  35. package/dist/lib/accounting/usage.js +7 -1
  36. package/dist/lib/auth-mint.d.ts +11 -1
  37. package/dist/lib/auth-mint.js +21 -6
  38. package/dist/lib/auto-pull-worker.js +7 -2
  39. package/dist/lib/browser/ipc.d.ts +8 -0
  40. package/dist/lib/browser/ipc.js +87 -0
  41. package/dist/lib/browser/service.d.ts +19 -0
  42. package/dist/lib/browser/service.js +96 -11
  43. package/dist/lib/browser/sessions-list.js +10 -1
  44. package/dist/lib/cloud/rush.d.ts +7 -0
  45. package/dist/lib/cloud/rush.js +29 -1
  46. package/dist/lib/daemon/daemon.d.ts +22 -0
  47. package/dist/lib/daemon/daemon.js +39 -0
  48. package/dist/lib/daemon/runner.d.ts +3 -0
  49. package/dist/lib/daemon/runner.js +86 -45
  50. package/dist/lib/daemon/session-index-service.js +9 -1
  51. package/dist/lib/daemon/usage-sync-service.d.ts +3 -3
  52. package/dist/lib/daemon/usage-sync-service.js +14 -8
  53. package/dist/lib/daemon-services.js +1 -1
  54. package/dist/lib/daemon-ticks.d.ts +15 -0
  55. package/dist/lib/daemon-ticks.js +26 -0
  56. package/dist/lib/device-config.d.ts +5 -1
  57. package/dist/lib/device-config.js +2 -2
  58. package/dist/lib/devices/connect.d.ts +17 -8
  59. package/dist/lib/devices/connect.js +31 -14
  60. package/dist/lib/devices/health.js +5 -1
  61. package/dist/lib/devices/pool.d.ts +25 -2
  62. package/dist/lib/devices/pool.js +32 -2
  63. package/dist/lib/devices/stats-cache.d.ts +0 -6
  64. package/dist/lib/devices/stats-cache.js +2 -9
  65. package/dist/lib/doctor-diff.d.ts +14 -0
  66. package/dist/lib/doctor-diff.js +120 -9
  67. package/dist/lib/fleet/manifest.d.ts +17 -0
  68. package/dist/lib/fleet/manifest.js +26 -0
  69. package/dist/lib/git.d.ts +38 -0
  70. package/dist/lib/git.js +58 -0
  71. package/dist/lib/hooks/install.d.ts +27 -11
  72. package/dist/lib/hooks/install.js +42 -17
  73. package/dist/lib/hosts/ready.d.ts +8 -0
  74. package/dist/lib/hosts/ready.js +13 -2
  75. package/dist/lib/hosts/reconnect.d.ts +52 -203
  76. package/dist/lib/hosts/reconnect.js +64 -284
  77. package/dist/lib/installations/migrate.d.ts +6 -120
  78. package/dist/lib/installations/migrate.js +27 -259
  79. package/dist/lib/installations/shims.d.ts +13 -95
  80. package/dist/lib/installations/shims.js +22 -139
  81. package/dist/lib/installations/store.js +1 -1
  82. package/dist/lib/installations/versions.d.ts +43 -133
  83. package/dist/lib/installations/versions.js +94 -206
  84. package/dist/lib/monitors/config.d.ts +71 -3
  85. package/dist/lib/monitors/config.js +100 -12
  86. package/dist/lib/monitors/pid-watch.d.ts +35 -0
  87. package/dist/lib/monitors/pid-watch.js +45 -0
  88. package/dist/lib/monitors/remote.d.ts +18 -0
  89. package/dist/lib/monitors/remote.js +11 -0
  90. package/dist/lib/permissions.js +7 -2
  91. package/dist/lib/plugins/plugins.d.ts +17 -3
  92. package/dist/lib/plugins/plugins.js +84 -9
  93. package/dist/lib/plugins/skills.d.ts +8 -1
  94. package/dist/lib/plugins/skills.js +18 -2
  95. package/dist/lib/pty-server.d.ts +14 -0
  96. package/dist/lib/pty-server.js +49 -5
  97. package/dist/lib/refresh.d.ts +9 -0
  98. package/dist/lib/refresh.js +3 -1
  99. package/dist/lib/routine-readiness.d.ts +15 -1
  100. package/dist/lib/routine-readiness.js +41 -0
  101. package/dist/lib/sandbox.d.ts +4 -1
  102. package/dist/lib/sandbox.js +30 -1
  103. package/dist/lib/secrets/agent.d.ts +80 -225
  104. package/dist/lib/secrets/agent.js +139 -401
  105. package/dist/lib/secrets/bundles.d.ts +73 -222
  106. package/dist/lib/secrets/bundles.js +168 -467
  107. package/dist/lib/secrets/drivers/rush.js +5 -0
  108. package/dist/lib/secrets/reaper.d.ts +28 -70
  109. package/dist/lib/secrets/reaper.js +30 -85
  110. package/dist/lib/secrets/remote.d.ts +42 -129
  111. package/dist/lib/secrets/remote.js +55 -173
  112. package/dist/lib/self-heal/checks/install-staging.d.ts +4 -0
  113. package/dist/lib/self-heal/checks/install-staging.js +96 -0
  114. package/dist/lib/self-heal/registry.js +2 -0
  115. package/dist/lib/self-heal/types.d.ts +1 -1
  116. package/dist/lib/self-update.d.ts +65 -0
  117. package/dist/lib/self-update.js +138 -0
  118. package/dist/lib/session/active.d.ts +13 -1
  119. package/dist/lib/session/active.js +2 -0
  120. package/dist/lib/session/cloud.js +5 -0
  121. package/dist/lib/session/db.d.ts +51 -6
  122. package/dist/lib/session/db.js +266 -20
  123. package/dist/lib/session/fork.d.ts +45 -26
  124. package/dist/lib/session/fork.js +32 -95
  125. package/dist/lib/session/tool-calls.d.ts +43 -1
  126. package/dist/lib/session/tool-calls.js +74 -44
  127. package/dist/lib/session/tool-store.d.ts +33 -2
  128. package/dist/lib/session/tool-store.js +56 -3
  129. package/dist/lib/smart-launch.d.ts +6 -0
  130. package/dist/lib/smart-launch.js +5 -2
  131. package/dist/lib/staleness/writers/plugins.js +5 -2
  132. package/dist/lib/staleness/writers/sources.d.ts +5 -0
  133. package/dist/lib/staleness/writers/sources.js +2 -1
  134. package/dist/lib/staleness/writers/subagents.js +13 -3
  135. package/dist/lib/state.d.ts +7 -4
  136. package/dist/lib/state.js +7 -4
  137. package/dist/lib/subagents.js +8 -2
  138. package/dist/lib/sync-status.d.ts +22 -0
  139. package/dist/lib/sync-status.js +27 -0
  140. package/dist/lib/sync-umbrella.d.ts +9 -0
  141. package/dist/lib/sync-umbrella.js +21 -2
  142. package/dist/lib/teams/scheduler.d.ts +10 -0
  143. package/dist/lib/teams/scheduler.js +8 -0
  144. package/dist/lib/traces/insights.d.ts +47 -14
  145. package/dist/lib/traces/insights.js +92 -21
  146. package/dist/lib/traces/phenotype.d.ts +23 -3
  147. package/dist/lib/traces/phenotype.js +72 -24
  148. package/dist/lib/traces/sync.d.ts +128 -6
  149. package/dist/lib/traces/sync.js +294 -35
  150. package/dist/lib/traces/worker-template.js +154 -1
  151. package/dist/lib/view-types.d.ts +12 -0
  152. package/package.json +2 -2
@@ -1,128 +1,14 @@
1
- /**
2
- * One-shot idempotent migrations for the FOUNDATION refactor.
3
- *
4
- * Called from postinstall and as a command-time fallback from agents view/use/pull.
5
- * Each migration is guarded by an existence check so re-running is safe.
6
- */
1
+ /** One-shot idempotent migrations. Each is guarded by an existence check so re-running is safe. */
7
2
  export { foldLegacySystemRepo } from '../migrate-fold.js';
8
- /**
9
- * Migrate a legacy single-repo agents.yaml into ~/.agents/agents.yaml.
10
- *
11
- * The post-fold system dir (~/.agents/.system) is a pull-only mirror of the
12
- * npm-shipped defaults. Its agents.yaml is a TRACKED file that readMeta() reads
13
- * in place as the defaults base (see SYSTEM_META_FILE in state.ts). Deleting or
14
- * moving a tracked file out of the mirror dirties its working tree, which
15
- * permanently wedges `agents setup`, `agents sync`, and background auto-pull
16
- * ("Working tree has uncommitted changes.") because the mirror sync refuses a
17
- * dirty tree. So when agents.yaml is tracked there, treat the mirror as strictly
18
- * read-only and leave the file alone.
19
- *
20
- * Only an UNTRACKED agents.yaml is residue from the pre-split single-repo layout
21
- * (or a legacy ~/.agents-system fold) and is safe to move/drop.
22
- *
23
- * Params default to the real on-disk locations; they are injectable so tests can
24
- * drive a fixture tree without touching the user's ~/.agents.
25
- */
3
+ /** Migrate a legacy single-repo agents.yaml into ~/.agents/agents.yaml. Leaves the tracked system-mirror copy alone to avoid dirtying the npm-shipped defaults. */
26
4
  export declare function migrateAgentsYaml(systemDir?: string, userDir?: string): void;
27
- /**
28
- * Fold the legacy GLOBAL browser captures root
29
- * (`~/.agents/.cache/browser/sessions/<task>/`) into the new PER-PROFILE layout
30
- * (`~/.agents/.cache/browser/<profile>/sessions/<task>/`), so each profile is one
31
- * self-contained tree and the new `browser sessions` listing finds old captures.
32
- *
33
- * Attribution: a legacy `sessions/<task>` dir is owned by whichever profile's
34
- * `tasks.json` still lists that task name. Tasks no profile claims (the profile
35
- * was deleted, or tasks.json was cleared) move under a `_legacy` pseudo-profile so
36
- * nothing is lost. Idempotent — once the global `sessions/` root is gone it no-ops.
37
- */
5
+ /** Fold the legacy global browser/sessions/<task>/ tree into the per-profile browser/<profile>/sessions/<task>/ layout. Unclaimed tasks move under `_legacy`. */
38
6
  export declare function foldBrowserSessionsIntoProfiles(browserDir?: string): void;
39
- /**
40
- * Repair self-referential agent binary symlinks.
41
- *
42
- * Some installScript-based agents — notably Factory.ai's `droid`, whose installer
43
- * drops a standalone native binary at ~/.local/bin/droid — were registered at
44
- * install time by resolving the post-install binary with `which <cli>`. Because
45
- * ~/.agents/.cache/shims sits ahead of ~/.local/bin on PATH, `which` could
46
- * return OUR OWN dispatcher shim, and the install step symlinked
47
- * ~/.agents/.history/versions/<agent>/<version>/node_modules/.bin/<cli>
48
- * back at ~/.agents/.cache/shims/<cli>. Launching that agent then re-execs the
49
- * dispatcher forever (an infinite exec loop that hangs the terminal).
50
- *
51
- * This walks every installed version's node_modules/.bin and, for any entry
52
- * whose symlink resolves into the shims dir, re-points it at the real binary
53
- * (found on PATH with the shims dir excluded) — or removes it when no real
54
- * binary can be found, letting getBinaryPath's per-agent resolver take over.
55
- * Idempotent: a correctly-pointed link is left untouched on re-run.
56
- *
57
- * Params default to the real on-disk locations; they are injectable so tests
58
- * can drive a fixture tree without touching the user's ~/.agents.
59
- */
7
+ /** Repair node_modules/.bin/<cli> symlinks that resolve back into our own shims dir (infinite exec-loop fix). */
60
8
  export declare function repairSelfReferentialBinShims(versionsRoot?: string, shimsDir?: string, historyDir?: string): void;
61
- /**
62
- * Move the auto-detected `default` browser profile OUT of the committed central
63
- * agents.yaml and into this machine's per-device file.
64
- *
65
- * `browser` is a CENTRAL key because named profiles a user creates are real fleet
66
- * config, but the ONE `default` entry inside it is machine-local: its `binary` is
67
- * an OS-specific path and its endpoint is a locally-chosen free port.
68
- * `createProfile`/`updateProfile` already route that entry to `deviceBrowser`
69
- * (`isMachineLocalProfile`, browser/profiles.ts) — but nothing ever removed the
70
- * copy older versions had already written into the shared file, and
71
- * `serializeCentral` cannot: it deletes whole KEYS that are device-scoped, and
72
- * `browser` is not one.
73
- *
74
- * So the entry sat in the committed file and every box rewrote it with its own
75
- * browser. Measured 2026-08-13, all three boxes on 1.22.38 (which HAS the writer
76
- * fix) with an empty `deviceBrowser` and a machine-specific `default` in central:
77
- *
78
- * zion browser: chrome binary: /Applications/Google Chrome.app/...
79
- * yosemite-s1 browser: brave binary: /opt/brave.com/brave/brave
80
- * mark-1 (same shape)
81
- *
82
- * `agents repos pull user` refuses when an incoming change touches a locally
83
- * modified path, so agents.yaml being permanently dirty wedged the fleet config
84
- * sync outright — those boxes sat 5, 8 and 79 commits behind. (RUSH-2161)
85
- *
86
- * Idempotent: no-op once central carries no `default` entry. The device file is
87
- * written FIRST so a crash between the two writes can never lose the profile,
88
- * and an entry already in the device file wins (this machine's live value is
89
- * newer than the stale central copy by construction).
90
- *
91
- * Central is edited through a `yaml.Document` rather than re-stringified, so the
92
- * hand-written comments in the committed agents.yaml survive — a plain
93
- * `yaml.stringify` would drop every one of them and rewrite the whole file,
94
- * which is the same churn this migration exists to stop (see `serializeCentral`).
95
- */
9
+ /** Move the auto-detected `default` browser profile from committed agents.yaml into the per-device file. The device file is written first so a crash cannot lose the profile; central is edited via yaml.Document to preserve comments. */
96
10
  export declare function migrateMachineLocalBrowserProfileOutOfCentral(userDir?: string, machine?: string): void;
97
- /**
98
- * Rename the legacy `extras-extras/` plugin-marketplace dir to `agents-extras/`
99
- * inside every installed agent version-home, and rewrite cross-references in
100
- * `known_marketplaces.json` and the agent's `settings.json`.
101
- *
102
- * A previous dev build of `agents-cli` named the extras-aliased repo's
103
- * synthesized marketplace dir `extras-extras` (double "extras" because the alias
104
- * itself was `extras`). The new naming convention is `agents-<alias>`, so the
105
- * directory should be `agents-extras`. Without this migration the orphan dir
106
- * stays on disk and Claude Code loads two parallel marketplaces (the legacy
107
- * `extras-extras` entry from `known_marketplaces.json` plus the freshly
108
- * synthesized `agents-extras` from the new code path).
109
- *
110
- * Strategy per `<historyDir>/versions/<agent>/<ver>/home/.<agent>/plugins/`:
111
- * 1. `marketplaces/extras-extras/` → `marketplaces/agents-extras/`
112
- * (drops `extras-extras/` outright when `agents-extras/` already exists —
113
- * previous incomplete migration ran)
114
- * 2. Inside the renamed dir's `.claude-plugin/marketplace.json`, set
115
- * `"name": "agents-extras"`.
116
- * 3. In `<configDir>/plugins/known_marketplaces.json`, rename the
117
- * `extras-extras` key to `agents-extras` and rewrite `source.path` /
118
- * `installLocation`.
119
- * 4. In `<configDir>/settings.json`'s `enabledPlugins`, rename every
120
- * `<plugin>@extras-extras` key to `<plugin>@agents-extras` (preserving
121
- * its boolean value, skipping if the new key already exists).
122
- *
123
- * Idempotent: re-running converges without further writes once everything is on
124
- * the new name.
125
- */
11
+ /** Rename the legacy `extras-extras/` plugin marketplace dir to `agents-extras/` in every installed version home, and rewrite cross-references in `known_marketplaces.json` and `settings.json`. */
126
12
  export declare function migrateExtrasExtrasToAgentsExtras(historyDir?: string): void;
127
13
  /**
128
14
  * Rewrite every routine YAML that carries the legacy singular `device: <value>`
@@ -1,9 +1,4 @@
1
- /**
2
- * One-shot idempotent migrations for the FOUNDATION refactor.
3
- *
4
- * Called from postinstall and as a command-time fallback from agents view/use/pull.
5
- * Each migration is guarded by an existence check so re-running is safe.
6
- */
1
+ /** One-shot idempotent migrations. Each is guarded by an existence check so re-running is safe. */
7
2
  import * as fs from 'fs';
8
3
  import * as path from 'path';
9
4
  import * as os from 'os';
@@ -32,7 +27,6 @@ const USER_DIR = path.join(HOME, '.agents');
32
27
  const SYSTEM_DIR = path.join(USER_DIR, '.system');
33
28
  const HISTORY_DIR = path.join(USER_DIR, '.history');
34
29
  const CACHE_DIR = path.join(USER_DIR, '.cache');
35
- /** True when `relPath` is a file tracked by a git repo rooted at `repoDir`. */
36
30
  function isTrackedInGitRepo(repoDir, relPath) {
37
31
  try {
38
32
  execSync(`git ls-files --error-unmatch -- ${relPath}`, { cwd: repoDir, stdio: 'ignore' });
@@ -43,31 +37,13 @@ function isTrackedInGitRepo(repoDir, relPath) {
43
37
  return false;
44
38
  }
45
39
  }
46
- /**
47
- * Migrate a legacy single-repo agents.yaml into ~/.agents/agents.yaml.
48
- *
49
- * The post-fold system dir (~/.agents/.system) is a pull-only mirror of the
50
- * npm-shipped defaults. Its agents.yaml is a TRACKED file that readMeta() reads
51
- * in place as the defaults base (see SYSTEM_META_FILE in state.ts). Deleting or
52
- * moving a tracked file out of the mirror dirties its working tree, which
53
- * permanently wedges `agents setup`, `agents sync`, and background auto-pull
54
- * ("Working tree has uncommitted changes.") because the mirror sync refuses a
55
- * dirty tree. So when agents.yaml is tracked there, treat the mirror as strictly
56
- * read-only and leave the file alone.
57
- *
58
- * Only an UNTRACKED agents.yaml is residue from the pre-split single-repo layout
59
- * (or a legacy ~/.agents-system fold) and is safe to move/drop.
60
- *
61
- * Params default to the real on-disk locations; they are injectable so tests can
62
- * drive a fixture tree without touching the user's ~/.agents.
63
- */
40
+ /** Migrate a legacy single-repo agents.yaml into ~/.agents/agents.yaml. Leaves the tracked system-mirror copy alone to avoid dirtying the npm-shipped defaults. */
64
41
  export function migrateAgentsYaml(systemDir = SYSTEM_DIR, userDir = USER_DIR) {
65
42
  const src = path.join(systemDir, 'agents.yaml');
66
43
  const dest = path.join(userDir, 'agents.yaml');
67
44
  if (!fs.existsSync(src))
68
45
  return;
69
- // Tracked in the system mirror → npm-shipped defaults; never mutate. readMeta()
70
- // already surfaces it in place, so no user-dir copy is needed either.
46
+ // Never move the tracked system-mirror copy; doing so would dirty the npm-shipped defaults.
71
47
  if (isTrackedInGitRepo(systemDir, 'agents.yaml'))
72
48
  return;
73
49
  if (fs.existsSync(dest)) {
@@ -85,9 +61,6 @@ export function migrateAgentsYaml(systemDir = SYSTEM_DIR, userDir = USER_DIR) {
85
61
  }
86
62
  catch { /* best-effort */ }
87
63
  }
88
- /**
89
- * Delete ~/.agents-system/prompts.json (dead file — zero refs in src/).
90
- */
91
64
  function deleteSystemPromptsJson() {
92
65
  const f = path.join(SYSTEM_DIR, 'prompts.json');
93
66
  if (!fs.existsSync(f))
@@ -97,14 +70,7 @@ function deleteSystemPromptsJson() {
97
70
  }
98
71
  catch { /* best-effort */ }
99
72
  }
100
- /**
101
- * Move ~/.agents-system/config.json -> ~/.agents/teams/config.json.
102
- * The teams persistence layer already reads the legacy path as a fallback;
103
- * moving it here keeps the canonical location consistent.
104
- */
105
- // Delete the legacy ~/.agents-system/config.json. This was the teams agent
106
- // registry, which no longer exists — `agents teams` discovers agents through
107
- // `listInstalledVersions` and invokes them through `agents run`.
73
+ /** Delete the legacy ~/.agents-system/config.json (dead teams agent registry). */
108
74
  function migrateSystemConfigJson() {
109
75
  const src = path.join(SYSTEM_DIR, 'config.json');
110
76
  if (!fs.existsSync(src))
@@ -114,12 +80,7 @@ function migrateSystemConfigJson() {
114
80
  }
115
81
  catch { /* best-effort */ }
116
82
  }
117
- /**
118
- * Move promptcuts.yaml from each repo root into hooks/ subdir.
119
- * ~/.agents-system/promptcuts.yaml -> ~/.agents-system/hooks/promptcuts.yaml
120
- * ~/.agents/promptcuts.yaml -> ~/.agents/hooks/promptcuts.yaml
121
- * Idempotent: skips if dest already exists or src absent.
122
- */
83
+ /** Move promptcuts.yaml from each repo root into hooks/. */
123
84
  function migratePromptcutsIntoHooks() {
124
85
  for (const root of [SYSTEM_DIR, USER_DIR]) {
125
86
  const src = path.join(root, 'promptcuts.yaml');
@@ -133,23 +94,7 @@ function migratePromptcutsIntoHooks() {
133
94
  catch { /* best-effort */ }
134
95
  }
135
96
  }
136
- /**
137
- * Move installed agent versions from the legacy system-root layout
138
- * (~/.agents-system/versions/<agent>/<ver>/) into the user root
139
- * (~/.agents/versions/<agent>/<ver>/).
140
- *
141
- * Earlier installs (and an inverted prior version of this migrator) put
142
- * binaries and home dirs under ~/.agents-system/. The current architecture
143
- * keeps all operational state (versions, sessions, shims, trash) under
144
- * ~/.agents/, and getVersionsDir() in state.ts resolves there. Without this
145
- * migration the legacy versions become invisible to listInstalledVersions
146
- * and every command that depends on it (view, prune, run, sync) writes a
147
- * second copy to ~/.agents/versions/ while the agent CLIs keep reading the
148
- * stale ~/.agents-system/versions/ copy via the existing ~/.<agent> symlink.
149
- *
150
- * Idempotent and non-destructive: if a same-named dest already exists we
151
- * leave the legacy copy in place so the user can reconcile manually.
152
- */
97
+ /** Move installed versions from the legacy ~/.agents-system/versions/ tree into ~/.agents/versions/. Leaves collisions in place for manual reconciliation. */
153
98
  function migrateSystemVersionsToUser() {
154
99
  const sysVersions = path.join(SYSTEM_DIR, 'versions');
155
100
  const userVersions = path.join(USER_DIR, 'versions');
@@ -213,10 +158,7 @@ function migrateSystemVersionsToUser() {
213
158
  console.error(`Skipped ${skippedCount} version dir${skippedCount === 1 ? '' : 's'} already present in ~/.agents/versions/ (kept legacy copy at ~/.agents-system/versions/)`);
214
159
  }
215
160
  }
216
- /**
217
- * Move ~/. agents/runs/ -> ~/.agents/routines/runs/.
218
- * Runs now live inside routines directory for cleaner organization.
219
- */
161
+ /** Move ~/.agents/runs/ into ~/.agents/routines/runs/. */
220
162
  function migrateRunsIntoRoutines() {
221
163
  const src = path.join(USER_DIR, 'runs');
222
164
  const dest = path.join(USER_DIR, 'routines', 'runs');
@@ -228,10 +170,7 @@ function migrateRunsIntoRoutines() {
228
170
  }
229
171
  catch { /* best-effort */ }
230
172
  }
231
- /**
232
- * Move ~/.agents/trash/ -> ~/.agents/.trash/.
233
- * Hide the trash directory.
234
- */
173
+ /** Move ~/.agents/trash/ to ~/.agents/.trash/. */
235
174
  function migrateTrashToHidden() {
236
175
  const src = path.join(USER_DIR, 'trash');
237
176
  const dest = path.join(USER_DIR, '.trash');
@@ -242,10 +181,7 @@ function migrateTrashToHidden() {
242
181
  }
243
182
  catch { /* best-effort */ }
244
183
  }
245
- /**
246
- * Move ~/.agents/backups/ -> ~/.agents/.backups/.
247
- * Hide the backups directory.
248
- */
184
+ /** Move ~/.agents/backups/ to ~/.agents/.backups/. */
249
185
  function migrateBackupsToHidden() {
250
186
  const src = path.join(USER_DIR, 'backups');
251
187
  const dest = path.join(USER_DIR, '.backups');
@@ -256,17 +192,7 @@ function migrateBackupsToHidden() {
256
192
  }
257
193
  catch { /* best-effort */ }
258
194
  }
259
- /**
260
- * Fold ~/.agents/hooks.yaml into ~/.agents/agents.yaml under a `hooks:` key,
261
- * then delete the standalone hooks.yaml. Single user file to sync.
262
- *
263
- * On collision (a hook name already exists in agents.yaml hooks:), the
264
- * existing agents.yaml entry wins and the standalone copy is dropped — this
265
- * matches the behavior a user would get if they had already migrated
266
- * manually and edited agents.yaml.
267
- *
268
- * Idempotent: skips if hooks.yaml is absent or unparseable.
269
- */
195
+ /** Fold ~/.agents/hooks.yaml into ~/.agents/agents.yaml under `hooks:`, dropping the standalone file. agents.yaml wins on collision. */
270
196
  function foldUserHooksYamlIntoAgentsYaml() {
271
197
  const hooksFile = path.join(USER_DIR, 'hooks.yaml');
272
198
  if (!fs.existsSync(hooksFile))
@@ -309,17 +235,7 @@ function foldUserHooksYamlIntoAgentsYaml() {
309
235
  }
310
236
  catch { /* best-effort */ }
311
237
  }
312
- /**
313
- * Fold the legacy GLOBAL browser captures root
314
- * (`~/.agents/.cache/browser/sessions/<task>/`) into the new PER-PROFILE layout
315
- * (`~/.agents/.cache/browser/<profile>/sessions/<task>/`), so each profile is one
316
- * self-contained tree and the new `browser sessions` listing finds old captures.
317
- *
318
- * Attribution: a legacy `sessions/<task>` dir is owned by whichever profile's
319
- * `tasks.json` still lists that task name. Tasks no profile claims (the profile
320
- * was deleted, or tasks.json was cleared) move under a `_legacy` pseudo-profile so
321
- * nothing is lost. Idempotent — once the global `sessions/` root is gone it no-ops.
322
- */
238
+ /** Fold the legacy global browser/sessions/<task>/ tree into the per-profile browser/<profile>/sessions/<task>/ layout. Unclaimed tasks move under `_legacy`. */
323
239
  export function foldBrowserSessionsIntoProfiles(browserDir = path.join(CACHE_DIR, 'browser')) {
324
240
  const legacySessionsDir = path.join(browserDir, 'sessions');
325
241
  let taskDirs;
@@ -329,7 +245,6 @@ export function foldBrowserSessionsIntoProfiles(browserDir = path.join(CACHE_DIR
329
245
  catch {
330
246
  return; // no legacy global sessions/ root — already folded or never existed
331
247
  }
332
- // Map task name -> owning profile from every profile's tasks.json (first wins).
333
248
  const taskOwner = new Map();
334
249
  let profileDirs = [];
335
250
  try {
@@ -359,15 +274,7 @@ export function foldBrowserSessionsIntoProfiles(browserDir = path.join(CACHE_DIR
359
274
  }
360
275
  rmEmptyDirTree(legacySessionsDir);
361
276
  }
362
- /**
363
- * Fold ~/.agents/browser/profiles/*.yaml into ~/.agents/agents.yaml under a
364
- * `browser:` key, then delete the profiles directory. Single user file to sync.
365
- *
366
- * On collision (a profile name already exists in agents.yaml browser:), the
367
- * existing agents.yaml entry wins and the standalone copy is dropped.
368
- *
369
- * Idempotent: skips if profiles dir is absent or empty.
370
- */
277
+ /** Fold ~/.agents/browser/profiles/*.yaml into ~/.agents/agents.yaml under `browser:`. agents.yaml wins on collision. */
371
278
  function foldBrowserProfilesIntoAgentsYaml() {
372
279
  const profilesDir = path.join(USER_DIR, 'browser', 'profiles');
373
280
  if (!fs.existsSync(profilesDir))
@@ -441,10 +348,7 @@ function foldBrowserProfilesIntoAgentsYaml() {
441
348
  }
442
349
  catch { /* best-effort */ }
443
350
  }
444
- /**
445
- * Delete ~/.agents/linear.json. The linear-cli now manages its own
446
- * credentials in the OS keychain; this file was a legacy plaintext store.
447
- */
351
+ /** Delete the legacy ~/.agents/linear.json plaintext credential store. */
448
352
  function deleteUserLinearJson() {
449
353
  const f = path.join(USER_DIR, 'linear.json');
450
354
  if (!fs.existsSync(f))
@@ -454,11 +358,7 @@ function deleteUserLinearJson() {
454
358
  }
455
359
  catch { /* best-effort */ }
456
360
  }
457
- /**
458
- * Delete ~/.agents/prompts.json. Dead file with zero refs in src/ (the
459
- * system-repo copy was cleared by deleteSystemPromptsJson; this is the
460
- * matching cleanup at the user layer).
461
- */
361
+ /** Delete the dead ~/.agents/prompts.json file. */
462
362
  function deleteUserPromptsJson() {
463
363
  const f = path.join(USER_DIR, 'prompts.json');
464
364
  if (!fs.existsSync(f))
@@ -468,12 +368,7 @@ function deleteUserPromptsJson() {
468
368
  }
469
369
  catch { /* best-effort */ }
470
370
  }
471
- /**
472
- * Delete ~/.agents/teams/config.json. The teams subsystem no longer carries
473
- * its own agent registry — agent discovery flows through `listInstalledVersions`
474
- * (the same source `agents view` uses) and invocation flows through
475
- * `agents run`. The on-disk file is pure dead state on existing installs.
476
- */
371
+ /** Delete the dead ~/.agents/teams/config.json file. */
477
372
  function deleteTeamsConfigJson() {
478
373
  const f = path.join(USER_DIR, 'teams', 'config.json');
479
374
  if (!fs.existsSync(f))
@@ -483,22 +378,13 @@ function deleteTeamsConfigJson() {
483
378
  }
484
379
  catch { /* best-effort */ }
485
380
  }
486
- /**
487
- * Move ~/.agents/teams/registry.json → ~/.agents/.history/teams/registry.json.
488
- * The registry is per-machine runtime state (timestamps + absolute worktree
489
- * paths) and belongs in the durable-runtime bucket, not at the user-root
490
- * where `agents repo push` would sync it across machines.
491
- */
381
+ /** Move ~/.agents/teams/registry.json (per-machine runtime state) into ~/.agents/.history/teams/. */
492
382
  function moveTeamsRegistryToHistory() {
493
383
  const src = path.join(USER_DIR, 'teams', 'registry.json');
494
384
  const dest = path.join(HISTORY_DIR, 'teams', 'registry.json');
495
385
  moveFileOnce(src, dest);
496
386
  }
497
- /**
498
- * Delete ~/.agents/config.json. This was the legacy teams config location;
499
- * the teams subsystem no longer carries a config file at all, so the legacy
500
- * copy is simply removed.
501
- */
387
+ /** Delete the legacy ~/.agents/config.json teams config file. */
502
388
  function cleanupUserConfigJson() {
503
389
  const legacy = path.join(USER_DIR, 'config.json');
504
390
  if (!fs.existsSync(legacy))
@@ -508,11 +394,7 @@ function cleanupUserConfigJson() {
508
394
  }
509
395
  catch { /* best-effort */ }
510
396
  }
511
- /**
512
- * Remove an empty ~/.agents/runs/ directory left over after the
513
- * migrateRunsIntoRoutines() rename. Some older code paths re-created the
514
- * empty parent; this trims it once it has no contents.
515
- */
397
+ /** Remove an empty ~/.agents/runs/ directory left over after migrateRunsIntoRoutines(). */
516
398
  function cleanupEmptyTopLevelRuns() {
517
399
  const dir = path.join(USER_DIR, 'runs');
518
400
  if (!fs.existsSync(dir))
@@ -523,11 +405,7 @@ function cleanupEmptyTopLevelRuns() {
523
405
  }
524
406
  catch { /* best-effort */ }
525
407
  }
526
- /**
527
- * Move ~/.agents-system/aliases.json -> ~/.agents/aliases.json.
528
- * Aliases are per-user state and were previously written to the system root
529
- * by mistake. Idempotent: skips if dest already exists or src absent.
530
- */
408
+ /** Move ~/.agents-system/aliases.json into ~/.agents/aliases.json. */
531
409
  function migrateAliasesToUser() {
532
410
  const src = path.join(SYSTEM_DIR, 'aliases.json');
533
411
  const dest = path.join(USER_DIR, 'aliases.json');
@@ -615,11 +493,7 @@ function mergeOverlappingVersionHomes() {
615
493
  console.error(`Merged ${mergedCount} overlapping version home${mergedCount === 1 ? '' : 's'} from legacy ~/.agents-system/versions/ into ~/.agents/versions/ (legacy moved to ~/.agents/.trash/versions/)`);
616
494
  }
617
495
  }
618
- /**
619
- * Rename ~/.agents/permissions/sets/ -> ~/.agents/permissions/presets/.
620
- * Also handles ~/.agents-system/permissions/sets/ for system repo.
621
- * Idempotent: skips if dest already exists or src absent.
622
- */
496
+ /** Rename permissions/sets/ to permissions/presets/ in both user and system repos. */
623
497
  function migratePermissionSetsToPresets() {
624
498
  for (const root of [USER_DIR, SYSTEM_DIR]) {
625
499
  const src = path.join(root, 'permissions', 'sets');
@@ -634,16 +508,7 @@ function migratePermissionSetsToPresets() {
634
508
  catch { /* best-effort */ }
635
509
  }
636
510
  }
637
- /**
638
- * After versions are migrated to ~/.agents/versions/, rewrite the per-agent
639
- * config symlinks (~/.claude, ~/.codex, …) to point at the user-side
640
- * version-home so the agent CLIs read fresh resources.
641
- *
642
- * Idempotent: if the symlink already points at the right user-path target,
643
- * leave it. If it points at the legacy system path, re-create it. If a real
644
- * directory exists there (no symlink yet), leave it alone — version-config
645
- * switching is owned by `agents use`, not the migrator.
646
- */
511
+ /** Rewrite per-agent config symlinks to point at the user-side version home after migration. Leaves real directories alone (switching is owned by `agents use`). */
647
512
  function repairAgentConfigSymlinks() {
648
513
  // Version pins live in the machine-local pins JSON (~/.agents/.history/
649
514
  // devices/pins-<machine>.json); the tracked device doc and central
@@ -734,32 +599,9 @@ function repairAgentConfigSymlinks() {
734
599
  console.error(`Repaired ${repaired} agent config symlink${repaired === 1 ? '' : 's'} to point at ~/.agents/versions/`);
735
600
  }
736
601
  }
737
- /**
738
- * Repair self-referential agent binary symlinks.
739
- *
740
- * Some installScript-based agents — notably Factory.ai's `droid`, whose installer
741
- * drops a standalone native binary at ~/.local/bin/droid — were registered at
742
- * install time by resolving the post-install binary with `which <cli>`. Because
743
- * ~/.agents/.cache/shims sits ahead of ~/.local/bin on PATH, `which` could
744
- * return OUR OWN dispatcher shim, and the install step symlinked
745
- * ~/.agents/.history/versions/<agent>/<version>/node_modules/.bin/<cli>
746
- * back at ~/.agents/.cache/shims/<cli>. Launching that agent then re-execs the
747
- * dispatcher forever (an infinite exec loop that hangs the terminal).
748
- *
749
- * This walks every installed version's node_modules/.bin and, for any entry
750
- * whose symlink resolves into the shims dir, re-points it at the real binary
751
- * (found on PATH with the shims dir excluded) — or removes it when no real
752
- * binary can be found, letting getBinaryPath's per-agent resolver take over.
753
- * Idempotent: a correctly-pointed link is left untouched on re-run.
754
- *
755
- * Params default to the real on-disk locations; they are injectable so tests
756
- * can drive a fixture tree without touching the user's ~/.agents.
757
- */
602
+ /** Repair node_modules/.bin/<cli> symlinks that resolve back into our own shims dir (infinite exec-loop fix). */
758
603
  export function repairSelfReferentialBinShims(versionsRoot = path.join(HISTORY_DIR, 'versions'), shimsDir = path.resolve(CACHE_DIR, 'shims'), historyDir = path.dirname(versionsRoot)) {
759
- // Normalize the shims dir through realpath so the prefix check below survives
760
- // a symlinked ~/.agents (or macOS's /tmp -> /private/tmp): fs.realpathSync on
761
- // the link target resolves those symlinks, so the dir we compare against must
762
- // too, or every loop would read as "points at a real binary" and be skipped.
604
+ // realpath the shims dir so the prefix check survives symlinked paths (e.g. macOS /tmp -> /private/tmp).
763
605
  shimsDir = path.resolve(shimsDir);
764
606
  try {
765
607
  shimsDir = fs.realpathSync(shimsDir);
@@ -831,14 +673,7 @@ export function repairSelfReferentialBinShims(versionsRoot = path.join(HISTORY_D
831
673
  console.error(`Repaired ${repaired} self-referential agent binary symlink${repaired === 1 ? '' : 's'} (infinite exec-loop fix).`);
832
674
  }
833
675
  }
834
- /**
835
- * Move a directory from `src` to `dest`. No-op when src is absent. When dest
836
- * already exists, merge by copying everything that isn't already there, then
837
- * remove the source. Idempotent: re-running converges without duplicating.
838
- *
839
- * The merge-on-collision behavior matters because `ensureAgentsDir()` may have
840
- * pre-created an empty `dest` during startup before the migrator gets to run.
841
- */
676
+ /** Move `src` to `dest`; when `dest` exists, merge missing entries then remove `src`. Idempotent. */
842
677
  function moveDirOnce(src, dest) {
843
678
  if (!fs.existsSync(src))
844
679
  return;
@@ -858,11 +693,7 @@ function moveDirOnce(src, dest) {
858
693
  }
859
694
  catch { /* best-effort */ }
860
695
  }
861
- /**
862
- * Move a single file from `src` to `dest`. No-op when src is absent. When
863
- * dest exists, the source is simply deleted (the in-place version is treated
864
- * as the canonical state). Idempotent.
865
- */
696
+ /** Move `src` to `dest`; when `dest` exists, delete `src` (dest is canonical). Idempotent. */
866
697
  function moveFileOnce(src, dest) {
867
698
  if (!fs.existsSync(src))
868
699
  return;
@@ -885,7 +716,6 @@ function moveFileOnce(src, dest) {
885
716
  catch { /* best-effort */ }
886
717
  }
887
718
  }
888
- /** Remove a directory tree if it exists and contains no files (best-effort). */
889
719
  function rmEmptyDirTree(dir) {
890
720
  if (!fs.existsSync(dir))
891
721
  return;
@@ -1711,41 +1541,7 @@ function migrateSplitDeviceLocalMeta() {
1711
1541
  console.error('Split agents.yaml: agents: -> .history/devices/pins-*.json, versions: -> .history/version-resources.json');
1712
1542
  }
1713
1543
  }
1714
- /**
1715
- * Move the auto-detected `default` browser profile OUT of the committed central
1716
- * agents.yaml and into this machine's per-device file.
1717
- *
1718
- * `browser` is a CENTRAL key because named profiles a user creates are real fleet
1719
- * config, but the ONE `default` entry inside it is machine-local: its `binary` is
1720
- * an OS-specific path and its endpoint is a locally-chosen free port.
1721
- * `createProfile`/`updateProfile` already route that entry to `deviceBrowser`
1722
- * (`isMachineLocalProfile`, browser/profiles.ts) — but nothing ever removed the
1723
- * copy older versions had already written into the shared file, and
1724
- * `serializeCentral` cannot: it deletes whole KEYS that are device-scoped, and
1725
- * `browser` is not one.
1726
- *
1727
- * So the entry sat in the committed file and every box rewrote it with its own
1728
- * browser. Measured 2026-08-13, all three boxes on 1.22.38 (which HAS the writer
1729
- * fix) with an empty `deviceBrowser` and a machine-specific `default` in central:
1730
- *
1731
- * zion browser: chrome binary: /Applications/Google Chrome.app/...
1732
- * yosemite-s1 browser: brave binary: /opt/brave.com/brave/brave
1733
- * mark-1 (same shape)
1734
- *
1735
- * `agents repos pull user` refuses when an incoming change touches a locally
1736
- * modified path, so agents.yaml being permanently dirty wedged the fleet config
1737
- * sync outright — those boxes sat 5, 8 and 79 commits behind. (RUSH-2161)
1738
- *
1739
- * Idempotent: no-op once central carries no `default` entry. The device file is
1740
- * written FIRST so a crash between the two writes can never lose the profile,
1741
- * and an entry already in the device file wins (this machine's live value is
1742
- * newer than the stale central copy by construction).
1743
- *
1744
- * Central is edited through a `yaml.Document` rather than re-stringified, so the
1745
- * hand-written comments in the committed agents.yaml survive — a plain
1746
- * `yaml.stringify` would drop every one of them and rewrite the whole file,
1747
- * which is the same churn this migration exists to stop (see `serializeCentral`).
1748
- */
1544
+ /** Move the auto-detected `default` browser profile from committed agents.yaml into the per-device file. The device file is written first so a crash cannot lose the profile; central is edited via yaml.Document to preserve comments. */
1749
1545
  export function migrateMachineLocalBrowserProfileOutOfCentral(userDir = USER_DIR, machine = machineId()) {
1750
1546
  const metaFile = path.join(userDir, 'agents.yaml');
1751
1547
  if (!fs.existsSync(metaFile))
@@ -1808,35 +1604,7 @@ export function migrateMachineLocalBrowserProfileOutOfCentral(userDir = USER_DIR
1808
1604
  atomicWriteFileSync(metaFile, remaining === 0 ? DEVICE_META_HEADER : stringifyDoc(doc));
1809
1605
  console.error(`Migrated agents.yaml: browser '${LEGACY_DEFAULT_BROWSER_PROFILE_NAME}' profile -> devices/${machine}/agents.yaml`);
1810
1606
  }
1811
- /**
1812
- * Rename the legacy `extras-extras/` plugin-marketplace dir to `agents-extras/`
1813
- * inside every installed agent version-home, and rewrite cross-references in
1814
- * `known_marketplaces.json` and the agent's `settings.json`.
1815
- *
1816
- * A previous dev build of `agents-cli` named the extras-aliased repo's
1817
- * synthesized marketplace dir `extras-extras` (double "extras" because the alias
1818
- * itself was `extras`). The new naming convention is `agents-<alias>`, so the
1819
- * directory should be `agents-extras`. Without this migration the orphan dir
1820
- * stays on disk and Claude Code loads two parallel marketplaces (the legacy
1821
- * `extras-extras` entry from `known_marketplaces.json` plus the freshly
1822
- * synthesized `agents-extras` from the new code path).
1823
- *
1824
- * Strategy per `<historyDir>/versions/<agent>/<ver>/home/.<agent>/plugins/`:
1825
- * 1. `marketplaces/extras-extras/` → `marketplaces/agents-extras/`
1826
- * (drops `extras-extras/` outright when `agents-extras/` already exists —
1827
- * previous incomplete migration ran)
1828
- * 2. Inside the renamed dir's `.claude-plugin/marketplace.json`, set
1829
- * `"name": "agents-extras"`.
1830
- * 3. In `<configDir>/plugins/known_marketplaces.json`, rename the
1831
- * `extras-extras` key to `agents-extras` and rewrite `source.path` /
1832
- * `installLocation`.
1833
- * 4. In `<configDir>/settings.json`'s `enabledPlugins`, rename every
1834
- * `<plugin>@extras-extras` key to `<plugin>@agents-extras` (preserving
1835
- * its boolean value, skipping if the new key already exists).
1836
- *
1837
- * Idempotent: re-running converges without further writes once everything is on
1838
- * the new name.
1839
- */
1607
+ /** Rename the legacy `extras-extras/` plugin marketplace dir to `agents-extras/` in every installed version home, and rewrite cross-references in `known_marketplaces.json` and `settings.json`. */
1840
1608
  export function migrateExtrasExtrasToAgentsExtras(historyDir = HISTORY_DIR) {
1841
1609
  const versionsRoot = path.join(historyDir, 'versions');
1842
1610
  if (!fs.existsSync(versionsRoot))