@phnx-labs/agents-cli 1.20.74 → 1.20.76

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (199) hide show
  1. package/CHANGELOG.md +893 -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 +31 -3
  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 +15 -2
  26. package/dist/commands/secrets.js +44 -21
  27. package/dist/commands/sessions-browser.js +1 -1
  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.js +16 -1
  32. package/dist/commands/sessions.d.ts +36 -13
  33. package/dist/commands/sessions.js +210 -40
  34. package/dist/commands/setup-computer.js +2 -2
  35. package/dist/commands/ssh.js +71 -35
  36. package/dist/commands/status.js +10 -2
  37. package/dist/commands/versions.js +15 -6
  38. package/dist/commands/view.d.ts +5 -0
  39. package/dist/commands/view.js +24 -4
  40. package/dist/commands/watchdog.d.ts +7 -2
  41. package/dist/commands/watchdog.js +73 -57
  42. package/dist/index.js +58 -7
  43. package/dist/lib/activity.d.ts +14 -4
  44. package/dist/lib/activity.js +17 -5
  45. package/dist/lib/actor.d.ts +50 -0
  46. package/dist/lib/actor.js +166 -0
  47. package/dist/lib/agent-spec/provider.js +2 -1
  48. package/dist/lib/agent-spec/resolve.js +19 -5
  49. package/dist/lib/agent-spec/types.d.ts +9 -1
  50. package/dist/lib/agents.d.ts +21 -0
  51. package/dist/lib/agents.js +70 -6
  52. package/dist/lib/cloud/codex.d.ts +2 -0
  53. package/dist/lib/cloud/codex.js +14 -3
  54. package/dist/lib/cloud/session-index.d.ts +32 -0
  55. package/dist/lib/cloud/session-index.js +58 -0
  56. package/dist/lib/cloud/store.d.ts +7 -0
  57. package/dist/lib/cloud/store.js +25 -0
  58. package/dist/lib/config-transfer.js +4 -0
  59. package/dist/lib/daemon.js +18 -2
  60. package/dist/lib/devices/connect.d.ts +15 -1
  61. package/dist/lib/devices/connect.js +15 -1
  62. package/dist/lib/devices/fleet.d.ts +21 -0
  63. package/dist/lib/devices/fleet.js +26 -0
  64. package/dist/lib/devices/health-report.d.ts +38 -0
  65. package/dist/lib/devices/health-report.js +214 -0
  66. package/dist/lib/devices/health.js +4 -1
  67. package/dist/lib/devices/reachability.d.ts +31 -0
  68. package/dist/lib/devices/reachability.js +40 -0
  69. package/dist/lib/devices/registry.d.ts +33 -0
  70. package/dist/lib/devices/registry.js +37 -0
  71. package/dist/lib/devices/resolve-target.d.ts +15 -27
  72. package/dist/lib/devices/resolve-target.js +63 -102
  73. package/dist/lib/devices/sync.d.ts +18 -0
  74. package/dist/lib/devices/sync.js +23 -1
  75. package/dist/lib/devices/tailscale.d.ts +3 -0
  76. package/dist/lib/devices/tailscale.js +1 -0
  77. package/dist/lib/events.d.ts +1 -1
  78. package/dist/lib/exec.d.ts +66 -0
  79. package/dist/lib/exec.js +195 -6
  80. package/dist/lib/feed-post.d.ts +63 -0
  81. package/dist/lib/feed-post.js +204 -0
  82. package/dist/lib/fleet/apply.d.ts +32 -2
  83. package/dist/lib/fleet/apply.js +97 -10
  84. package/dist/lib/fleet/types.d.ts +11 -0
  85. package/dist/lib/fs-walk.d.ts +13 -0
  86. package/dist/lib/fs-walk.js +16 -7
  87. package/dist/lib/hooks.d.ts +1 -0
  88. package/dist/lib/hooks.js +156 -0
  89. package/dist/lib/hosts/dispatch.d.ts +7 -0
  90. package/dist/lib/hosts/dispatch.js +2 -0
  91. package/dist/lib/hosts/passthrough.js +2 -0
  92. package/dist/lib/hosts/reconnect.d.ts +78 -0
  93. package/dist/lib/hosts/reconnect.js +127 -0
  94. package/dist/lib/hosts/registry.d.ts +75 -14
  95. package/dist/lib/hosts/registry.js +205 -30
  96. package/dist/lib/hosts/remote-cmd.js +1 -0
  97. package/dist/lib/hosts/run-target.d.ts +13 -5
  98. package/dist/lib/hosts/run-target.js +26 -10
  99. package/dist/lib/hosts/session-index.d.ts +18 -1
  100. package/dist/lib/hosts/session-index.js +37 -2
  101. package/dist/lib/hosts/session-marker.d.ts +33 -0
  102. package/dist/lib/hosts/session-marker.js +51 -0
  103. package/dist/lib/hosts/tasks.d.ts +8 -4
  104. package/dist/lib/import.d.ts +2 -0
  105. package/dist/lib/import.js +35 -2
  106. package/dist/lib/menubar/install-menubar.d.ts +20 -0
  107. package/dist/lib/menubar/install-menubar.js +45 -1
  108. package/dist/lib/migrate.d.ts +16 -0
  109. package/dist/lib/migrate.js +36 -0
  110. package/dist/lib/models.d.ts +27 -0
  111. package/dist/lib/models.js +54 -1
  112. package/dist/lib/overdue.d.ts +7 -2
  113. package/dist/lib/overdue.js +18 -11
  114. package/dist/lib/picker.js +4 -1
  115. package/dist/lib/platform/process.d.ts +17 -0
  116. package/dist/lib/platform/process.js +70 -0
  117. package/dist/lib/profiles-presets.js +9 -7
  118. package/dist/lib/profiles.d.ts +31 -0
  119. package/dist/lib/profiles.js +70 -0
  120. package/dist/lib/pty-server.d.ts +2 -10
  121. package/dist/lib/pty-server.js +4 -38
  122. package/dist/lib/registry.d.ts +1 -1
  123. package/dist/lib/registry.js +48 -8
  124. package/dist/lib/rotate.d.ts +18 -0
  125. package/dist/lib/rotate.js +28 -0
  126. package/dist/lib/routines.d.ts +26 -0
  127. package/dist/lib/routines.js +19 -2
  128. package/dist/lib/runner.js +104 -7
  129. package/dist/lib/secrets/agent.d.ts +66 -1
  130. package/dist/lib/secrets/agent.js +154 -90
  131. package/dist/lib/secrets/bundles.d.ts +34 -0
  132. package/dist/lib/secrets/bundles.js +179 -19
  133. package/dist/lib/secrets/index.d.ts +15 -1
  134. package/dist/lib/secrets/index.js +21 -3
  135. package/dist/lib/secrets/rc-hygiene.d.ts +55 -0
  136. package/dist/lib/secrets/rc-hygiene.js +154 -0
  137. package/dist/lib/secrets/remote.d.ts +7 -4
  138. package/dist/lib/secrets/remote.js +7 -4
  139. package/dist/lib/secrets/sync-commands.d.ts +21 -0
  140. package/dist/lib/secrets/sync-commands.js +21 -0
  141. package/dist/lib/self-update.d.ts +23 -2
  142. package/dist/lib/self-update.js +86 -5
  143. package/dist/lib/session/active.d.ts +114 -7
  144. package/dist/lib/session/active.js +323 -52
  145. package/dist/lib/session/cloud.js +2 -1
  146. package/dist/lib/session/db.d.ts +48 -0
  147. package/dist/lib/session/db.js +238 -14
  148. package/dist/lib/session/detached.d.ts +30 -0
  149. package/dist/lib/session/detached.js +92 -0
  150. package/dist/lib/session/discover.d.ts +465 -2
  151. package/dist/lib/session/discover.js +1648 -468
  152. package/dist/lib/session/fork.js +2 -1
  153. package/dist/lib/session/hook-sessions.d.ts +43 -0
  154. package/dist/lib/session/hook-sessions.js +135 -0
  155. package/dist/lib/session/linear.d.ts +6 -0
  156. package/dist/lib/session/linear.js +55 -0
  157. package/dist/lib/session/migrate-targets.d.ts +65 -0
  158. package/dist/lib/session/migrate-targets.js +94 -0
  159. package/dist/lib/session/migrations.d.ts +37 -0
  160. package/dist/lib/session/migrations.js +60 -0
  161. package/dist/lib/session/parse.d.ts +18 -0
  162. package/dist/lib/session/parse.js +130 -30
  163. package/dist/lib/session/pid-registry.d.ts +34 -2
  164. package/dist/lib/session/pid-registry.js +49 -2
  165. package/dist/lib/session/render.d.ts +7 -2
  166. package/dist/lib/session/render.js +22 -10
  167. package/dist/lib/session/short-id.d.ts +17 -0
  168. package/dist/lib/session/short-id.js +20 -0
  169. package/dist/lib/session/state.js +6 -2
  170. package/dist/lib/shims.d.ts +1 -1
  171. package/dist/lib/shims.js +20 -6
  172. package/dist/lib/sqlite.d.ts +3 -2
  173. package/dist/lib/sqlite.js +27 -4
  174. package/dist/lib/staleness/detectors/commands.js +1 -1
  175. package/dist/lib/staleness/detectors/workflows.js +13 -2
  176. package/dist/lib/staleness/writers/commands.js +7 -7
  177. package/dist/lib/staleness/writers/workflows.d.ts +4 -2
  178. package/dist/lib/startup/command-registry.d.ts +1 -0
  179. package/dist/lib/startup/command-registry.js +3 -0
  180. package/dist/lib/state.d.ts +1 -1
  181. package/dist/lib/state.js +1 -32
  182. package/dist/lib/teams/agents.d.ts +19 -10
  183. package/dist/lib/teams/agents.js +40 -39
  184. package/dist/lib/types.d.ts +37 -2
  185. package/dist/lib/usage.d.ts +33 -1
  186. package/dist/lib/usage.js +172 -12
  187. package/dist/lib/versions.d.ts +9 -2
  188. package/dist/lib/versions.js +37 -8
  189. package/dist/lib/watchdog/log.d.ts +43 -0
  190. package/dist/lib/watchdog/log.js +69 -0
  191. package/dist/lib/watchdog/routine.d.ts +44 -0
  192. package/dist/lib/watchdog/routine.js +69 -0
  193. package/dist/lib/watchdog/runner.d.ts +51 -7
  194. package/dist/lib/watchdog/runner.js +239 -64
  195. package/dist/lib/watchdog/watchdog.d.ts +1 -1
  196. package/dist/lib/watchdog/watchdog.js +31 -16
  197. package/dist/lib/workflows.d.ts +16 -0
  198. package/dist/lib/workflows.js +110 -1
  199. package/package.json +1 -1
