@phnx-labs/agents-cli 1.20.72 → 1.20.74

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 (234) hide show
  1. package/CHANGELOG.md +395 -0
  2. package/README.md +40 -4
  3. package/dist/bin/agents +0 -0
  4. package/dist/commands/activity.d.ts +12 -0
  5. package/dist/commands/activity.js +68 -0
  6. package/dist/commands/apply.d.ts +13 -0
  7. package/dist/commands/apply.js +28 -3
  8. package/dist/commands/browser.js +1 -1
  9. package/dist/commands/cloud.js +21 -11
  10. package/dist/commands/doctor.js +21 -5
  11. package/dist/commands/events.d.ts +11 -10
  12. package/dist/commands/events.js +30 -21
  13. package/dist/commands/exec.d.ts +34 -0
  14. package/dist/commands/exec.js +337 -39
  15. package/dist/commands/export.d.ts +11 -0
  16. package/dist/commands/export.js +215 -0
  17. package/dist/commands/feed.d.ts +13 -0
  18. package/dist/commands/feed.js +130 -29
  19. package/dist/commands/hq.d.ts +2 -0
  20. package/dist/commands/hq.js +58 -0
  21. package/dist/commands/import.js +51 -3
  22. package/dist/commands/lease.d.ts +19 -0
  23. package/dist/commands/lease.js +138 -11
  24. package/dist/commands/login.d.ts +2 -0
  25. package/dist/commands/login.js +123 -0
  26. package/dist/commands/logs.js +1 -1
  27. package/dist/commands/mine.d.ts +27 -0
  28. package/dist/commands/mine.js +207 -0
  29. package/dist/commands/monitors.js +79 -49
  30. package/dist/commands/plugins.js +1 -0
  31. package/dist/commands/profiles.js +158 -0
  32. package/dist/commands/repo.js +2 -60
  33. package/dist/commands/resources.d.ts +5 -0
  34. package/dist/commands/resources.js +95 -0
  35. package/dist/commands/routines.d.ts +2 -0
  36. package/dist/commands/routines.js +139 -37
  37. package/dist/commands/secrets.js +234 -47
  38. package/dist/commands/send.d.ts +15 -0
  39. package/dist/commands/send.js +62 -0
  40. package/dist/commands/sessions-tail.d.ts +1 -0
  41. package/dist/commands/sessions-tail.js +20 -9
  42. package/dist/commands/sessions.js +18 -3
  43. package/dist/commands/setup-mine.d.ts +18 -0
  44. package/dist/commands/setup-mine.js +106 -0
  45. package/dist/commands/setup-share.js +2 -2
  46. package/dist/commands/setup.js +9 -2
  47. package/dist/commands/share.d.ts +8 -1
  48. package/dist/commands/share.js +122 -37
  49. package/dist/commands/ssh.d.ts +9 -0
  50. package/dist/commands/ssh.js +196 -26
  51. package/dist/commands/sync.js +1 -1
  52. package/dist/commands/teams.js +11 -2
  53. package/dist/commands/versions.js +36 -5
  54. package/dist/commands/view.js +75 -27
  55. package/dist/index.js +80 -10
  56. package/dist/lib/activity.d.ts +124 -0
  57. package/dist/lib/activity.js +542 -0
  58. package/dist/lib/agents.d.ts +35 -0
  59. package/dist/lib/agents.js +79 -6
  60. package/dist/lib/artifact-actions.d.ts +1 -1
  61. package/dist/lib/artifact-actions.js +1 -1
  62. package/dist/lib/ask-classifier.d.ts +2 -1
  63. package/dist/lib/ask-classifier.js +3 -3
  64. package/dist/lib/auth-health.d.ts +32 -4
  65. package/dist/lib/auth-health.js +40 -1
  66. package/dist/lib/auto-pull.js +8 -1
  67. package/dist/lib/brand.d.ts +40 -0
  68. package/dist/lib/brand.js +122 -0
  69. package/dist/lib/browser/drivers/ssh.js +4 -7
  70. package/dist/lib/browser/service.js +6 -8
  71. package/dist/lib/channels/providers/index.d.ts +2 -0
  72. package/dist/lib/channels/providers/index.js +20 -0
  73. package/dist/lib/channels/providers/mailbox.d.ts +2 -0
  74. package/dist/lib/channels/providers/mailbox.js +26 -0
  75. package/dist/lib/channels/providers/openclaw-telegram.d.ts +2 -0
  76. package/dist/lib/channels/providers/openclaw-telegram.js +34 -0
  77. package/dist/lib/channels/providers/rush.d.ts +6 -0
  78. package/dist/lib/channels/providers/rush.js +55 -0
  79. package/dist/lib/channels/registry.d.ts +42 -0
  80. package/dist/lib/channels/registry.js +20 -0
  81. package/dist/lib/channels/resolve.d.ts +11 -0
  82. package/dist/lib/channels/resolve.js +12 -0
  83. package/dist/lib/cli-resources.d.ts +18 -0
  84. package/dist/lib/cli-resources.js +53 -4
  85. package/dist/lib/cloud/codex.js +6 -3
  86. package/dist/lib/cloud/factory.d.ts +1 -0
  87. package/dist/lib/cloud/factory.js +10 -4
  88. package/dist/lib/cloud/rush.d.ts +3 -1
  89. package/dist/lib/cloud/rush.js +6 -1
  90. package/dist/lib/cloud/types.d.ts +2 -0
  91. package/dist/lib/codex-home.d.ts +35 -0
  92. package/dist/lib/codex-home.js +136 -0
  93. package/dist/lib/config-transfer.d.ts +16 -0
  94. package/dist/lib/config-transfer.js +56 -0
  95. package/dist/lib/crabbox/cli.d.ts +44 -0
  96. package/dist/lib/crabbox/cli.js +120 -20
  97. package/dist/lib/crabbox/lease.d.ts +52 -1
  98. package/dist/lib/crabbox/lease.js +117 -16
  99. package/dist/lib/crabbox/progress.d.ts +31 -0
  100. package/dist/lib/crabbox/progress.js +76 -0
  101. package/dist/lib/crabbox/runtimes.d.ts +4 -0
  102. package/dist/lib/crabbox/runtimes.js +33 -8
  103. package/dist/lib/crabbox/setup-copy.d.ts +87 -0
  104. package/dist/lib/crabbox/setup-copy.js +127 -0
  105. package/dist/lib/daemon.d.ts +5 -5
  106. package/dist/lib/daemon.js +9 -9
  107. package/dist/lib/drive-sync.js +6 -3
  108. package/dist/lib/event-stream.d.ts +36 -0
  109. package/dist/lib/event-stream.js +68 -0
  110. package/dist/lib/events.d.ts +1 -1
  111. package/dist/lib/exec.d.ts +11 -0
  112. package/dist/lib/exec.js +23 -2
  113. package/dist/lib/export.d.ts +72 -0
  114. package/dist/lib/export.js +269 -0
  115. package/dist/lib/feed-policy.d.ts +1 -1
  116. package/dist/lib/feed-policy.js +14 -9
  117. package/dist/lib/feed-ranking.d.ts +32 -0
  118. package/dist/lib/feed-ranking.js +224 -0
  119. package/dist/lib/feed.d.ts +35 -2
  120. package/dist/lib/feed.js +66 -3
  121. package/dist/lib/fleet/auth-sync.d.ts +53 -0
  122. package/dist/lib/fleet/auth-sync.js +92 -0
  123. package/dist/lib/fleet/remote-login.d.ts +187 -0
  124. package/dist/lib/fleet/remote-login.js +556 -0
  125. package/dist/lib/format.d.ts +30 -3
  126. package/dist/lib/format.js +34 -5
  127. package/dist/lib/fs-atomic.d.ts +7 -2
  128. package/dist/lib/fs-atomic.js +8 -12
  129. package/dist/lib/git.d.ts +16 -0
  130. package/dist/lib/git.js +79 -2
  131. package/dist/lib/heal.js +11 -3
  132. package/dist/lib/hosts/progress.d.ts +28 -10
  133. package/dist/lib/hosts/progress.js +79 -22
  134. package/dist/lib/hosts/remote-cmd.js +4 -0
  135. package/dist/lib/hq/floor.d.ts +87 -0
  136. package/dist/lib/hq/floor.js +226 -0
  137. package/dist/lib/import.d.ts +23 -0
  138. package/dist/lib/import.js +74 -2
  139. package/dist/lib/isolation-boundary-report.d.ts +10 -0
  140. package/dist/lib/isolation-boundary-report.js +35 -0
  141. package/dist/lib/menubar/install-menubar.js +14 -20
  142. package/dist/lib/notify.d.ts +1 -0
  143. package/dist/lib/notify.js +3 -3
  144. package/dist/lib/open-url.d.ts +2 -0
  145. package/dist/lib/open-url.js +19 -0
  146. package/dist/lib/openclaw-keychain.d.ts +56 -0
  147. package/dist/lib/openclaw-keychain.js +236 -0
  148. package/dist/lib/overdue.js +10 -0
  149. package/dist/lib/permissions.d.ts +44 -1
  150. package/dist/lib/permissions.js +284 -7
  151. package/dist/lib/project-launch.d.ts +6 -12
  152. package/dist/lib/project-launch.js +13 -228
  153. package/dist/lib/project-resources.d.ts +7 -0
  154. package/dist/lib/project-resources.js +306 -0
  155. package/dist/lib/pty-client.d.ts +27 -0
  156. package/dist/lib/pty-client.js +136 -10
  157. package/dist/lib/refresh.js +14 -10
  158. package/dist/lib/resource-profiles.d.ts +26 -0
  159. package/dist/lib/resource-profiles.js +157 -0
  160. package/dist/lib/resources/permissions.js +7 -1
  161. package/dist/lib/resources/types.d.ts +1 -1
  162. package/dist/lib/resources.d.ts +1 -1
  163. package/dist/lib/resources.js +32 -3
  164. package/dist/lib/routines.d.ts +11 -0
  165. package/dist/lib/routines.js +56 -15
  166. package/dist/lib/runner.d.ts +1 -0
  167. package/dist/lib/runner.js +66 -5
  168. package/dist/lib/secrets/bundles.d.ts +16 -3
  169. package/dist/lib/secrets/bundles.js +206 -37
  170. package/dist/lib/secrets/icloud-import.d.ts +2 -2
  171. package/dist/lib/secrets/icloud-import.js +9 -6
  172. package/dist/lib/secrets/mcp.js +1 -1
  173. package/dist/lib/secrets/vault-age-helper.d.ts +1 -0
  174. package/dist/lib/secrets/vault-age-helper.js +34 -0
  175. package/dist/lib/secrets/vault.d.ts +49 -0
  176. package/dist/lib/secrets/vault.js +397 -0
  177. package/dist/lib/self-heal/checks/path.js +3 -1
  178. package/dist/lib/self-heal/checks/shadowing.js +10 -2
  179. package/dist/lib/self-heal/checks/shims.js +19 -11
  180. package/dist/lib/session/cloud.d.ts +2 -2
  181. package/dist/lib/session/cloud.js +2 -2
  182. package/dist/lib/session/db.d.ts +4 -0
  183. package/dist/lib/session/db.js +44 -7
  184. package/dist/lib/session/discover.d.ts +2 -0
  185. package/dist/lib/session/discover.js +114 -3
  186. package/dist/lib/session/stream-render.d.ts +3 -0
  187. package/dist/lib/session/stream-render.js +130 -0
  188. package/dist/lib/session/types.d.ts +6 -0
  189. package/dist/lib/share/analytics.d.ts +13 -0
  190. package/dist/lib/share/analytics.js +45 -0
  191. package/dist/lib/share/config.d.ts +19 -5
  192. package/dist/lib/share/config.js +44 -8
  193. package/dist/lib/share/provision.d.ts +58 -6
  194. package/dist/lib/share/provision.js +97 -22
  195. package/dist/lib/share/publish.d.ts +36 -13
  196. package/dist/lib/share/publish.js +55 -8
  197. package/dist/lib/share/worker-template.js +83 -17
  198. package/dist/lib/shims.d.ts +35 -0
  199. package/dist/lib/shims.js +159 -13
  200. package/dist/lib/ssh-exec.d.ts +14 -0
  201. package/dist/lib/ssh-exec.js +57 -0
  202. package/dist/lib/staleness/detectors/hooks.js +25 -1
  203. package/dist/lib/staleness/detectors/permissions.js +66 -0
  204. package/dist/lib/staleness/detectors/workflows.js +20 -0
  205. package/dist/lib/staleness/writers/hooks.js +58 -4
  206. package/dist/lib/staleness/writers/permissions.js +2 -2
  207. package/dist/lib/staleness/writers/skills.js +1 -1
  208. package/dist/lib/staleness/writers/sources.d.ts +10 -1
  209. package/dist/lib/staleness/writers/sources.js +68 -1
  210. package/dist/lib/star-nudge.d.ts +45 -0
  211. package/dist/lib/star-nudge.js +91 -0
  212. package/dist/lib/startup/command-registry.d.ts +7 -1
  213. package/dist/lib/startup/command-registry.js +19 -3
  214. package/dist/lib/state.d.ts +2 -0
  215. package/dist/lib/state.js +17 -4
  216. package/dist/lib/subagents-registry.d.ts +2 -0
  217. package/dist/lib/subagents-registry.js +3 -0
  218. package/dist/lib/teams/parsers.js +214 -6
  219. package/dist/lib/teams/supervisor.d.ts +7 -0
  220. package/dist/lib/teams/supervisor.js +2 -1
  221. package/dist/lib/template.d.ts +1 -1
  222. package/dist/lib/template.js +1 -1
  223. package/dist/lib/triggers/webhook.js +36 -3
  224. package/dist/lib/types.d.ts +84 -0
  225. package/dist/lib/uninstall.js +1 -45
  226. package/dist/lib/version.d.ts +40 -0
  227. package/dist/lib/version.js +94 -16
  228. package/dist/lib/versions.d.ts +24 -0
  229. package/dist/lib/versions.js +269 -78
  230. package/dist/lib/workflows.d.ts +2 -0
  231. package/dist/lib/workflows.js +88 -0
  232. package/package.json +2 -1
  233. package/dist/commands/daemon.d.ts +0 -10
  234. package/dist/commands/daemon.js +0 -121
