@phnx-labs/agents-cli 1.20.74 → 1.20.77

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 (231) hide show
  1. package/CHANGELOG.md +1055 -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 +18 -0
  13. package/dist/commands/exec.js +83 -13
  14. package/dist/commands/feed.d.ts +7 -1
  15. package/dist/commands/feed.js +47 -4
  16. package/dist/commands/go.d.ts +9 -2
  17. package/dist/commands/go.js +16 -6
  18. package/dist/commands/harness.d.ts +14 -0
  19. package/dist/commands/harness.js +145 -0
  20. package/dist/commands/import.js +38 -8
  21. package/dist/commands/inspect.js +5 -2
  22. package/dist/commands/profiles.d.ts +20 -0
  23. package/dist/commands/profiles.js +68 -21
  24. package/dist/commands/repo.js +14 -0
  25. package/dist/commands/routines.js +286 -15
  26. package/dist/commands/secrets.js +102 -37
  27. package/dist/commands/sessions-browser.js +12 -2
  28. package/dist/commands/sessions-export.js +19 -5
  29. package/dist/commands/sessions-migrate.d.ts +29 -0
  30. package/dist/commands/sessions-migrate.js +596 -0
  31. package/dist/commands/sessions-picker.d.ts +14 -1
  32. package/dist/commands/sessions-picker.js +184 -16
  33. package/dist/commands/sessions.d.ts +56 -14
  34. package/dist/commands/sessions.js +322 -62
  35. package/dist/commands/setup-computer.js +2 -2
  36. package/dist/commands/ssh.d.ts +13 -0
  37. package/dist/commands/ssh.js +110 -37
  38. package/dist/commands/status.js +10 -2
  39. package/dist/commands/versions.js +15 -6
  40. package/dist/commands/view.d.ts +5 -0
  41. package/dist/commands/view.js +24 -4
  42. package/dist/commands/watchdog.d.ts +7 -2
  43. package/dist/commands/watchdog.js +73 -57
  44. package/dist/index.js +59 -7
  45. package/dist/lib/activity.d.ts +17 -6
  46. package/dist/lib/activity.js +466 -40
  47. package/dist/lib/actor.d.ts +50 -0
  48. package/dist/lib/actor.js +166 -0
  49. package/dist/lib/agent-spec/provider.js +2 -1
  50. package/dist/lib/agent-spec/resolve.js +19 -5
  51. package/dist/lib/agent-spec/types.d.ts +9 -1
  52. package/dist/lib/agents.d.ts +21 -0
  53. package/dist/lib/agents.js +70 -6
  54. package/dist/lib/cloud/codex.d.ts +2 -0
  55. package/dist/lib/cloud/codex.js +14 -3
  56. package/dist/lib/cloud/session-index.d.ts +32 -0
  57. package/dist/lib/cloud/session-index.js +58 -0
  58. package/dist/lib/cloud/store.d.ts +7 -0
  59. package/dist/lib/cloud/store.js +25 -0
  60. package/dist/lib/config-transfer.js +4 -0
  61. package/dist/lib/daemon.d.ts +10 -1
  62. package/dist/lib/daemon.js +114 -11
  63. package/dist/lib/devices/connect.d.ts +15 -1
  64. package/dist/lib/devices/connect.js +15 -1
  65. package/dist/lib/devices/fleet.d.ts +37 -1
  66. package/dist/lib/devices/fleet.js +36 -2
  67. package/dist/lib/devices/health-report.d.ts +38 -0
  68. package/dist/lib/devices/health-report.js +214 -0
  69. package/dist/lib/devices/health.js +4 -1
  70. package/dist/lib/devices/reachability.d.ts +31 -0
  71. package/dist/lib/devices/reachability.js +40 -0
  72. package/dist/lib/devices/registry.d.ts +33 -0
  73. package/dist/lib/devices/registry.js +37 -0
  74. package/dist/lib/devices/resolve-target.d.ts +15 -27
  75. package/dist/lib/devices/resolve-target.js +63 -102
  76. package/dist/lib/devices/sync.d.ts +18 -0
  77. package/dist/lib/devices/sync.js +23 -1
  78. package/dist/lib/devices/tailscale.d.ts +3 -0
  79. package/dist/lib/devices/tailscale.js +1 -0
  80. package/dist/lib/events.d.ts +1 -1
  81. package/dist/lib/exec.d.ts +82 -0
  82. package/dist/lib/exec.js +218 -6
  83. package/dist/lib/feed-post.d.ts +63 -0
  84. package/dist/lib/feed-post.js +204 -0
  85. package/dist/lib/feed.d.ts +7 -1
  86. package/dist/lib/feed.js +96 -6
  87. package/dist/lib/fleet/apply.d.ts +32 -2
  88. package/dist/lib/fleet/apply.js +97 -10
  89. package/dist/lib/fleet/types.d.ts +11 -0
  90. package/dist/lib/fs-walk.d.ts +13 -0
  91. package/dist/lib/fs-walk.js +16 -7
  92. package/dist/lib/heal.d.ts +7 -4
  93. package/dist/lib/heal.js +10 -22
  94. package/dist/lib/hooks.d.ts +1 -0
  95. package/dist/lib/hooks.js +156 -0
  96. package/dist/lib/hosts/dispatch.d.ts +16 -0
  97. package/dist/lib/hosts/dispatch.js +26 -4
  98. package/dist/lib/hosts/passthrough.js +9 -2
  99. package/dist/lib/hosts/reconnect.d.ts +78 -0
  100. package/dist/lib/hosts/reconnect.js +127 -0
  101. package/dist/lib/hosts/registry.d.ts +75 -14
  102. package/dist/lib/hosts/registry.js +205 -30
  103. package/dist/lib/hosts/remote-cmd.d.ts +11 -0
  104. package/dist/lib/hosts/remote-cmd.js +32 -8
  105. package/dist/lib/hosts/remote-session-id.d.ts +45 -0
  106. package/dist/lib/hosts/remote-session-id.js +84 -0
  107. package/dist/lib/hosts/run-target.d.ts +17 -5
  108. package/dist/lib/hosts/run-target.js +30 -10
  109. package/dist/lib/hosts/session-index.d.ts +18 -1
  110. package/dist/lib/hosts/session-index.js +40 -5
  111. package/dist/lib/hosts/session-marker.d.ts +33 -0
  112. package/dist/lib/hosts/session-marker.js +51 -0
  113. package/dist/lib/hosts/tasks.d.ts +8 -4
  114. package/dist/lib/import.d.ts +2 -0
  115. package/dist/lib/import.js +35 -2
  116. package/dist/lib/menubar/install-menubar.d.ts +29 -0
  117. package/dist/lib/menubar/install-menubar.js +59 -1
  118. package/dist/lib/menubar/notify-desktop.d.ts +44 -0
  119. package/dist/lib/menubar/notify-desktop.js +78 -0
  120. package/dist/lib/migrate.d.ts +16 -0
  121. package/dist/lib/migrate.js +36 -0
  122. package/dist/lib/models.d.ts +27 -0
  123. package/dist/lib/models.js +54 -1
  124. package/dist/lib/overdue.d.ts +6 -2
  125. package/dist/lib/overdue.js +10 -36
  126. package/dist/lib/picker.js +4 -1
  127. package/dist/lib/platform/process.d.ts +17 -0
  128. package/dist/lib/platform/process.js +70 -0
  129. package/dist/lib/profiles-presets.js +9 -7
  130. package/dist/lib/profiles.d.ts +31 -0
  131. package/dist/lib/profiles.js +70 -0
  132. package/dist/lib/pty-server.d.ts +2 -10
  133. package/dist/lib/pty-server.js +4 -38
  134. package/dist/lib/registry.d.ts +1 -1
  135. package/dist/lib/registry.js +48 -8
  136. package/dist/lib/rotate.d.ts +18 -0
  137. package/dist/lib/rotate.js +28 -0
  138. package/dist/lib/routine-notify.d.ts +76 -0
  139. package/dist/lib/routine-notify.js +190 -0
  140. package/dist/lib/routines-placement.d.ts +38 -0
  141. package/dist/lib/routines-placement.js +79 -0
  142. package/dist/lib/routines-project.d.ts +97 -0
  143. package/dist/lib/routines-project.js +349 -0
  144. package/dist/lib/routines.d.ts +94 -0
  145. package/dist/lib/routines.js +93 -9
  146. package/dist/lib/runner.d.ts +12 -2
  147. package/dist/lib/runner.js +242 -20
  148. package/dist/lib/sandbox.js +10 -0
  149. package/dist/lib/secrets/account-token.d.ts +20 -0
  150. package/dist/lib/secrets/account-token.js +64 -0
  151. package/dist/lib/secrets/agent.d.ts +74 -4
  152. package/dist/lib/secrets/agent.js +181 -107
  153. package/dist/lib/secrets/bundles.d.ts +54 -6
  154. package/dist/lib/secrets/bundles.js +230 -34
  155. package/dist/lib/secrets/index.d.ts +26 -3
  156. package/dist/lib/secrets/index.js +42 -12
  157. package/dist/lib/secrets/rc-hygiene.d.ts +55 -0
  158. package/dist/lib/secrets/rc-hygiene.js +154 -0
  159. package/dist/lib/secrets/remote.d.ts +7 -4
  160. package/dist/lib/secrets/remote.js +7 -4
  161. package/dist/lib/secrets/session-store.d.ts +6 -2
  162. package/dist/lib/secrets/session-store.js +65 -15
  163. package/dist/lib/secrets/sync-commands.d.ts +21 -0
  164. package/dist/lib/secrets/sync-commands.js +21 -0
  165. package/dist/lib/secrets/unlock-hints.d.ts +27 -0
  166. package/dist/lib/secrets/unlock-hints.js +36 -0
  167. package/dist/lib/self-update.d.ts +23 -2
  168. package/dist/lib/self-update.js +86 -5
  169. package/dist/lib/session/active.d.ts +124 -7
  170. package/dist/lib/session/active.js +390 -56
  171. package/dist/lib/session/cloud.js +2 -1
  172. package/dist/lib/session/db.d.ts +54 -0
  173. package/dist/lib/session/db.js +320 -19
  174. package/dist/lib/session/detached.d.ts +30 -0
  175. package/dist/lib/session/detached.js +92 -0
  176. package/dist/lib/session/discover.d.ts +478 -3
  177. package/dist/lib/session/discover.js +1740 -472
  178. package/dist/lib/session/fork.js +2 -1
  179. package/dist/lib/session/hook-sessions.d.ts +43 -0
  180. package/dist/lib/session/hook-sessions.js +135 -0
  181. package/dist/lib/session/linear.d.ts +6 -0
  182. package/dist/lib/session/linear.js +55 -0
  183. package/dist/lib/session/migrate-targets.d.ts +65 -0
  184. package/dist/lib/session/migrate-targets.js +94 -0
  185. package/dist/lib/session/migrations.d.ts +37 -0
  186. package/dist/lib/session/migrations.js +60 -0
  187. package/dist/lib/session/parse.d.ts +18 -0
  188. package/dist/lib/session/parse.js +141 -32
  189. package/dist/lib/session/pid-registry.d.ts +34 -2
  190. package/dist/lib/session/pid-registry.js +49 -2
  191. package/dist/lib/session/prompt.d.ts +7 -0
  192. package/dist/lib/session/prompt.js +37 -0
  193. package/dist/lib/session/provenance.d.ts +7 -0
  194. package/dist/lib/session/render.d.ts +7 -2
  195. package/dist/lib/session/render.js +40 -26
  196. package/dist/lib/session/short-id.d.ts +17 -0
  197. package/dist/lib/session/short-id.js +20 -0
  198. package/dist/lib/session/state.d.ts +4 -0
  199. package/dist/lib/session/state.js +99 -13
  200. package/dist/lib/session/types.d.ts +25 -1
  201. package/dist/lib/session/types.js +8 -0
  202. package/dist/lib/shims.d.ts +1 -1
  203. package/dist/lib/shims.js +20 -6
  204. package/dist/lib/sqlite.d.ts +3 -2
  205. package/dist/lib/sqlite.js +27 -4
  206. package/dist/lib/staleness/detectors/commands.js +1 -1
  207. package/dist/lib/staleness/detectors/workflows.js +13 -2
  208. package/dist/lib/staleness/writers/commands.js +7 -7
  209. package/dist/lib/staleness/writers/workflows.d.ts +4 -2
  210. package/dist/lib/startup/command-registry.d.ts +1 -0
  211. package/dist/lib/startup/command-registry.js +3 -0
  212. package/dist/lib/state.d.ts +8 -4
  213. package/dist/lib/state.js +21 -37
  214. package/dist/lib/teams/agents.d.ts +19 -10
  215. package/dist/lib/teams/agents.js +40 -39
  216. package/dist/lib/types.d.ts +47 -2
  217. package/dist/lib/usage.d.ts +33 -1
  218. package/dist/lib/usage.js +172 -12
  219. package/dist/lib/versions.d.ts +9 -2
  220. package/dist/lib/versions.js +37 -8
  221. package/dist/lib/watchdog/log.d.ts +43 -0
  222. package/dist/lib/watchdog/log.js +69 -0
  223. package/dist/lib/watchdog/routine.d.ts +44 -0
  224. package/dist/lib/watchdog/routine.js +69 -0
  225. package/dist/lib/watchdog/runner.d.ts +51 -7
  226. package/dist/lib/watchdog/runner.js +239 -64
  227. package/dist/lib/watchdog/watchdog.d.ts +1 -1
  228. package/dist/lib/watchdog/watchdog.js +31 -16
  229. package/dist/lib/workflows.d.ts +16 -0
  230. package/dist/lib/workflows.js +110 -1
  231. package/package.json +1 -1