@@ -783,18 +783,26 @@ export function registerRoutinesCommands(program) {
783
783
  try {
784
784
  const result = await executeJob(job);
785
785
  const logPath = `${getRunDir(name, result.meta.runId)}/stdout.log`;
786
+ const succeeded = result.meta.status === 'completed';
786
787
  if (options.json) {
787
788
  writeJson({
788
- ok: true,
789
+ ok: succeeded,
789
790
  job: name,
790
791
  logDir: getRunDir(name, result.meta.runId),
791
792
  ...runMetaJson(result.meta),
792
793
  logPath,
793
794
  reportPath: result.reportPath ?? null,
794
795
  });
796
+ // A failed run must exit non-zero so cron wrappers, `&&` chains, and
797
+ // `--json` consumers actually see the failure (a logged-out agent used
798
+ // to exit 0 with ok:true, hiding the whole auth-failure epidemic). Set
799
+ // exitCode rather than process.exit() so the JSON payload is fully
800
+ // flushed to a pipe before the process ends.
801
+ if (!succeeded)
802
+ process.exitCode = 1;
795
803
  return;
796
804
  }
797
- if (result.meta.status === 'completed') {
805
+ if (succeeded) {
798
806
  spinner.succeed(`Job completed (exit code: ${result.meta.exitCode})`);
799
807
  }
800
808
  else if (result.meta.status === 'timeout') {
@@ -805,10 +813,15 @@ export function registerRoutinesCommands(program) {
805
813
  }
806
814
  console.log(chalk.gray(` Run: ${result.meta.runId}`));
807
815
  console.log(chalk.gray(` Log: ${logPath}`));
816
+ if (result.meta.errorMessage) {
817
+ console.log(chalk.gray(` Reason: ${result.meta.errorMessage}`));
818
+ }
808
819
  if (result.reportPath) {
809
820
  console.log(chalk.bold('\nReport:\n'));
810
821
  console.log(fs.readFileSync(result.reportPath, 'utf-8'));
811
822
  }
823
+ if (!succeeded)
824
+ process.exitCode = 1;
812
825
  }
813
826
  catch (err) {
814
827
  if (options.json) {
@@ -15,10 +15,10 @@ import * as path from 'path';
15
15
  import { SSH_TARGET_RE, assertValidSshTarget, sshExec } from '../lib/ssh-exec.js';
16
16
  import { quoteWin32ExecArg, composeWin32CommandLine } from '../lib/platform/index.js';
17
17
  import { ensureDaemonStarted, isDaemonRunning } from '../lib/daemon.js';
18
- import { parseHostsOption, remoteResolveEnv, remoteSecretsRaw, remoteSecretsStream, resolveSshTarget, } from '../lib/secrets/remote.js';
18
+ import { parseHostsOption, remoteResolveEnv, remoteSecretsRaw, remoteSecretsStream, resolveHostSshTarget, } from '../lib/secrets/remote.js';
19
19
  import { remoteShellFor, buildWindowsStdinImportCommand } from '../lib/hosts/remote-cmd.js';
20
20
  import { resolveRemoteOsSync } from '../lib/hosts/remote-os.js';
21
- import { bundleExists, bundleItemStore, bundlePolicy, deleteBundle, describeBundle, keychainItemsForBundle, keychainRef, listBundles, migrateLegacyBundles, parseDotenv, readAndResolveBundleEnv, isHeadlessSecretsContext, readBundle, renameBundle, rotateBundleSecret, sanitizeProcessEnv, validateBundleName, validateEnvKey, validateExpiresFutureDated, validateSecretType, writeBundle, writeBundleWithItems, } from '../lib/secrets/bundles.js';
21
+ import { bundleExists, bundleItemStore, bundlePolicy, deleteBundle, describeBundle, keychainItemsForBundle, keychainRef, listBundles, migrateLegacyBundles, parseDotenv, readAndResolveBundleEnv, isHeadlessSecretsContext, readBundle, readBundleIfDecryptable, renameBundle, rotateBundleSecret, sanitizeProcessEnv, validateBundleName, validateEnvKey, validateExpiresFutureDated, validateSecretType, writeBundle, writeBundleWithItems, } from '../lib/secrets/bundles.js';
22
22
  import { encryptForFallback, decryptForFallback } from '../lib/secrets/filestore.js';
23
23
  import { getKeychainToken, getKeychainTokens, hasKeychainToken, secretsKeychainItem, setKeychainToken, } from '../lib/secrets/index.js';
24
24
  import { assertOpAvailable, createPasswordItem, deleteItemByTitle, extractSecrets, itemExistsByTitle, listItems, listVaults, } from '../lib/onepassword.js';
@@ -225,17 +225,29 @@ async function importFromICloud(bundleName, opts) {
225
225
  }
226
226
  }
227
227
  /**
228
- * Printed under a "bundle not found" failure: if the name matches a bundle
229
- * stranded in the iCloud Keychain (pre-device-local-cutover era), point at the
230
- * recovery command instead of leaving a dead end.
228
+ * Printed under a read failure: if the name matches a bundle stranded in the
229
+ * iCloud Keychain (pre-device-local-cutover era), point at the recovery command
230
+ * instead of leaving a dead end.
231
+ *
232
+ * `stillPresent` distinguishes the two failures, because they need different
233
+ * commands. A MISSING bundle imports straight from iCloud. A bundle that is on
234
+ * disk but undecryptable does not: `import` reads the existing bundle before
235
+ * writing into it, so it fails the same way the read just did. That one has to
236
+ * be deleted first — which is why naming the wrong command here bricked the
237
+ * name entirely.
231
238
  */
232
- function maybePrintSyncedHint(name) {
239
+ function maybePrintSyncedHint(name, stillPresent) {
233
240
  if (process.platform !== 'darwin')
234
241
  return;
235
242
  try {
236
- if (discoverSyncedBundles().some((c) => c.name === name)) {
237
- console.error(chalk.yellow(`A legacy iCloud Keychain copy of '${name}' exists. Recover it with: agents secrets import ${name} --from icloud`));
238
- }
243
+ if (!discoverSyncedBundles().some((c) => c.name === name))
244
+ return;
245
+ console.error(chalk.yellow(stillPresent
246
+ ? `A legacy iCloud Keychain copy of '${name}' exists, but the local copy is unreadable and ` +
247
+ `import writes into it. Delete the local copy first, then recover:\n` +
248
+ ` agents secrets delete ${name}\n` +
249
+ ` agents secrets import ${name} --from icloud`
250
+ : `A legacy iCloud Keychain copy of '${name}' exists. Recover it with: agents secrets import ${name} --from icloud`));
239
251
  }
240
252
  catch {
241
253
  // Hint only — never mask the original error.
@@ -383,12 +395,12 @@ async function browseRemote(targets, args, tty) {
383
395
  };
384
396
  if (tty) {
385
397
  for (const t of targets) {
386
- const target = await resolveSshTarget(t);
398
+ const target = await resolveHostSshTarget(t);
387
399
  render(t, remoteSecretsRaw(target, args, { tty: true, osLookupName: t }));
388
400
  }
389
401
  }
390
402
  else {
391
- const resolved = await Promise.all(targets.map(async (t) => ({ name: t, target: await resolveSshTarget(t) })));
403
+ const resolved = await Promise.all(targets.map(async (t) => ({ name: t, target: await resolveHostSshTarget(t) })));
392
404
  const results = resolved.map(({ name, target }) => remoteSecretsRaw(target, args, { osLookupName: name }));
393
405
  targets.forEach((t, i) => render(t, results[i]));
394
406
  }
@@ -871,7 +883,7 @@ export function registerSecretsCommands(program) {
871
883
  }
872
884
  catch (err) {
873
885
  console.error(chalk.red(err.message));
874
- maybePrintSyncedHint(resolvedName);
886
+ maybePrintSyncedHint(resolvedName, bundleExists(resolvedName));
875
887
  process.exit(1);
876
888
  }
877
889
  const entries = describeBundle(bundle);
@@ -1465,16 +1477,27 @@ Examples:
1465
1477
  .action(async (name, opts) => {
1466
1478
  try {
1467
1479
  const resolvedName = name ?? (await pickBundleName('delete'));
1468
- const bundle = readBundle(resolvedName);
1480
+ // An undecryptable bundle (lost/rotated passphrase) must still be
1481
+ // deletable: deletion is the ONLY way out of that state, and every other
1482
+ // verb — view, add, import — refuses to touch the name until it's gone.
1483
+ // Its key refs are unreadable, so its keychain items cannot be
1484
+ // enumerated for purging; say so rather than claiming a clean purge.
1485
+ const bundle = readBundleIfDecryptable(resolvedName);
1469
1486
  if (!opts.yes) {
1470
1487
  if (!isInteractiveTerminal()) {
1471
1488
  console.error(chalk.red(`Refusing to delete '${resolvedName}' without --yes in a non-interactive shell.`));
1472
1489
  process.exit(1);
1473
1490
  }
1474
- const keychainCount = describeBundle(bundle).filter((e) => e.kind === 'keychain').length;
1475
- const suffix = keychainCount && !opts.keepSecrets
1476
- ? ` and purge ${keychainCount} keychain item${keychainCount === 1 ? '' : 's'}`
1477
- : '';
1491
+ let suffix;
1492
+ if (!bundle) {
1493
+ suffix = ' (unreadable its keychain items cannot be enumerated and will be left in place)';
1494
+ }
1495
+ else {
1496
+ const keychainCount = describeBundle(bundle).filter((e) => e.kind === 'keychain').length;
1497
+ suffix = keychainCount && !opts.keepSecrets
1498
+ ? ` and purge ${keychainCount} keychain item${keychainCount === 1 ? '' : 's'}`
1499
+ : '';
1500
+ }
1478
1501
  const { confirm } = await import('@inquirer/prompts');
1479
1502
  const proceed = await confirm({
1480
1503
  message: `Delete bundle '${resolvedName}'${suffix}?`,
@@ -1485,7 +1508,7 @@ Examples:
1485
1508
  return;
1486
1509
  }
1487
1510
  }
1488
- if (!opts.keepSecrets) {
1511
+ if (bundle && !opts.keepSecrets) {
1489
1512
  const store = bundleItemStore(bundle.backend);
1490
1513
  for (const { item } of keychainItemsForBundle(bundle)) {
1491
1514
  store.delete(item);
@@ -1583,7 +1606,7 @@ Examples:
1583
1606
  }
1584
1607
  assertValidSshTarget(opts.host);
1585
1608
  const resolvedBundleName = bundleName ?? (await pickBundleName('import into'));
1586
- const target = await resolveSshTarget(opts.host);
1609
+ const target = await resolveHostSshTarget(opts.host);
1587
1610
  const env = await remoteResolveEnv(target, resolvedBundleName, { osLookupName: opts.host });
1588
1611
  const bundle = resolveImportBundle(resolvedBundleName, opts.backend, opts.synced);
1589
1612
  const { added, skipped } = applyEnvToBundle(bundle, env, opts);
@@ -1848,7 +1871,7 @@ Examples:
1848
1871
  // locally instead.
1849
1872
  const { assertRemoteBundleFlagsUnsupported } = await import('../lib/secrets/bundles.js');
1850
1873
  assertRemoteBundleFlagsUnsupported(bundleName, execOpts.host, { keys: keysSubset, allowExpired: execOpts.allowExpired }, { keysFlag: '--keys', allowExpiredFlag: '--allow-expired' });
1851
- secretEnv = await remoteResolveEnv(await resolveSshTarget(execOpts.host), bundleName, { osLookupName: execOpts.host });
1874
+ secretEnv = await remoteResolveEnv(await resolveHostSshTarget(execOpts.host), bundleName, { osLookupName: execOpts.host });
1852
1875
  }
1853
1876
  else {
1854
1877
  const { readAndResolveBundleEnv } = await import('../lib/secrets/bundles.js');
@@ -2052,7 +2075,7 @@ Examples:
2052
2075
  const unlockArgs = buildRemoteUnlockArgs(names, opts);
2053
2076
  let failures = 0;
2054
2077
  for (const h of hosts) {
2055
- const target = await resolveSshTarget(h);
2078
+ const target = await resolveHostSshTarget(h);
2056
2079
  // FOREGROUND stream (stdio inherited), NOT the piped remoteSecretsRaw:
2057
2080
  // the remote's passphrase prompt only surfaces if the remote process
2058
2081
  // sees a real TTY, which requires our local terminal to pass straight
@@ -214,7 +214,7 @@ function helpFor(_f, mode) {
214
214
  if (mode === 'search') {
215
215
  return 'type to filter · ↑↓ navigate · esc exit search · ⏎ resume';
216
216
  }
217
- return 's search · r running · c teams · a agent · d device · p project · w window · y copy-cmd · ⏎ resume · esc quit';
217
+ return 's search · r running · c teams · a agent · d device · p project · w window · tab preview · y copy-cmd · ⏎ resume · esc quit';
218
218
  }
219
219
  /**
220
220
  * Launch the interactive session browser. `initial` seeds the filter (e.g.
@@ -20,7 +20,8 @@
20
20
  import * as fs from 'fs';
21
21
  import * as path from 'path';
22
22
  import chalk from 'chalk';
23
- import { discoverSessions, resolveSessionById } from '../lib/session/discover.js';
23
+ import { discoverSessions, resolveSessionById, isCompleteSessionId } from '../lib/session/discover.js';
24
+ import { findSessionsById } from '../lib/session/db.js';
24
25
  import { filterSessionsByQuery, parseAgentFilter } from './sessions.js';
25
26
  import { listLocalTranscripts, SYNC_AGENTS } from '../lib/session/sync/agents.js';
26
27
  import { machineId } from '../lib/machine-id.js';
@@ -228,16 +229,29 @@ function selectSessions(metas, selectors) {
228
229
  const byId = [];
229
230
  const unmatched = [];
230
231
  for (const sel of selectors) {
231
- const hits = resolveSessionById(metas, sel);
232
- if (hits.length > 0)
233
- byId.push(...hits);
232
+ const trimmed = sel.trim();
233
+ // Same reason as resolveSessionQuery: the discovered pool is a minority of
234
+ // the index, so a complete id absent from it may still be indexed here.
235
+ const hits = resolveSessionById(metas, trimmed);
236
+ const resolved = hits.length > 0 || !isCompleteSessionId(trimmed) ? hits : findSessionsById(trimmed);
237
+ if (resolved.length > 0)
238
+ byId.push(...resolved);
234
239
  else
235
- unmatched.push(sel);
240
+ unmatched.push(trimmed);
236
241
  }
237
242
  if (byId.length > 0 && unmatched.length === 0) {
238
243
  const seen = new Set();
239
244
  return byId.filter(s => (seen.has(s.id) ? false : (seen.add(s.id), true)));
240
245
  }
246
+ // A selector that is a COMPLETE session id and still missed cannot be widened:
247
+ // the id is unique, so the text query below could only bundle sessions that
248
+ // merely mention it. Bundling those would ship unrelated transcripts to
249
+ // whoever receives the export, so select nothing and let the caller report it.
250
+ const missingIds = unmatched.filter(isCompleteSessionId);
251
+ if (missingIds.length > 0) {
252
+ process.stderr.write(chalk.red(`No session with id ${missingIds.join(', ')} on this machine.\n`));
253
+ return [];
254
+ }
241
255
  // Any selector that isn't an id → treat the whole thing as a text query.
242
256
  return filterSessionsByQuery(metas, selectors.join(' '));
243
257
  }
@@ -0,0 +1,29 @@
1
+ import type { Command } from 'commander';
2
+ import type { SessionMeta } from '../lib/session/types.js';
3
+ /** Agents whose sessions cannot be faithfully --resume'd (buildResumeCommand → null). */
4
+ type MigrateMode = 'resume' | 'rehydrate';
5
+ export declare function registerSessionsMigrateCommand(sessionsCmd: Command): void;
6
+ /**
7
+ * Harness-parity gate (pure, testable). `buildResumeCommand` returns null for the
8
+ * non-resumable agents (gemini, antigravity, openclaw, rush, hermes, grok, kimi,
9
+ * droid) — for those a faithful --resume is impossible, so a requested `resume`
10
+ * transparently becomes `rehydrate`. A resumable agent honors the request.
11
+ * Returns the effective mode plus whether it was downgraded, so the caller can
12
+ * print the notice.
13
+ */
14
+ export declare function effectiveMode(source: SessionMeta, requested: MigrateMode): {
15
+ mode: MigrateMode;
16
+ downgraded: boolean;
17
+ };
18
+ /**
19
+ * Rehydrate command: launch a fresh agent that reads the transcript. For a
20
+ * non-resumable agent there is no faithful --resume, so we start the agent with
21
+ * a prompt pointing it at the imported transcript path.
22
+ */
23
+ export declare function rehydrateCommand(source: SessionMeta): string[];
24
+ /**
25
+ * `agents sessions migrations` — the border tracker: the append-only ledger of
26
+ * every session handed off to/from another machine (RUSH-1977).
27
+ */
28
+ export declare function registerSessionsMigrationsCommand(sessionsCmd: Command): void;
29
+ export {};