@@ -0,0 +1,215 @@
1
+ import chalk from 'chalk';
2
+ import { execFileSync } from 'child_process';
3
+ import { confirm, select } from '@inquirer/prompts';
4
+ import { AGENTS, agentLabel, formatAgentError, resolveAgentName } from '../lib/agents.js';
5
+ import { listInstalledVersions, isVersionIsolated } from '../lib/versions.js';
6
+ import { planExport, executeExport, CONFLICT_SUFFIX } from '../lib/export.js';
7
+ import { isInteractiveTerminal, isPromptCancelled } from './utils.js';
8
+ function isolatedVersions(agent) {
9
+ return listInstalledVersions(agent).filter((v) => isVersionIsolated(agent, v));
10
+ }
11
+ /** Explain a blocker in terms of what the user should do instead. */
12
+ function reportBlocker(plan) {
13
+ const label = agentLabel(plan.agent);
14
+ const b = plan.blocker;
15
+ switch (b.kind) {
16
+ case 'not-installed':
17
+ console.log(chalk.red(`${label}@${plan.version} is not installed.`));
18
+ console.log(chalk.gray(` Installed: ${listInstalledVersions(plan.agent).join(', ') || 'none'}`));
19
+ break;
20
+ case 'not-isolated':
21
+ console.log(chalk.yellow(`${label}@${plan.version} is not an isolated install.`));
22
+ console.log(chalk.gray(` A normal install's config already IS ${plan.dest} (adoption symlinks it),`));
23
+ console.log(chalk.gray(' so there is nothing to export. To un-adopt it, use: agents uninstall'));
24
+ break;
25
+ case 'no-config':
26
+ console.log(chalk.yellow(`${label}@${plan.version} has no config to export yet (${b.source} is empty).`));
27
+ console.log(chalk.gray(` Run it once first: agents run ${plan.agent}@${plan.version}`));
28
+ break;
29
+ case 'dest-adopted':
30
+ console.log(chalk.red(`${b.realPath} is managed by agents-cli (adopted by ${label}@${b.adoptedVersion}).`));
31
+ console.log(chalk.gray(' Exporting would write into that version\'s home, not your real config.'));
32
+ console.log(chalk.gray(` Release it first: agents uninstall (or agents remove ${plan.agent}@${b.adoptedVersion})`));
33
+ break;
34
+ }
35
+ }
36
+ /** Unified diff between the user's file and the incoming one, via git. */
37
+ function printDiff(existing, incoming) {
38
+ try {
39
+ execFileSync('git', ['diff', '--no-index', '--color', '--', existing, incoming], {
40
+ stdio: ['ignore', 'inherit', 'ignore'],
41
+ });
42
+ }
43
+ catch {
44
+ // git diff --no-index exits 1 when files differ; output already streamed.
45
+ }
46
+ }
47
+ function printPlan(plan, opts) {
48
+ const label = agentLabel(plan.agent);
49
+ console.log(chalk.bold(`Export ${label}@${plan.version} -> ${plan.dest} ${chalk.gray(`[${plan.mode}]`)}\n`));
50
+ console.log(` ${chalk.gray('from')} ${plan.source}`);
51
+ console.log(` ${chalk.gray('to')} ${plan.stagedPath ?? plan.dest}`);
52
+ console.log();
53
+ if (plan.mode === 'replace') {
54
+ console.log(chalk.yellow(` Replacing ${plan.dest} wholesale — ${plan.writes.length} file(s).`));
55
+ if (plan.backupPath)
56
+ console.log(chalk.gray(` Your current config moves to: ${plan.backupPath}`));
57
+ else if (plan.destKind === 'foreign-symlink') {
58
+ console.log(chalk.yellow(` ${plan.dest} is a symlink agents-cli does not own; it will be removed.`));
59
+ console.log(chalk.gray(' Its target is left untouched.'));
60
+ }
61
+ }
62
+ else if (plan.mode === 'staged') {
63
+ console.log(chalk.gray(` Writing ${plan.writes.length} file(s) into ${plan.stagedPath}.`));
64
+ console.log(chalk.gray(' Nothing is activated — your config is untouched.'));
65
+ }
66
+ else {
67
+ if (plan.writes.length > 0) {
68
+ console.log(chalk.green(` ${plan.writes.length} new file(s) will be added:`));
69
+ for (const w of plan.writes.slice(0, 15))
70
+ console.log(chalk.gray(` + ${w.rel}`));
71
+ if (plan.writes.length > 15)
72
+ console.log(chalk.gray(` + ${plan.writes.length - 15} more`));
73
+ }
74
+ else {
75
+ console.log(chalk.gray(' No new files to add.'));
76
+ }
77
+ if (plan.conflicts.length > 0) {
78
+ console.log();
79
+ console.log(chalk.yellow(` ${plan.conflicts.length} file(s) you already have — YOURS ARE NOT MODIFIED.`));
80
+ console.log(chalk.gray(` The incoming version is written beside each as *${CONFLICT_SUFFIX}:`));
81
+ for (const c of plan.conflicts.slice(0, 15))
82
+ console.log(chalk.gray(` ~ ${c.rel}`));
83
+ if (plan.conflicts.length > 15)
84
+ console.log(chalk.gray(` ~ ${plan.conflicts.length - 15} more`));
85
+ }
86
+ }
87
+ if (opts.diff && plan.conflicts.length > 0) {
88
+ for (const c of plan.conflicts) {
89
+ console.log(chalk.bold(`\n─── ${c.rel} ───`));
90
+ printDiff(c.existing, c.source);
91
+ }
92
+ }
93
+ console.log(chalk.gray(`\n Receipt: ${plan.receiptPath}`));
94
+ }
95
+ export function registerExportCommand(program) {
96
+ program
97
+ .command('export <spec>')
98
+ .description('Copy an isolated install\'s config out to your real ~/.<agent>')
99
+ .option('--replace', 'Replace your config wholesale (yours is backed up) instead of merging')
100
+ .option('--staged', 'Write into ~/.<agent>/.agents-export-<ts>/ and activate nothing')
101
+ .option('--diff', 'Show a unified diff for each file you already have')
102
+ .option('--dry-run', 'Show what would change without writing anything')
103
+ .option('-y, --yes', 'Skip the confirmation prompt')
104
+ .addHelpText('after', `
105
+ Modes:
106
+ (default) merge — additive. Your files are NEVER modified. Anything that
107
+ collides is written beside yours as <name>${CONFLICT_SUFFIX}
108
+ so you can diff and take the parts you want.
109
+ --replace — the isolated config becomes ~/.<agent>; yours is moved to
110
+ backups/<agent>/<ts>. For promoting a sandbox wholesale.
111
+ --staged — dump the tree into ~/.<agent>/.agents-export-<ts>/ and
112
+ activate nothing. For inspecting first.
113
+
114
+ Every mode writes a receipt to ~/.<agent>/.agents-cli-export.json listing exactly
115
+ what came from the export — so you can tell your settings from the CLI's, and undo it.
116
+
117
+ Examples:
118
+ agents export codex --dry-run # see the plan
119
+ agents export codex --diff # ...and the deltas on colliding files
120
+ agents export codex # additive; nothing of yours changes
121
+ agents export codex@0.144.6 --replace # promote the sandbox wholesale
122
+
123
+ Note: file CONTENTS are never auto-merged. The TOML parser here does not preserve
124
+ comments, so merging keys would silently delete them. You get both files and a diff.
125
+ `)
126
+ .action(async (spec, options) => {
127
+ if (options.replace && options.staged) {
128
+ console.error(chalk.red('--replace and --staged are mutually exclusive; pass only one.'));
129
+ process.exit(1);
130
+ }
131
+ const mode = options.replace ? 'replace' : options.staged ? 'staged' : 'merge';
132
+ const [rawAgent, rawVersion] = spec.split('@');
133
+ const agent = resolveAgentName(rawAgent);
134
+ if (!agent) {
135
+ console.error(chalk.red(formatAgentError(rawAgent)));
136
+ process.exit(1);
137
+ }
138
+ let version = rawVersion;
139
+ if (!version) {
140
+ const candidates = isolatedVersions(agent);
141
+ if (candidates.length === 0) {
142
+ console.log(chalk.yellow(`No isolated ${agentLabel(agent)} installs to export.`));
143
+ console.log(chalk.gray(` Create one with: agents add ${agent}@<version> --isolated`));
144
+ return;
145
+ }
146
+ if (candidates.length === 1) {
147
+ version = candidates[0];
148
+ }
149
+ else if (isInteractiveTerminal()) {
150
+ version = await select({
151
+ message: `Which isolated ${agentLabel(agent)} version?`,
152
+ choices: candidates.map((v) => ({ name: v, value: v })),
153
+ }).catch((err) => {
154
+ if (isPromptCancelled(err))
155
+ process.exit(130);
156
+ throw err;
157
+ });
158
+ }
159
+ else {
160
+ console.error(chalk.red(`${agentLabel(agent)} has several isolated installs; name one.`));
161
+ console.error(chalk.gray(` ${candidates.map((v) => `agents export ${agent}@${v}`).join('\n ')}`));
162
+ process.exit(1);
163
+ }
164
+ }
165
+ const timestamp = Date.now();
166
+ const plan = planExport(agent, version, timestamp, mode);
167
+ if (plan.blocker) {
168
+ reportBlocker(plan);
169
+ process.exit(1);
170
+ }
171
+ printPlan(plan, { diff: options.diff });
172
+ if (options.dryRun) {
173
+ console.log(chalk.gray('\nDry run — nothing was written.'));
174
+ return;
175
+ }
176
+ // Only `--replace` can destroy anything, so it is the only mode that demands
177
+ // confirmation. merge and staged are additive by construction.
178
+ if (mode === 'replace' && !options.yes) {
179
+ if (!isInteractiveTerminal()) {
180
+ console.error(chalk.red('\nRefusing to replace your config without confirmation in a non-interactive shell.'));
181
+ console.error(chalk.gray(' Re-run with --yes once you have checked --dry-run.'));
182
+ process.exit(1);
183
+ }
184
+ const ok = await confirm({ message: `Replace ${plan.dest}?`, default: false }).catch((err) => {
185
+ if (isPromptCancelled(err))
186
+ process.exit(130);
187
+ throw err;
188
+ });
189
+ if (!ok) {
190
+ console.log(chalk.gray('Aborted.'));
191
+ return;
192
+ }
193
+ }
194
+ const result = executeExport(plan, timestamp);
195
+ if (!result.exported) {
196
+ console.error(chalk.red(`Export failed: ${result.errors.join('; ')}`));
197
+ process.exit(1);
198
+ }
199
+ console.log();
200
+ if (result.written.length > 0)
201
+ console.log(chalk.green(`Added ${result.written.length} file(s) to ${result.dest}`));
202
+ if (result.conflicts.length > 0) {
203
+ console.log(chalk.yellow(`${result.conflicts.length} of your file(s) were left untouched; incoming copies written as *${CONFLICT_SUFFIX}`));
204
+ console.log(chalk.gray(` Compare them with: agents export ${agent}@${version} --diff --dry-run`));
205
+ }
206
+ if (result.backupPath)
207
+ console.log(chalk.gray(`Previous config kept at: ${result.backupPath}`));
208
+ if (result.stagedPath)
209
+ console.log(chalk.gray(`Staged (nothing activated): ${result.stagedPath}`));
210
+ console.log(chalk.gray(`Receipt: ${result.receiptPath}`));
211
+ if (mode !== 'staged') {
212
+ console.log(chalk.gray(` ${AGENTS[plan.agent].cliCommand} reads this config directly.`));
213
+ }
214
+ });
215
+ }
@@ -12,6 +12,8 @@
12
12
  import type { Command } from 'commander';