@@ -20,8 +20,9 @@ import { sshResolve } from '../lib/hosts/ssh-config.js';
20
20
  import * as fs from 'fs';
21
21
  import * as path from 'path';
22
22
  import * as os from 'os';
23
- import { randomUUID } from 'crypto';
24
23
  import { spawnSync } from 'child_process';
24
+ import { randomUUID } from 'crypto';
25
+ import { isSessionTrackedAgent } from '../lib/session/types.js';
25
26
  /** Distinguish a terminal account-picker marker from an explicit @version pin. */
26
27
  export function parseRunAccountPickerRequest(agentSpec) {
27
28
  const requested = agentSpec.endsWith('@');
@@ -398,6 +399,11 @@ export function registerRunCommand(program) {
398
399
  // `--on` and `--computer` are hidden aliases of `--host` — same behavior.
399
400
  runCmd.addOption(new Option('--on <name>', 'Alias of --host.').hideHelp());
400
401
  runCmd.addOption(new Option('--computer <name>', 'Alias of --host.').hideHelp());
402
+ // Internal: the `--host` dispatch forwards this so the REMOTE run prints its
403
+ // resolved session id as a one-line stdout sentinel (hosts/session-marker.ts),
404
+ // letting the launcher relate the remote-created session back to itself for
405
+ // every agent — not just Claude, whose id it forces up front.
406
+ runCmd.addOption(new Option('--emit-session-id', 'internal: print the resolved session id for a --host launcher to capture').hideHelp());
401
407
  // Required for the documented `agents run <agent> [prompt] -- <native flags>`
402
408
  // passthrough: commander >=13 rejects excess operands by default, so any
403
409
  // post-`--` token died with "too many arguments" before the action ran. The
@@ -872,7 +878,7 @@ export function registerRunCommand(program) {
872
878
  process.exit(1);
873
879
  }
874
880
  const hostName = hostGiven[0];
875
- const { resolveHostRunTarget, dispatchPromptToHost, HostResolutionError } = await import('../lib/hosts/run-target.js');
881
+ const { resolveHostRunTarget, resolveHostSessionId, dispatchPromptToHost, HostResolutionError } = await import('../lib/hosts/run-target.js');
876
882
  const { runInteractiveOnHost } = await import('../lib/hosts/dispatch.js');
877
883
  const { registerInteractiveHostSession } = await import('../lib/hosts/session-index.js');
878
884
  const { RUN_OPTION_REJECT_MESSAGES } = await import('../lib/hosts/remote-cmd.js');
@@ -1030,10 +1036,21 @@ export function registerRunCommand(program) {
1030
1036
  process.exit(1);
1031
1037
  }
1032
1038
  // Mirror the local path (lib/exec.ts): only Claude accepts a forced
1033
- // `--session-id`. Generating it here lets us register the run in the
1034
- // local index and makes it resumable by id. On resume the remote
1035
- // session keeps its existing id don't mint a new one.
1036
- const hostSessionId = runAgent === 'claude' && !resumeId ? randomUUID() : undefined;
1039
+ // `--session-id`. Adopt the caller's id when present; otherwise mint
1040
+ // one here. Registering that same id keeps the local index aligned
1041
+ // with the remote agent. On resume, don't mint a new one.
1042
+ const hostSessionId = resolveHostSessionId(runAgent, resumeId, options.sessionId);
1043
+ // For every OTHER agent the remote coins its own id, which we can't
1044
+ // know up front. Forward a launch id we control as AGENT_LAUNCH_ID:
1045
+ // the remote `agents run` adopts it (exec.ts resolveLaunchId) and its
1046
+ // SessionStart hook records the real id under that exact key, so after
1047
+ // the stream we resolve the id by one ssh read of the remote hook
1048
+ // record — the same launch-id join used locally (RUSH-2034). Not
1049
+ // needed for Claude (id forced) or resume (id already known).
1050
+ const correlationLaunchId = !hostSessionId && !resumeId && isSessionTrackedAgent(runAgent) ? randomUUID() : undefined;
1051
+ const hostEnv = correlationLaunchId
1052
+ ? [...options.env, `AGENT_LAUNCH_ID=${correlationLaunchId}`]
1053
+ : options.env;
1037
1054
  if (hostSessionId) {
1038
1055
  registerInteractiveHostSession({
1039
1056
  cwd: process.cwd(),
@@ -1043,6 +1060,7 @@ export function registerRunCommand(program) {
1043
1060
  name: options.name,
1044
1061
  });
1045
1062
  }
1063
+ const isRaw = options.raw || options.tmux === false || options.disableTmux === true;
1046
1064
  const exitCode = await runInteractiveOnHost(host, {
1047
1065
  agent: runAgent,
1048
1066
  version: resumeId ? undefined : runVersion,
@@ -1052,7 +1070,7 @@ export function registerRunCommand(program) {
1052
1070
  mode: options.mode,
1053
1071
  model: options.model,
1054
1072
  effort: options.effort,
1055
- env: options.env,
1073
+ env: hostEnv,
1056
1074
  addDir: hostAddDirs,
1057
1075
  json: options.json,
1058
1076
  verbose: options.verbose,
@@ -1064,10 +1082,55 @@ export function registerRunCommand(program) {
1064
1082
  name: options.name,
1065
1083
  resume: resumeId,
1066
1084
  passthroughArgs,
1067
- raw: options.raw || options.tmux === false || options.disableTmux === true,
1085
+ raw: isRaw,
1068
1086
  forceInteractive: options.interactive,
1069
1087
  copyCreds: hostCopyCreds,
1070
1088
  });
1089
+ // Resolve a non-Claude agent's REAL remote session id now the run has
1090
+ // booted (its hook has fired on the peer): one ssh read of the remote
1091
+ // hook record, keyed by the launch id we forwarded. Register it so the
1092
+ // run shows in `agents sessions` and can be reconnected/focused —
1093
+ // closing the non-Claude gap RUSH-2033 left. Best-effort: an
1094
+ // unreachable host or a not-yet-landed record leaves the run un-mapped
1095
+ // rather than mis-mapped.
1096
+ let resolvedRemoteId;
1097
+ if (correlationLaunchId) {
1098
+ const { resolveRemoteSessionId } = await import('../lib/hosts/remote-session-id.js');
1099
+ const { sshTargetFor } = await import('../lib/hosts/types.js');
1100
+ try {
1101
+ resolvedRemoteId = resolveRemoteSessionId(sshTargetFor(host), correlationLaunchId);
1102
+ }
1103
+ catch {
1104
+ /* ssh read is best-effort — keep the run un-mapped, never guess */
1105
+ }
1106
+ if (resolvedRemoteId) {
1107
+ registerInteractiveHostSession({
1108
+ cwd: process.cwd(),
1109
+ host: host.name,
1110
+ agent: runAgent,
1111
+ sessionId: resolvedRemoteId,
1112
+ name: options.name,
1113
+ });
1114
+ }
1115
+ }
1116
+ // A network drop kills the local ssh client (exit 255) but the remote
1117
+ // agent survives in its detached tmux session. With a known session id
1118
+ // (Claude's forced id, a resumed run, or a non-Claude id we just
1119
+ // resolved from the remote hook record) and a tmux-hosted run,
1120
+ // re-attach the live pane automatically instead of exiting — the user
1121
+ // never has to notice the drop and `agents sessions focus` by hand.
1122
+ // `raw` runs aren't tmux wrapped, so there is nothing to reconnect to.
1123
+ const reconnectId = hostSessionId ?? resolvedRemoteId ?? resumeId;
1124
+ if (reconnectId && !isRaw) {
1125
+ const { reconnectInteractiveSession, SSH_CONN_FAILURE } = await import('../lib/hosts/reconnect.js');
1126
+ if (exitCode === SSH_CONN_FAILURE) {
1127
+ process.exit(await reconnectInteractiveSession({
1128
+ host,
1129
+ sessionId: reconnectId,
1130
+ initialExit: exitCode,
1131
+ }));
1132
+ }
1133
+ }
1071
1134
  process.exit(exitCode);
1072
1135
  }
1073
1136
  // Headless host run: launch detached, tail the remote log, and follow
@@ -1103,6 +1166,7 @@ export function registerRunCommand(program) {
1103
1166
  remoteCwd: hostCwd,
1104
1167
  name: options.name,
1105
1168
  resume: resumeId,
1169
+ sessionId: options.sessionId,
1106
1170
  follow: options.follow !== false,
1107
1171
  passthroughArgs,
1108
1172
  copyCreds: hostCopyCreds,
@@ -1221,7 +1285,7 @@ export function registerRunCommand(program) {
1221
1285
  await warnUnpushedWork(resumeExec.cwd ?? process.cwd());
1222
1286
  process.exit(resumeExit);
1223
1287
  }
1224
- const [{ buildExecCommand, parseExecEnv, execAgent, runWithFallback, normalizeMode, resolveMode, headlessPlanStallCommand, nativeResume, resolveInteractive, inferredInteractiveWithoutTty }, { ALL_AGENT_IDS, ACCOUNT_INSPECTION_AGENT_IDS, agentLabel, supportsAccountInspection }, { profileExists, resolveProfileForRun }, { readAndResolveBundleEnv, describeBundle, assertRemoteBundleFlagsUnsupported, isHeadlessSecretsContext }, { splitBundleRef, resolveSshTarget, remoteResolveEnv }, { getConfiguredRunStrategy, normalizeRunStrategy, resolveRunVersion, rotationFailoverChain, shouldArmRotationFailover, RUN_STRATEGIES }, { getGlobalDefault, getVersionHomePath, resolveVersion, resolveVersionAlias, ensureAgentRunnable }, { buildDiscoveredPlugin, loadPluginManifest, syncPluginToVersion }, { parseWorkflowFrontmatter, resolveWorkflowRef, resolveAllowedSubagents, pruneStaleWorkflowSubagents, ensureSubagentDispatchTool }, { resolveRunDefaults }, { getMcpServersByName, buildWorkflowMcpConfig }, { supports }, { shareRuntimeEnv },] = await Promise.all([
1288
+ const [{ buildExecCommand, parseExecEnv, execAgent, runWithFallback, normalizeMode, resolveMode, headlessPlanStallCommand, nativeResume, resolveInteractive, inferredInteractiveWithoutTty }, { ALL_AGENT_IDS, ACCOUNT_INSPECTION_AGENT_IDS, agentLabel, supportsAccountInspection }, { profileExists, resolveProfileForRun }, { readAndResolveBundleEnv, describeBundle, assertRemoteBundleFlagsUnsupported, isHeadlessSecretsContext }, { splitBundleRef, resolveHostSshTarget, remoteResolveEnv }, { getConfiguredRunStrategy, normalizeRunStrategy, resolveRunVersion, rotationFailoverChain, shouldArmRotationFailover, RUN_STRATEGIES }, { getGlobalDefault, getVersionHomePath, resolveVersion, resolveVersionAlias, ensureAgentRunnable }, { buildDiscoveredPlugin, loadPluginManifest, syncPluginToVersion }, { parseWorkflowFrontmatter, resolveWorkflowRef, resolveAllowedSubagents, pruneStaleWorkflowSubagents, ensureSubagentDispatchTool }, { resolveRunDefaults }, { getMcpServersByName, buildWorkflowMcpConfig }, { supports }, { shareRuntimeEnv },] = await Promise.all([
1225
1289
  import('../lib/exec.js'),
1226
1290
  import('../lib/agents.js'),
1227
1291
  import('../lib/profiles.js'),
@@ -1851,7 +1915,7 @@ export function registerRunCommand(program) {
1851
1915
  assertRemoteBundleFlagsUnsupported(bundleName, host, { keys: secretsKeysSubset, allowExpired: options.allowExpired }, { keysFlag: '--secrets-keys', allowExpiredFlag: '--allow-expired' });
1852
1916
  // Remote bundle (`bundle@host`): resolve over SSH and inject
1853
1917
  // ephemerally — values never touch this machine's keychain or disk.
1854
- const target = await resolveSshTarget(host);
1918
+ const target = await resolveHostSshTarget(host);
1855
1919
  const bundleEnv = await remoteResolveEnv(target, bundleName, { osLookupName: host });
1856
1920
  console.log(chalk.gray(`[secrets] Resolved ${bundleName}@${host}: ${Object.keys(bundleEnv).length} keys (remote, ephemeral)`));
1857
1921
  secretsEnv = { ...secretsEnv, ...bundleEnv };
@@ -1859,11 +1923,13 @@ export function registerRunCommand(program) {
1859
1923
  else {
1860
1924
  const { bundle, env: bundleEnv } = readAndResolveBundleEnv(bundleName, {
1861
1925
  caller: `agent ${agent}`,
1926
+ agent,
1862
1927
  keys: secretsKeysSubset,
1863
1928
  allowExpired: options.allowExpired,
1864
- // A headless/background run (routine, teammate, detached) must not
1865
- // pop a Touch ID sheet nobody can answer resolve broker-only and
1866
- // fail fast with an actionable error. Interactive runs still prompt.
1929
+ // The harness identity scopes any cached grant. It no longer lets the
1930
+ // requesting agent wait for interactive approvalan agent launch
1931
+ // resolves broker-only and fails fast naming
1932
+ // `agents secrets unlock <bundle>` (bundles.ts:interactiveUnlock).
1867
1933
  agentOnly: isHeadlessSecretsContext(),
1868
1934
  });
1869
1935
  const entries = describeBundle(bundle);
@@ -1923,6 +1989,10 @@ export function registerRunCommand(program) {
1923
1989
  toolsRestrict: workflowToolsRestrict,
1924
1990
  mcpConfigPath: workflowMcpConfigPath,
1925
1991
  passthroughArgs,
1992
+ // Set only on the REMOTE side of a `--host` dispatch (the launcher
1993
+ // forwards `--emit-session-id`): print the resolved session id as a
1994
+ // stdout sentinel so the launcher captures the id this run coined.
1995
+ emitSessionId: options.emitSessionId === true,
1926
1996
  };
1927
1997
  if (options.interactive && options.headless) {
1928
1998
  console.error(chalk.red('--interactive and --headless are mutually exclusive. Pass one, or neither (mode is inferred from prompt presence).'));
@@ -1,6 +1,7 @@
1
1
  /**
2
- * `agents feed` -- list open blocks (decisions agents are waiting on).
2
+ * `agents feed` -- operator inbox + agent status posts.
3
3
  *
4
+ * Default (`agents feed`): list open blocks (decisions agents are waiting on).
4
5
  * Aggregates block records from the local feed store and, with --host, from
5
6
  * reachable remote hosts via SSH passthrough. Each block carries enough
6
7
  * identity for `agents message` to route a reply back to the right agent.
@@ -8,6 +9,11 @@
8
9
  * Default view groups by **outcome** (ticket / PR / worktree / Unassigned) so
9
10
  * an operator sees dozens of deliverables, not ~1,100 agents. Pass `--flat`
10
11
  * for the legacy per-agent list.
12
+ *
13
+ * `agents feed post`: agent-callable free-text progress into the activity
14
+ * stream (milestone `status.posted`). Session/agent/host/runtime/pid identity
15
+ * is auto-stamped from env + the pid registry — no domain-specific flags.
16
+ * Humans watch via the feed activity lane / `agents activity`.
11
17
  */
12
18
  import type { Command } from 'commander';
13
19
  import { type OpenBlock } from '../lib/feed.js';
@@ -1,6 +1,7 @@
1
1
  import chalk from 'chalk';
2
2
  import { ensureFeedPublishHook, listAskStats, listBlocks, recordNotified } from '../lib/feed.js';
3
3
  import { ensureActivityLogHook, readRecentActivity, formatActivityLine } from '../lib/activity.js';
4
+ import { postFeedStatus } from '../lib/feed-post.js';
4
5
  import { enrichBlocksFromSessions, groupBlocksByOutcome, isUnambiguousOutcomeAnswer, openBlocksForOutcome, stampBlockOutcomes, } from '../lib/feed-outcome.js';
5
6
  import { classifyBlock, filterBlocksForFeed, suppressionDigest, } from '../lib/ask-classifier.js';
6
7
  import { machineId, normalizeHost } from '../lib/machine-id.js';
@@ -214,9 +215,9 @@ export function sessionHintsFromActive(sessions) {
214
215
  }));
215
216
  }
216
217
  export function registerFeedCommand(program) {
217
- program
218
+ const feed = program
218
219
  .command('feed')
219
- .description('List open blocks -- decisions agents are waiting on (grouped by outcome)')
220
+ .description('Open blocks (needs you) + agent status posts (feed post)')
220
221
  .option('--json', 'Output as JSON (each block stamped with its outcome + ask class)')
221
222
  .option('--flat', 'List one block per agent instead of grouping by outcome')
222
223
  .option('--all', 'Include stalls/FYIs that policy would suppress (default: hide them)')
@@ -225,8 +226,50 @@ export function registerFeedCommand(program) {
225
226
  .option('--device <target...>', 'Alias for --host; repeatable')
226
227
  .option('--dispatch', 'Run stall suppression + default-on-no-answer policy and urgent notifications')
227
228
  .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')
229
- .action(async (opts) => {
229
+ .option('--kill <id>', 'Kill a runaway/needy local process (SIGTERM) or cancel a cloud task');
230
+ feed
231
+ .command('post')
232
+ .description('Post a status update to the fleet activity stream (for agents)')
233
+ .argument('<text...>', 'What just happened — one short human line')
234
+ .option('--session <id>', 'Session id escape hatch (default: auto from env / pid registry)')
235
+ .option('--json', 'Emit the written event as JSON')
236
+ .addHelpText('after', `
237
+ Examples:
238
+ # Inside an agents-cli run (session identity is already in the env):
239
+ agents feed post "CHANGELOG pushed; watching CI and mac-mini E2E"
240
+ agents feed post "ready for review" --json
241
+
242
+ # Outside a run, pass the session explicitly:
243
+ agents feed post "manual note" --session 00998b0e-2d15-4d2f-a58b-974a886c9b47
244
+
245
+ Identity (session, agent, host, runtime, pid, launchId) is stamped automatically.
246
+ Domain facts (tickets, PRs) are not CLI flags — join them on the session at read time.
247
+ `)
248
+ .action((textParts, opts, cmd) => {
249
+ // Parent `feed` also declares `--json` (for the list view). Commander
250
+ // binds the flag on the parent, so a `feed post … --json` lands on
251
+ // parent.opts().json — not the child. Read both.
252
+ const flags = {
253
+ session: opts?.session ?? cmd?.opts?.()?.session,
254
+ json: Boolean(opts?.json ?? cmd?.opts?.()?.json ?? cmd?.parent?.opts?.()?.json),
255
+ };
256
+ try {
257
+ const { event } = postFeedStatus({
258
+ text: Array.isArray(textParts) ? textParts.join(' ') : String(textParts ?? ''),
259
+ sessionId: flags.session,
260
+ });
261
+ if (flags.json) {
262
+ console.log(JSON.stringify(event, null, 2));
263
+ return;
264
+ }
265
+ console.log(formatActivityLine(event, { showHost: true }).trimStart());
266
+ }
267
+ catch (err) {
268
+ console.error(chalk.red(err.message));
269
+ process.exitCode = 1;
270
+ }
271
+ });
272
+ feed.action(async (opts) => {
230
273
  if (opts.device?.length)
231
274
  opts.host = [...(opts.host ?? []), ...opts.device];
232
275
  const self = machineId();
@@ -17,8 +17,15 @@ import type { Command } from 'commander';
17
17
  import { type ActiveSession } from '../lib/session/active.js';
18
18
  import type { SessionMeta } from '../lib/session/types.js';
19
19
  export declare function registerGoCommand(program: Command): void;
20
- /** Live jump targets (local + remote), keyed by session id. Cloud excluded (no pid). */
21
- export declare function gatherLiveTargets(local: boolean): Promise<{
20
+ /**
21
+ * Live jump targets (local + remote), keyed by session id. Cloud is excluded by
22
+ * default (it has no local pid to attach), but `detach` opts in with
23
+ * `includeCloud` so it can resolve a cloud id and refuse it with a clear message
24
+ * instead of a bare "no live session".
25
+ */
26
+ export declare function gatherLiveTargets(local: boolean, opts?: {
27
+ includeCloud?: boolean;
28
+ }): Promise<{
22
29
  self: string;
23
30
  activeById: Map<string, ActiveSession>;
24
31
  }>;
@@ -20,6 +20,7 @@ import { promisify } from 'util';
20
20
  import { getActiveSessions, findSessionFileForKind } from '../lib/session/active.js';
21
21
  import { gatherRemoteActive } from '../lib/session/remote-active.js';
22
22
  import { discoverSessions } from '../lib/session/discover.js';
23
+ import { deriveShortId } from '../lib/session/short-id.js';
23
24
  import { dedupeByMachineSession, mergeLocalFirst, pickSessionInteractive } from './sessions.js';
24
25
  import { focusAction } from './focus.js';
25
26
  import { machineId } from '../lib/session/sync/config.js';
@@ -39,8 +40,13 @@ export function registerGoCommand(program) {
39
40
  await focusAction(id, { local: opts.local, attachOnly: true });
40
41
  });
41
42
  }
42
- /** Live jump targets (local + remote), keyed by session id. Cloud excluded (no pid). */
43
- export async function gatherLiveTargets(local) {
43
+ /**
44
+ * Live jump targets (local + remote), keyed by session id. Cloud is excluded by
45
+ * default (it has no local pid to attach), but `detach` opts in with
46
+ * `includeCloud` so it can resolve a cloud id and refuse it with a clear message
47
+ * instead of a bare "no live session".
48
+ */
49
+ export async function gatherLiveTargets(local, opts = {}) {
44
50
  const self = machineId();
45
51
  const localActive = await getActiveSessions();
46
52
  for (const s of localActive)
@@ -55,9 +61,13 @@ export async function gatherLiveTargets(local) {
55
61
  catch { /* remote sweep is best-effort */ }
56
62
  }
57
63
  const activeById = new Map();
58
- for (const s of active)
59
- if (s.context !== 'cloud' && s.sessionId)
60
- activeById.set(s.sessionId, s);
64
+ for (const s of active) {
65
+ if (!s.sessionId)
66
+ continue;
67
+ if (s.context === 'cloud' && !opts.includeCloud)
68
+ continue;
69
+ activeById.set(s.sessionId, s);
70
+ }
61
71
  return { self, activeById };
62
72
  }
63
73
  /** Interactive pick over the live sessions' rich SessionMeta; returns the chosen live session. */
@@ -99,7 +109,7 @@ function synthMeta(s, self) {
99
109
  const filePath = remote ? '' : (findSessionFileForKind(s.kind, s.cwd, s.sessionId) ?? '');
100
110
  return {
101
111
  id: s.sessionId,
102
- shortId: s.sessionId.slice(0, 8),
112
+ shortId: deriveShortId(s.sessionId),
103
113
  agent: s.kind,
104
114
  timestamp: new Date(s.startedAtMs ?? Date.now()).toISOString(),
105
115
  filePath,
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Custom harness commands (`agents harness`).
3
+ *
4
+ * A "custom harness" is a named (host CLI + model) combo — e.g. OpenCode pinned
5
+ * to meta/muse-spark-1.1, called `spark`. It runs like a native agent type
6
+ * (`agents run spark`) and syncs across devices via `agents repo push user`.
7
+ *
8
+ * Mechanism: harnesses ARE profiles (same ~/.agents/profiles/*.yml, same run
9
+ * resolution). This command group is the harness-flavored surface over that
10
+ * layer + a discovery `list` spanning custom harnesses, addable presets, and the
11
+ * native harness registry. The `agents profiles` tree stays unchanged.
12
+ */
13
+ import type { Command } from 'commander';
14
+ export declare function registerHarnessCommands(program: Command): void;
@@ -0,0 +1,145 @@
1
+ /**
2
+ * Custom harness commands (`agents harness`).
3
+ *
4
+ * A "custom harness" is a named (host CLI + model) combo — e.g. OpenCode pinned
5
+ * to meta/muse-spark-1.1, called `spark`. It runs like a native agent type
6
+ * (`agents run spark`) and syncs across devices via `agents repo push user`.
7
+ *
8
+ * Mechanism: harnesses ARE profiles (same ~/.agents/profiles/*.yml, same run
9
+ * resolution). This command group is the harness-flavored surface over that
10
+ * layer + a discovery `list` spanning custom harnesses, addable presets, and the
11
+ * native harness registry. The `agents profiles` tree stays unchanged.
12
+ */
13
+ import chalk from 'chalk';
14
+ import { addProfile } from './profiles.js';
15
+ import { listProfiles, readProfile, deleteProfile, profileHostLabel, profileProviderLabel, profileModelLabel, profileAuthLabel, getProfilePath, } from '../lib/profiles.js';
16
+ import { listPresets } from '../lib/profiles-presets.js';
17
+ import { AGENTS, ALL_AGENT_IDS } from '../lib/agents.js';
18
+ /** Short capability summary for a native harness — its supported run modes. */
19
+ function nativeModes(id) {
20
+ const modes = AGENTS[id]?.capabilities?.modes ?? [];
21
+ return modes.length ? modes.join('/') : '-';
22
+ }
23
+ export function registerHarnessCommands(program) {
24
+ const cmd = program
25
+ .command('harness')
26
+ .alias('harnesses')
27
+ .description('Custom harnesses — name a (host CLI + model) combo and run it like a native agent type.')
28
+ .addHelpText('after', `
29
+ A custom harness pins a host CLI (opencode, claude, codex, grok, gemini, ...) to a
30
+ model and gives it a name. 'agents run <name>' then behaves like a native agent
31
+ type, and 'agents repo push user' syncs it to every device.
32
+
33
+ Examples:
34
+ # Meta Muse Spark 1.1 through OpenCode, called 'spark'
35
+ agents harness add spark --host opencode --model meta/muse-spark-1.1
36
+ agents run spark "refactor api/handlers/checkout.py"
37
+
38
+ # Per-run model override still wins
39
+ agents run spark --model opencode/big-pickle "quick pass"
40
+
41
+ # See custom harnesses, addable presets, and native harnesses
42
+ agents harness list
43
+
44
+ # Private OpenAI/Anthropic-compatible endpoint (stores a keychain key)
45
+ agents harness add corp --host claude --model gpt-x --base-url https://gw.corp/v1 --auth-provider corp
46
+
47
+ # Custom harnesses are just profiles — this also works via 'agents profiles'
48
+ `);
49
+ cmd
50
+ .command('add <name>')
51
+ .description('Create a custom harness from a host + model (or apply a built-in preset).')
52
+ .option('--host <agent>', 'Host CLI to run under (opencode, claude, codex, grok, gemini, ...) — pair with --model')
53
+ .option('--model <id>', 'Model id to pin on the host (e.g., meta/muse-spark-1.1) — pair with --host')
54
+ .option('--base-url <url>', 'Custom endpoint base URL (claude/codex hosts)')
55
+ .option('--auth-provider <provider>', 'Attach a keychain-backed API key under this provider (for private endpoints)')
56
+ .option('--preset <preset>', 'Apply a built-in preset instead of --host/--model')
57
+ .option('--version <version>', 'Pin the host CLI version (e.g., 1.16.0)')
58
+ .option('--key-stdin', 'Read API key from stdin instead of prompting (for scripts/CI)')
59
+ .option('--force', 'Overwrite an existing harness with the same name')
60
+ .action(async (name, opts) => {
61
+ try {
62
+ await addProfile(name, opts, 'Harness');
63
+ }
64
+ catch (err) {
65
+ console.error(chalk.red(err.message));
66
+ process.exit(1);
67
+ }
68
+ });
69
+ cmd
70
+ .command('list')
71
+ .alias('ls')
72
+ .description('List custom harnesses, addable presets, and native harnesses.')
73
+ .option('--json', 'Emit JSON instead of a table')
74
+ .action((opts) => {
75
+ const custom = listProfiles();
76
+ const presets = listPresets();
77
+ const native = ALL_AGENT_IDS.map((id) => ({ id, name: AGENTS[id].name, modes: nativeModes(id) }));
78
+ if (opts.json) {
79
+ console.log(JSON.stringify({
80
+ custom: custom.map((p) => ({
81
+ name: p.name,
82
+ host: p.host.agent,
83
+ model: profileModelLabel(p),
84
+ provider: profileProviderLabel(p),
85
+ })),
86
+ presets: presets.map((p) => ({ name: p.name, provider: p.provider, description: p.description })),
87
+ native,
88
+ }, null, 2));
89
+ return;
90
+ }
91
+ console.log(chalk.bold('Custom harnesses') + chalk.gray(' (yours — agents run <name>)'));
92
+ if (custom.length === 0) {
93
+ console.log(chalk.gray(' none yet — try: agents harness add spark --host opencode --model meta/muse-spark-1.1'));
94
+ }
95
+ else {
96
+ for (const p of custom) {
97
+ console.log(` ${chalk.cyan(p.name.padEnd(16))} ${profileHostLabel(p).padEnd(14)} ${chalk.gray(profileModelLabel(p))}`);
98
+ }
99
+ }
100
+ console.log('');
101
+ console.log(chalk.bold('Presets') + chalk.gray(' (addable — agents harness add <name>)'));
102
+ for (const p of presets) {
103
+ console.log(` ${chalk.cyan(p.name.padEnd(16))} ${p.provider.padEnd(12)} ${chalk.gray(p.description.slice(0, 70))}`);
104
+ }
105
+ console.log('');
106
+ console.log(chalk.bold('Native harnesses') + chalk.gray(' (built-in — agents run <id>)'));
107
+ for (const n of native) {
108
+ console.log(` ${chalk.cyan(n.id.padEnd(16))} ${n.name.padEnd(14)} ${chalk.gray('modes: ' + n.modes)}`);
109
+ }
110
+ });
111
+ cmd
112
+ .command('view <name>')
113
+ .alias('show')
114
+ .description('Show one custom harness (host, model, provider, auth, path).')
115
+ .action((name) => {
116
+ try {
117
+ const p = readProfile(name);
118
+ console.log(chalk.bold(p.name));
119
+ if (p.description)
120
+ console.log(chalk.gray(p.description));
121
+ console.log('');
122
+ console.log(`Host: ${profileHostLabel(p)}`);
123
+ console.log(`Model: ${profileModelLabel(p)}`);
124
+ console.log(`Provider: ${profileProviderLabel(p)}`);
125
+ console.log(`Auth: ${profileAuthLabel(p)}`);
126
+ console.log(chalk.gray(getProfilePath(p.name)));
127
+ }
128
+ catch (err) {
129
+ console.error(chalk.red(err.message));
130
+ process.exit(1);
131
+ }
132
+ });
133
+ cmd
134
+ .command('remove <name>')
135
+ .alias('rm')
136
+ .description('Delete a custom harness (keychain token is kept).')
137
+ .action((name) => {
138
+ const existed = deleteProfile(name);
139
+ if (!existed) {
140
+ console.error(chalk.red(`Harness '${name}' not found.`));
141
+ process.exit(1);
142
+ }
143
+ console.log(chalk.green(`Harness '${name}' removed.`));
144
+ });
145
+ }
@@ -134,7 +134,7 @@ async function runImport(agentArg, opts) {
134
134
  }
135
135
  }
136
136
  }
137
- let version = opts.version;
137
+ let version = opts.as;
138
138
  if (!version) {
139
139
  if (!useDirectBinaryImport && globalPath) {
140
140
  try {
@@ -157,7 +157,7 @@ async function runImport(agentArg, opts) {
157
157
  }
158
158
  if (!version) {
159
159
  console.error(chalk.red(`Could not determine version for ${agentLabel(agentId)}.`));
160
- console.error(chalk.gray('Pass --version <version> explicitly.'));
160
+ console.error(chalk.gray('Pass --as <version> explicitly.'));
161
161
  process.exit(1);
162
162
  }
163
163
  if (!isValidImportVersion(version)) {
@@ -167,7 +167,7 @@ async function runImport(agentArg, opts) {
167
167
  }
168
168
  const versionDir = getVersionDir(agentId, version);
169
169
  const fromLabel = useDirectBinaryImport ? installScriptBinary : globalPath;
170
- console.log(chalk.bold(`\nImport ${agentLabel(agentId)} v${version}`));
170
+ console.log(chalk.bold(`\nImport ${agentLabel(agentId)} v${version}${opts.isolated ? ' (isolated copy)' : ''}`));
171
171
  console.log(` from: ${chalk.gray(fromLabel)}`);
172
172
  console.log(` into: ${chalk.gray(versionDir)}`);
173
173
  const configDirExists = fs.existsSync(agent.configDir);
@@ -178,6 +178,16 @@ async function runImport(agentArg, opts) {
178
178
  configAlreadyManaged = true;
179
179
  console.log(` config: ${chalk.gray(`${agent.configDir} (already managed — will skip)`)}`);
180
180
  }
181
+ else if (opts.isolated) {
182
+ // This summary is printed BEFORE the branch that does the work, so it has to
183
+ // describe the mode it is actually about to run. Saying "will be moved" under
184
+ // --isolated announced the exact adoption the flag exists to prevent — a
185
+ // confirmation prompt that misdescribes the operation is worse than none.
186
+ console.log(` config: ${chalk.gray(`${agent.configDir} (will be COPIED — your original stays put)`)}`);
187
+ if (!opts.withAuth) {
188
+ console.log(` ${chalk.gray('credentials are skipped; pass --with-auth to include them')}`);
189
+ }
190
+ }
181
191
  else {
182
192
  console.log(` config: ${chalk.gray(`${agent.configDir} (will be moved into version home)`)}`);
183
193
  }
@@ -188,7 +198,9 @@ async function runImport(agentArg, opts) {
188
198
  if (!opts.yes && isInteractiveTerminal()) {
189
199
  console.log();
190
200
  const proceed = await confirm({
191
- message: `Import ${agentLabel(agentId)} v${version} into agents-cli?`,
201
+ message: opts.isolated
202
+ ? `Import ${agentLabel(agentId)} v${version} as an isolated copy?`
203
+ : `Import ${agentLabel(agentId)} v${version} into agents-cli?`,
192
204
  default: true,
193
205
  }).catch((err) => {
194
206
  if (isPromptCancelled(err))
@@ -205,17 +217,23 @@ async function runImport(agentArg, opts) {
205
217
  // want a stranded symlink farm. Binary registration is cheap and reversible
206
218
  // — if it fails after config, the next `agents import` call retries cleanly.
207
219
  const willImportConfig = configDirExists && !configAlreadyManaged && !opts.isolated;
208
- if (opts.isolated && configDirExists) {
220
+ if (opts.isolated && configDirExists && configAlreadyManaged) {
221
+ console.log(chalk.gray(` Skipping config copy: ${agent.configDir} is a managed symlink, not your real settings.`));
222
+ }
223
+ else if (opts.isolated && configDirExists) {
209
224
  // COPY the user's settings in; never move, never symlink. The original stays
210
225
  // exactly where it is — that is what separates this from adoption.
211
226
  const seedSpinner = ora(`Copying ${agent.configDir} into the isolated copy...`).start();
212
- const seed = seedIsolatedConfigFromLocal(agentId, version, { withAuth: opts.withAuth });
227
+ const seed = seedIsolatedConfigFromLocal(agentId, version, { withAuth: opts.withAuth, all: opts.all });
213
228
  if (seed.error) {
214
229
  seedSpinner.fail(`Config: ${seed.error}`);
215
230
  process.exit(1);
216
231
  }
217
232
  else if (seed.seeded) {
218
233
  seedSpinner.succeed(`Settings copied (${seed.from} -> ${seed.to}); your original is untouched`);
234
+ if (seed.skippedRuntime.length > 0) {
235
+ console.log(chalk.gray(` Runtime state NOT copied: ${seed.skippedRuntime.join(', ')} (regenerated as you use it; --all to include)`));
236
+ }
219
237
  if (seed.skippedAuth.length > 0) {
220
238
  console.log(chalk.gray(` Credentials NOT copied: ${seed.skippedAuth.join(', ')}`));
221
239
  console.log(chalk.gray(' The copy signs in separately. Use --with-auth to copy them too.'));
@@ -288,15 +306,22 @@ export function registerImportCommand(program) {
288
306
  .command('import')
289
307
  .argument('<agent>', 'Agent id (e.g. openclaw, claude, codex)')
290
308
  .description('Import an existing unmanaged agent install into agents-cli')
291
- .option('--version <version>', 'Pin a version label (otherwise read from package.json)')
309
+ // NOT `--version`. The program declares `.version(VERSION)` (src/index.ts), which
310
+ // claims `-V, --version` globally and wins over a subcommand option of the same
311
+ // name — so `agents import codex --version 1.2.3` printed the CLI's own version
312
+ // and exited without importing anything. The flag had been unreachable since it
313
+ // was introduced, and the "could not determine version" error even advised using
314
+ // it. Renamed so it actually reaches the command.
315
+ .option('--as <version>', 'Version label to import as (otherwise read from package.json)')
292
316
  .option('--from-path <path>', 'Path to the npm package dir (otherwise auto-detected from PATH)')
293
317
  .option('--isolated', 'Copy the install into a self-contained isolated version instead of adopting it')
294
318
  .option('--with-auth', 'With --isolated, also copy credentials into the sandbox (skipped by default)')
319
+ .option('--all', 'With --isolated, also copy session history, logs and caches (skipped by default)')
295
320
  .option('-y, --yes', 'Skip the confirmation prompt')
296
321
  .addHelpText('after', `
297
322
  Examples:
298
323
  $ agents import openclaw Auto-detect via PATH
299
- $ agents import openclaw --version 2026.3.8 Pin a version label
324
+ $ agents import openclaw --as 2026.3.8 Pin a version label
300
325
  $ agents import openclaw --from-path /opt/homebrew/lib/node_modules/openclaw
301
326
 
302
327
  # installScript-based agents (curl/brew installers, no npm package):
@@ -305,6 +330,11 @@ Examples:
305
330
  $ agents import cursor Adopt ~/.local/bin/cursor-agent
306
331
  $ agents import antigravity --from-path ~/.local/bin/agy
307
332
 
333
+ # Copy your setup into a sandbox instead of adopting it:
334
+ $ agents import codex --isolated New isolated copy at the local version
335
+ $ agents import codex --isolated --as 0.146.0 Re-seed an EXISTING isolated copy
336
+ $ agents import codex --isolated --with-auth ...and share credentials with it
337
+
308
338
  When to use:
309
339
  When an agent CLI is already installed globally and you want to bring it
310
340
  under agents-cli management without reinstalling. Creates a symlink farm