13
13
  import { type OpenBlock } from '../lib/feed.js';
14
14
  import { type OutcomeGroup, type SessionOutcomeHint } from '../lib/feed-outcome.js';
15
+ import { filterBlocksForFeed } from '../lib/ask-classifier.js';
16
+ import { type FeedSessionSignal } from '../lib/feed-ranking.js';
15
17
  export declare const FEED_NO_FANOUT_ENV = "AGENTS_FEED_LOCAL";
16
18
  /** Right-hand masthead summary: `N blocks · M agents`. */
17
19
  export declare function formatFeedMastheadRight(blocks: OpenBlock[]): string;
@@ -20,8 +22,19 @@ export declare function formatFeedReplyHint(mailboxId: string): string;
20
22
  export declare function parseRemoteFeed(stdout: string, machine: string): OpenBlock[];
21
23
  /** Merge local and remote rows, keeping the first copy of a host/session block. */
22
24
  export declare function mergeFeedBlocks(...groups: OpenBlock[][]): OpenBlock[];
25
+ export type FeedControlAction = 'pause' | 'kill';
26
+ export declare function controlFeedSession(action: FeedControlAction, target: string, signals: FeedSessionSignal[]): Promise<string>;
23
27
  export declare function shouldIncludeLocalFeed(hosts: string[] | undefined, self: string): boolean;
24
28
  export declare function remoteFeedHostsToDial(hosts: string[] | undefined, self: string): string[] | undefined;
29
+ export declare function prepareLocalFeedBlocks(localBlocks: OpenBlock[], opts: {
30
+ includeLocal: boolean;
31
+ all?: boolean;
32
+ dispatch?: boolean;
33
+ }): {
34
+ visible: OpenBlock[];
35
+ dispatch: OpenBlock[];
36
+ filter: ReturnType<typeof filterBlocksForFeed>;
37
+ };
25
38
  /** Human summary line for one outcome rollup. */
26
39
  export declare function formatOutcomeHeader(group: OutcomeGroup): string;
27
40
  /** Map active sessions into the lightweight hints outcome enrichment needs. */
@@ -1,5 +1,6 @@
1
1
  import chalk from 'chalk';
2
- import { ensureFeedPublishHook, listBlocks, recordNotified } from '../lib/feed.js';
2
+ import { ensureFeedPublishHook, listAskStats, listBlocks, recordNotified } from '../lib/feed.js';
3
+ import { ensureActivityLogHook, readRecentActivity, formatActivityLine } from '../lib/activity.js';
3
4
  import { enrichBlocksFromSessions, groupBlocksByOutcome, isUnambiguousOutcomeAnswer, openBlocksForOutcome, stampBlockOutcomes, } from '../lib/feed-outcome.js';
4
5
  import { classifyBlock, filterBlocksForFeed, suppressionDigest, } from '../lib/ask-classifier.js';
5
6
  import { machineId, normalizeHost } from '../lib/machine-id.js';
@@ -12,6 +13,9 @@ import { isValidMailboxId } from '../lib/mailbox.js';
12
13
  import { getActiveSessions } from '../lib/session/active.js';
13
14
  import { mailboxIdForActiveSession } from '../lib/mailbox-target.js';
14
15
  import { GLYPH, masthead } from '../lib/comms-render.js';
16
+ import { discoverSessions } from '../lib/session/discover.js';
17
+ import { resolveProvider } from '../lib/cloud/registry.js';
18
+ import { buildSessionSignals, rankFeedBlocks, synthesizeControlCards, } from '../lib/feed-ranking.js';
15
19
  export const FEED_NO_FANOUT_ENV = 'AGENTS_FEED_LOCAL';
16
20
  /** Right-hand masthead summary: `N blocks · M agents`. */
17
21
  export function formatFeedMastheadRight(blocks) {
@@ -58,6 +62,36 @@ export function mergeFeedBlocks(...groups) {
58
62
  }
59
63
  return [...byIdentity.values()].sort((a, b) => Date.parse(b.ts) - Date.parse(a.ts));
60
64
  }
65
+ function matchesControlTarget(signal, target) {
66
+ return [
67
+ signal.mailboxId,
68
+ signal.sessionId,
69
+ signal.cloudTaskId,
70
+ signal.pid !== undefined ? String(signal.pid) : undefined,
71
+ ].some((value) => value === target);
72
+ }
73
+ export async function controlFeedSession(action, target, signals) {
74
+ const signal = signals.find((s) => matchesControlTarget(s, target));
75
+ if (!signal)
76
+ throw new Error(`No live feed session matches '${target}'.`);
77
+ if (signal.cloudProvider && signal.cloudTaskId) {
78
+ const provider = resolveProvider(signal.cloudProvider);
79
+ await provider.cancel(signal.cloudTaskId);
80
+ return `${action === 'pause' ? 'paused' : 'killed'} cloud task ${signal.cloudTaskId}`;
81
+ }
82
+ if (!signal.pid) {
83
+ throw new Error(`Session '${target}' has no local pid or cancellable cloud task.`);
84
+ }
85
+ if (action === 'pause') {
86
+ if (process.platform === 'win32') {
87
+ throw new Error('Pause is not supported for local Windows processes; use --kill.');
88
+ }
89
+ process.kill(signal.pid, 'SIGSTOP');
90
+ return `paused pid ${signal.pid}`;
91
+ }
92
+ process.kill(signal.pid, 'SIGTERM');
93
+ return `killed pid ${signal.pid}`;
94
+ }
61
95
  function hostToken(host) {
62
96
  return normalizeHost(host.split('@').pop() || host);
63
97
  }
@@ -69,6 +103,16 @@ export function remoteFeedHostsToDial(hosts, self) {
69
103
  return undefined;
70
104
  return hosts.filter((host) => hostToken(host) !== self);
71
105
  }
106
+ export function prepareLocalFeedBlocks(localBlocks, opts) {
107
+ const filter = filterBlocksForFeed(localBlocks, {
108
+ apply: opts.includeLocal && (!opts.all || opts.dispatch === true),
109
+ });
110
+ return {
111
+ visible: opts.all ? localBlocks : filter.surfaced,
112
+ dispatch: filter.surfaced,
113
+ filter,
114
+ };
115
+ }
72
116
  function renderBlock(b, localHost, indent = '') {
73
117
  const host = b.host !== localHost ? chalk.yellow(` [${b.host}]`) : '';
74
118
  const runtime = chalk.gray(b.runtime);
@@ -76,13 +120,16 @@ function renderBlock(b, localHost, indent = '') {
76
120
  const cls = b.blockClass ? chalk.gray(`(${b.blockClass})`) : '';
77
121
  const consequence = b.consequence && b.consequence !== 'normal' ? chalk.red(`[${b.consequence}]`) : '';
78
122
  const cost = b.costOfDelay ? chalk.gray(`cost:${b.costOfDelay}`) : '';
123
+ const rank = b.delayRank ? chalk.gray(`rank:${Math.round(b.delayRank.score)}`) : '';
79
124
  // Shared fleet-comms glyphs: ▲ open ask, ✓ answered (see comms-render GLYPH).
80
125
  const marker = b.answer
81
126
  ? chalk.green(GLYPH.delivered)
82
- : !b.parkedAt
83
- ? chalk.yellow(GLYPH.ask)
84
- : ' ';
85
- console.log(`${indent}${marker} ${chalk.cyan(b.mailboxId)}${host} ${runtime} ${age} ${cls} ${consequence} ${cost}`.trimEnd());
127
+ : b.kind === 'control'
128
+ ? chalk.red('!')
129
+ : !b.parkedAt
130
+ ? chalk.yellow(GLYPH.ask)
131
+ : ' ';
132
+ console.log(`${indent}${marker} ${chalk.cyan(b.mailboxId)}${host} ${runtime} ${age} ${cls} ${consequence} ${cost} ${rank}`.trimEnd());
86
133
  for (const question of b.questions) {
87
134
  const header = question.header ? chalk.gray(`[${question.header}] `) : '';
88
135
  console.log(`${indent} ${header}${question.text}`);
@@ -119,7 +166,15 @@ function renderBlock(b, localHost, indent = '') {
119
166
  if (b.notifiedAt) {
120
167
  console.log(`${indent} ${chalk.dim('notified')} ${relTime(b.notifiedAt)}`);
121
168
  }
122
- if (!b.answer && !b.parkedAt) {
169
+ if (b.runaway) {
170
+ console.log(`${indent} ${chalk.red('runaway:')} ${b.runaway.reason}`);
171
+ console.log(`${indent} ${chalk.dim(`control: ag feed --pause ${b.mailboxId} · ag feed --kill ${b.mailboxId}`)}`);
172
+ }
173
+ if (b.needy) {
174
+ console.log(`${indent} ${chalk.yellow('needy:')} ${b.needy.askCountLastHour}/${b.needy.threshold} asks in the last hour`);
175
+ console.log(`${indent} ${chalk.dim(`inspect: ag sessions ${b.sessionId}`)}`);
176
+ }
177
+ if (!b.answer && !b.parkedAt && b.kind !== 'control') {
123
178
  console.log(`${indent} ${chalk.dim(formatFeedReplyHint(b.mailboxId))}`);
124
179
  }
125
180
  console.log();
@@ -169,6 +224,8 @@ export function registerFeedCommand(program) {
169
224
  .option('-H, --host <target...>', 'Scope to remote machine(s) over SSH; repeatable')
170
225
  .option('--device <target...>', 'Alias for --host; repeatable')
171
226
  .option('--dispatch', 'Run stall suppression + default-on-no-answer policy and urgent notifications')
227
+ .option('--pause <id>', 'Pause a runaway/needy local process (SIGSTOP) or cancel a cloud task')
228
+ .option('--kill <id>', 'Kill a runaway/needy local process (SIGTERM) or cancel a cloud task')
172
229
  .action(async (opts) => {
173
230
  if (opts.device?.length)
174
231
  opts.host = [...(opts.host ?? []), ...opts.device];
@@ -176,11 +233,16 @@ export function registerFeedCommand(program) {
176
233
  const includeLocal = shouldIncludeLocalFeed(opts.host, self);
177
234
  const setupWarnings = [];
178
235
  if (includeLocal) {
236
+ // Feed and activity hooks are independent -- install both, and register
237
+ // the manifest as long as at least one wrote its entries (don't couple
238
+ // activity registration to the feed hook succeeding).
179
239
  const hookInstall = ensureFeedPublishHook();
180
- if (hookInstall.error) {
240
+ const activityInstall = ensureActivityLogHook();
241
+ if (hookInstall.error)
181
242
  setupWarnings.push(hookInstall.error);
182
- }
183
- else {
243
+ if (activityInstall.error)
244
+ setupWarnings.push(activityInstall.error);
245
+ if (!hookInstall.error || !activityInstall.error) {
184
246
  const [{ iterHooksCapableVersions, parseHookManifest, registerHooksToSettings }, { getVersionHomePath }] = await Promise.all([
185
247
  import('../lib/hooks.js'),
186
248
  import('../lib/versions.js'),
@@ -199,6 +261,17 @@ export function registerFeedCommand(program) {
199
261
  if (includeLocal) {
200
262
  sessions = await getActiveSessions();
201
263
  }
264
+ const sessionMetas = includeLocal && sessions.length > 0 ? await discoverSessions({ all: true, limit: 5000 }) : [];
265
+ const localSignals = buildSessionSignals(sessions, sessionMetas);
266
+ if (opts.pause || opts.kill) {
267
+ if (!includeLocal) {
268
+ throw new Error('Feed controls run on the local machine. Re-run against the target host with --local.');
269
+ }
270
+ const action = opts.pause ? 'pause' : 'kill';
271
+ const target = opts.pause ?? opts.kill ?? '';
272
+ console.log(await controlFeedSession(action, target, localSignals));
273
+ return;
274
+ }
202
275
  if (opts.dispatch && includeLocal) {
203
276
  // Liveness sweep: drop messages to dead agents and retire stale blocks
204
277
  // before we render the feed.
@@ -208,8 +281,26 @@ export function registerFeedCommand(program) {
208
281
  console.log(chalk.yellow(`gc: ${gcResult.messagesDroppedDead} dead messages, ${gcResult.blocksRemoved} stale blocks removed`));
209
282
  }
210
283
  }
211
- const localBlocks = includeLocal ? listBlocks() : [];
212
- let blocks = localBlocks;
284
+ let localBlocks = includeLocal
285
+ ? [...listBlocks(), ...synthesizeControlCards(localSignals, listAskStats())]
286
+ : [];
287
+ // Fill missing ticket/PR/worktree from live session meta before local
288
+ // policy mutates the store, so outcome keys land even when the publish
289
+ // hook had no deliverable stamp.
290
+ if (sessions.length > 0) {
291
+ localBlocks = enrichBlocksFromSessions(localBlocks, sessionHintsFromActive(sessions));
292
+ }
293
+ // Stall suppression (RUSH-1477) must only mutate blocks owned by this
294
+ // machine. Remote peers run their own `feed --json`; never enqueue a
295
+ // policy answer into a local mailbox for a remote agent.
296
+ const preparedLocal = prepareLocalFeedBlocks(localBlocks, {
297
+ includeLocal,
298
+ all: opts.all,
299
+ dispatch: opts.dispatch,
300
+ });
301
+ const visibleLocalBlocks = preparedLocal.visible;
302
+ const dispatchBlocks = preparedLocal.dispatch;
303
+ let blocks = visibleLocalBlocks;
213
304
  const forceLocal = opts.local === true || process.env[FEED_NO_FANOUT_ENV] === '1';
214
305
  if (!forceLocal) {
215
306
  const remoteHosts = remoteFeedHostsToDial(opts.host, self);
@@ -221,31 +312,18 @@ export function registerFeedCommand(program) {
221
312
  hosts: remoteHosts,
222
313
  parse: parseRemoteFeed,
223
314
  });
224
- blocks = mergeFeedBlocks(localBlocks, remote.items);
315
+ blocks = mergeFeedBlocks(visibleLocalBlocks, remote.items);
225
316
  }
226
317
  }
227
- // Fill missing ticket/PR/worktree from live session meta so outcome keys
228
- // land even when the publish hook had no deliverable stamp.
229
- if (sessions.length > 0) {
230
- blocks = enrichBlocksFromSessions(blocks, sessionHintsFromActive(sessions));
231
- }
232
- // Stall suppression (RUSH-1477): auto-answer "should I…?" / "what's next?"
233
- // so they never render. Applied on every feed read unless --all; --dispatch
234
- // also applies so unattended ticks drain the stall backlog.
235
- const filter = filterBlocksForFeed(blocks, {
236
- apply: !opts.all || opts.dispatch === true,
237
- });
238
- if (!opts.all) {
239
- blocks = filter.surfaced;
240
- }
241
- const digest = suppressionDigest(filter);
318
+ blocks = rankFeedBlocks(blocks, localSignals);
319
+ const digest = suppressionDigest(preparedLocal.filter);
242
320
  if (digest && !opts.json) {
243
321
  console.log(chalk.dim(digest));
244
322
  }
245
323
  if (opts.dispatch) {
246
324
  const policy = loadPolicy();
247
325
  const now = new Date();
248
- for (const b of blocks) {
326
+ for (const b of dispatchBlocks) {
249
327
  // Wrap per-block policy so one malformed block (e.g. a crafted
250
328
  // mailboxId that throws in mailboxDir) can't abort the whole loop and
251
329
  // strand every remaining block's dispatch.
@@ -285,6 +363,8 @@ export function registerFeedCommand(program) {
285
363
  }
286
364
  if (blocks.length === 0) {
287
365
  console.log(chalk.gray(digest ? 'No open blocks after stall suppression.' : 'No open blocks.'));
366
+ if (includeLocal)
367
+ renderActivityLane();
288
368
  return;
289
369
  }
290
370
  // Shared fleet-comms masthead (same family as `agents mailboxes`).
@@ -300,8 +380,29 @@ export function registerFeedCommand(program) {
300
380
  renderBlock(b, self);
301
381
  return;
302
382
  }
303
- const groups = groupBlocksByOutcome(blocks);
383
+ const groups = groupBlocksByOutcome(blocks).sort((a, b) => {
384
+ const ar = Math.max(...a.blocks.map((block) => block.delayRank?.score ?? 0));
385
+ const br = Math.max(...b.blocks.map((block) => block.delayRank?.score ?? 0));
386
+ return br - ar;
387
+ });
304
388
  for (const g of groups)
305
389
  renderOutcomeGroup(g, self);
390
+ if (includeLocal)
391
+ renderActivityLane();
306
392
  });
307
393
  }
394
+ /**
395
+ * Print a compact "recent activity" lane under the feed: the last few milestone
396
+ * events (plans, PRs, worktrees, sub-agents) from the append-only activity logs.
397
+ * Read-only tail of the logs -- no transcript re-parsing. Silent when empty.
398
+ */
399
+ function renderActivityLane() {
400
+ const events = readRecentActivity({ sinceMs: Date.now() - 24 * 60 * 60 * 1000, limit: 6 })
401
+ .filter((e) => e.tier === 'milestone');
402
+ if (events.length === 0)
403
+ return;
404
+ console.log(chalk.bold('\n recent activity'));
405
+ for (const ev of events)
406
+ console.log(formatActivityLine(ev, { showHost: true }));
407
+ console.log(chalk.gray(' → agents activity for the full stream'));
408
+ }
@@ -0,0 +1,2 @@
1
+ import type { Command } from 'commander';
2
+ export declare function registerHqCommand(program: Command): void;
@@ -0,0 +1,58 @@
1
+ import chalk from 'chalk';
2
+ import { getActiveSessions } from '../lib/session/active.js';
3
+ import { AgentManager } from '../lib/teams/agents.js';
4
+ import { handleStatus, handleTasks } from '../lib/teams/api.js';
5
+ import { listAskStats, listBlocks } from '../lib/feed.js';
6
+ import { buildSessionSignals, synthesizeControlCards } from '../lib/feed-ranking.js';
7
+ import { discoverSessions } from '../lib/session/discover.js';
8
+ import { buildHqFloor } from '../lib/hq/floor.js';
9
+ import { die } from '../lib/format.js';
10
+ async function collectFloorSnapshot() {
11
+ const manager = new AgentManager();
12
+ const [sessions, tasksResult] = await Promise.all([
13
+ getActiveSessions(),
14
+ handleTasks(manager, 1000),
15
+ ]);
16
+ const teammatesByTeam = new Map();
17
+ await Promise.all(tasksResult.tasks.map(async (team) => {
18
+ const status = await handleStatus(manager, team.task_name, 'all');
19
+ teammatesByTeam.set(team.task_name, status.agents);
20
+ }));
21
+ const sessionMetas = sessions.length > 0 ? await discoverSessions({ all: true, limit: 5000 }) : [];
22
+ const blocks = [
23
+ ...listBlocks(),
24
+ ...synthesizeControlCards(buildSessionSignals(sessions, sessionMetas), listAskStats()),
25
+ ];
26
+ return buildHqFloor({
27
+ sessions,
28
+ teams: tasksResult.tasks,
29
+ teammatesByTeam,
30
+ blocks,
31
+ });
32
+ }
33
+ export function registerHqCommand(program) {
34
+ const hq = program
35
+ .command('hq')
36
+ .description('Machine-readable bridge for Agents HQ floor management.');
37
+ hq
38
+ .command('floor')
39
+ .description('Emit the live floor snapshot: rooms, agents, ambient events, and runnable actions.')
40
+ .option('--json', 'Output machine-readable JSON')
41
+ .action(async (opts) => {
42
+ try {
43
+ const snapshot = await collectFloorSnapshot();
44
+ if (opts.json || !process.stdout.isTTY) {
45
+ console.log(JSON.stringify(snapshot, null, 2));
46
+ return;
47
+ }
48
+ console.log(chalk.bold('Agents HQ floor'));
49
+ console.log(chalk.gray(`${snapshot.counters.agents} agents, ${snapshot.counters.rooms} rooms, ${snapshot.counters.needsInput} need input`));
50
+ for (const room of snapshot.rooms) {
51
+ console.log(`${chalk.cyan(room.name)} ${chalk.gray(`${room.counts.agents} agents, ${room.counts.running} running, ${room.counts.needsInput} need input`)}`);
52
+ }
53
+ }
54
+ catch (err) {
55
+ die(err.message);
56
+ }
57
+ });
58
+ }
@@ -20,6 +20,9 @@
20
20
  * In all forms, the agent's config dir (e.g. ~/.openclaw) is also moved under
21
21
  * management — same behavior as the first-run `agents setup` import flow.
22
22
  */
23
+ import { withIsolationBoundary } from '../lib/isolation-boundary-report.js';
24
+ import { assertIsolationBoundary, createVersionedAlias } from '../lib/shims.js';
25
+ import { markVersionIsolated } from '../lib/versions.js';
23
26
  import chalk from 'chalk';
24
27
  import ora from 'ora';
25
28
  import * as fs from 'fs';
@@ -29,7 +32,7 @@ import { confirm } from '@inquirer/prompts';
29
32
  import { ALL_AGENT_IDS } from '../lib/agents.js';
30
33
  import { AGENTS, getCliPath, getCliVersion, agentLabel, resolveAgentName } from '../lib/agents.js';
31
34
  import { getVersionDir } from '../lib/versions.js';
32
- import { finalizeImport, importAgentBinary, importAgentConfig, importInstallScriptBinary, isValidImportVersion, resolvePackageDirFromBinary, } from '../lib/import.js';
35
+ import { finalizeImport, importAgentBinary, importAgentConfig, importInstallScriptBinary, seedIsolatedConfigFromLocal, isValidImportVersion, resolvePackageDirFromBinary, } from '../lib/import.js';
33
36
  import { isPromptCancelled, isInteractiveTerminal } from './utils.js';
34
37
  async function runImport(agentArg, opts) {
35
38
  const agentId = resolveAgentName(agentArg);
@@ -39,6 +42,16 @@ async function runImport(agentArg, opts) {
39
42
  process.exit(1);
40
43
  }
41
44
  const agent = AGENTS[agentId];
45
+ // Import registers the adopted install as a NORMAL version and only then sets the
46
+ // default / creates the shim / repoints the config. By that point the agent has a
47
+ // non-isolated version, so it is no longer protected and the primitive gates —
48
+ // which read the state as it is at call time — would let the adoption through.
49
+ // The boundary has to be checked against the state BEFORE the import mutates it.
50
+ // --isolated adopts nothing, so the boundary does not apply to it — it is in fact
51
+ // the supported way to bring a local install in while protection is on.
52
+ if (!opts.isolated) {
53
+ assertIsolationBoundary(agentId, 'adopt your existing install');
54
+ }
42
55
  // installScript-based agents (Grok, Antigravity, Cursor, Kiro, Goose, Roo)
43
56
  // don't have an npm package; their binary lives wherever the curl/brew
44
57
  // installer dropped it. We adopt by symlinking that PATH binary directly
@@ -191,7 +204,27 @@ async function runImport(agentArg, opts) {
191
204
  // user-visible side effect (renaming ~/.<agent>/), so if it fails we don't
192
205
  // want a stranded symlink farm. Binary registration is cheap and reversible
193
206
  // — if it fails after config, the next `agents import` call retries cleanly.
194
- const willImportConfig = configDirExists && !configAlreadyManaged;
207
+ const willImportConfig = configDirExists && !configAlreadyManaged && !opts.isolated;
208
+ if (opts.isolated && configDirExists) {
209
+ // COPY the user's settings in; never move, never symlink. The original stays
210
+ // exactly where it is — that is what separates this from adoption.
211
+ const seedSpinner = ora(`Copying ${agent.configDir} into the isolated copy...`).start();
212
+ const seed = seedIsolatedConfigFromLocal(agentId, version, { withAuth: opts.withAuth });
213
+ if (seed.error) {
214
+ seedSpinner.fail(`Config: ${seed.error}`);
215
+ process.exit(1);
216
+ }
217
+ else if (seed.seeded) {
218
+ seedSpinner.succeed(`Settings copied (${seed.from} -> ${seed.to}); your original is untouched`);
219
+ if (seed.skippedAuth.length > 0) {
220
+ console.log(chalk.gray(` Credentials NOT copied: ${seed.skippedAuth.join(', ')}`));
221
+ console.log(chalk.gray(' The copy signs in separately. Use --with-auth to copy them too.'));
222
+ }
223
+ }
224
+ else {
225
+ seedSpinner.info('No existing config to copy.');
226
+ }
227
+ }
195
228
  if (willImportConfig) {
196
229
  const cfgSpinner = ora(`Importing config dir for ${agentLabel(agentId)} v${version}...`).start();
197
230
  const cfgResult = await importAgentConfig(agentId, version);
@@ -224,6 +257,19 @@ async function runImport(agentArg, opts) {
224
257
  // Wire the imported version into the resolver: global default, main shim,
225
258
  // versioned alias, home-file symlinks. Idempotent — safe to call even if
226
259
  // importAgentConfig already set the global default.
260
+ if (opts.isolated) {
261
+ // The isolated finalizer: launchable alias + marker, and nothing else. No global
262
+ // default, no bare shim, no config symlink — the same shape as
263
+ // `agents add --isolated`.
264
+ createVersionedAlias(agentId, version);
265
+ markVersionIsolated(agentId, version);
266
+ console.log();
267
+ console.log(chalk.green(`${agentLabel(agentId)} v${version} imported as an isolated copy.`));
268
+ console.log(chalk.gray(` Your ${agent.configDir} and ${agent.cliCommand} launcher are untouched.`));
269
+ console.log(chalk.gray(` Run it: agents run ${agentId}@${version}`));
270
+ console.log(chalk.gray(` Or make it the default isolated copy: agents use ${agentId}@${version}`));
271
+ return;
272
+ }
227
273
  const finalizeSpinner = ora(`Wiring ${agentLabel(agentId)} v${version} as the active version...`).start();
228
274
  try {
229
275
  finalizeImport(agentId, version);
@@ -244,6 +290,8 @@ export function registerImportCommand(program) {
244
290
  .description('Import an existing unmanaged agent install into agents-cli')
245
291
  .option('--version <version>', 'Pin a version label (otherwise read from package.json)')
246
292
  .option('--from-path <path>', 'Path to the npm package dir (otherwise auto-detected from PATH)')
293
+ .option('--isolated', 'Copy the install into a self-contained isolated version instead of adopting it')
294
+ .option('--with-auth', 'With --isolated, also copy credentials into the sandbox (skipped by default)')
247
295
  .option('-y, --yes', 'Skip the confirmation prompt')
248
296
  .addHelpText('after', `
249
297
  Examples:
@@ -265,5 +313,5 @@ When to use:
265
313
  npm-style packages (claude, codex, gemini, opencode, openclaw) and
266
314
  installScript-based agents (grok, antigravity, cursor, kiro, goose).
267
315
  `)
268
- .action(runImport);
316
+ .action((...args) => withIsolationBoundary(() => runImport(...args)));
269
317
  }