@phnx-labs/agents-cli 1.22.31 → 1.22.32

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 (66) hide show
  1. package/CHANGELOG.md +66 -0
  2. package/README.md +8 -2
  3. package/dist/bin/agents +0 -0
  4. package/dist/commands/daemon.js +52 -12
  5. package/dist/commands/doctor.d.ts +19 -0
  6. package/dist/commands/doctor.js +119 -17
  7. package/dist/commands/routines.js +164 -36
  8. package/dist/commands/sessions.d.ts +1 -1
  9. package/dist/commands/sessions.js +44 -10
  10. package/dist/commands/update.d.ts +2 -0
  11. package/dist/commands/update.js +148 -0
  12. package/dist/index.js +3 -1
  13. package/dist/lib/catchup.js +4 -1
  14. package/dist/lib/daemon.d.ts +17 -0
  15. package/dist/lib/daemon.js +69 -3
  16. package/dist/lib/devices/doctor-findings.d.ts +7 -2
  17. package/dist/lib/devices/doctor-findings.js +53 -2
  18. package/dist/lib/devices/doctor-overview-cache.d.ts +7 -0
  19. package/dist/lib/devices/doctor-overview-cache.js +15 -0
  20. package/dist/lib/devices/fleet-divergence.d.ts +11 -0
  21. package/dist/lib/devices/fleet-divergence.js +6 -0
  22. package/dist/lib/devices/fleet-inventory.js +16 -2
  23. package/dist/lib/drift.d.ts +6 -1
  24. package/dist/lib/drift.js +9 -0
  25. package/dist/lib/hooks/cache.js +20 -1
  26. package/dist/lib/hooks.d.ts +91 -1
  27. package/dist/lib/hooks.js +289 -3
  28. package/dist/lib/hosts/passthrough.js +3 -0
  29. package/dist/lib/installations/index.d.ts +14 -0
  30. package/dist/lib/installations/index.js +14 -0
  31. package/dist/lib/installations/resolve.d.ts +43 -0
  32. package/dist/lib/installations/resolve.js +93 -0
  33. package/dist/lib/installations/store.d.ts +56 -0
  34. package/dist/lib/installations/store.js +196 -0
  35. package/dist/lib/installations/strategies.d.ts +73 -0
  36. package/dist/lib/installations/strategies.js +293 -0
  37. package/dist/lib/installations/types.d.ts +78 -0
  38. package/dist/lib/installations/types.js +8 -0
  39. package/dist/lib/installations/update.d.ts +40 -0
  40. package/dist/lib/installations/update.js +131 -0
  41. package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
  42. package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
  43. package/dist/lib/migrate.d.ts +27 -0
  44. package/dist/lib/migrate.js +112 -2
  45. package/dist/lib/routine-context.d.ts +144 -0
  46. package/dist/lib/routine-context.js +268 -0
  47. package/dist/lib/routine-readiness.d.ts +47 -0
  48. package/dist/lib/routine-readiness.js +239 -0
  49. package/dist/lib/routines.d.ts +97 -1
  50. package/dist/lib/routines.js +107 -1
  51. package/dist/lib/runner.d.ts +18 -4
  52. package/dist/lib/runner.js +291 -98
  53. package/dist/lib/scheduler.d.ts +7 -1
  54. package/dist/lib/scheduler.js +5 -2
  55. package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
  56. package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
  57. package/dist/lib/self-heal/checks/hook-runtime.d.ts +2 -0
  58. package/dist/lib/self-heal/checks/hook-runtime.js +16 -0
  59. package/dist/lib/self-heal/registry.js +5 -2
  60. package/dist/lib/self-heal/types.d.ts +1 -1
  61. package/dist/lib/session/state.js +4 -1
  62. package/dist/lib/startup/command-registry.d.ts +1 -0
  63. package/dist/lib/startup/command-registry.js +2 -0
  64. package/dist/lib/versions.d.ts +24 -0
  65. package/dist/lib/versions.js +49 -16
  66. package/package.json +2 -2
@@ -5,15 +5,18 @@
5
5
  // SelfHealReport. Both front doors — the daemon (by cadence) and `agents doctor`
6
6
  // (all, or by id) — call runSelfHeal.
7
7
  import { resourcesCheck } from './checks/resources.js';
8
+ import { hookRuntimeCheck } from './checks/hook-runtime.js';
8
9
  import { shimsCheck } from './checks/shims.js';
9
10
  import { shadowingCheck } from './checks/shadowing.js';
10
11
  import { pathCheck } from './checks/path.js';
11
- // Order matters: cheap structural fixes (shims, shadow adoption, PATH) before the
12
- // heavier resource reconciliation, so a freshly-repaired shim is in place first.
12
+ // Order matters: cheap structural fixes (shims, shadow adoption, PATH, generated
13
+ // hook wrappers) before the heavier resource reconciliation, so a freshly-
14
+ // repaired shim is in place first.
13
15
  export const HEAL_CHECKS = [
14
16
  shimsCheck,
15
17
  shadowingCheck,
16
18
  pathCheck,
19
+ hookRuntimeCheck,
17
20
  resourcesCheck,
18
21
  ];
19
22
  /** Run the selected checks, isolating per-check failures. */
@@ -1,4 +1,4 @@
1
- export type HealCheckId = 'resources' | 'shims' | 'shadowing' | 'path';
1
+ export type HealCheckId = 'resources' | 'hook-runtime' | 'shims' | 'shadowing' | 'path';
2
2
  /** When the daemon schedules a check. */
3
3
  export type HealCadence = 'startup' | 'frequent' | 'periodic';
4
4
  export interface HealCtx {
@@ -194,7 +194,10 @@ const TICKET_BRANCH_RE = /(?:^|[/_-])([a-z]{2,6})-(\d{2,6})(?=[/_-]|$)/;
194
194
  /** Keys that look like tickets but aren't — avoid false positives from branches. */
195
195
  const TICKET_DENYLIST = new Set(['UTF', 'SHA', 'ISO', 'RFC', 'IPV', 'X86', 'ARM', 'MP', 'H']);
196
196
  const PR_URL_RE = /https:\/\/github\.com\/[^\s"'()<>]+\/pull\/(\d+)/;
197
- export const WORKTREE_RE = /\/\.agents\/worktrees\/([^/]+)/;
197
+ // Either separator: a Windows session cwd is `…\.agents\worktrees\<slug>`, and a
198
+ // forward-slash-only pattern silently derived no slug there (the RUSH-2358
199
+ // worktree_slug parity test is red on the Windows CI leg for exactly this).
200
+ export const WORKTREE_RE = /[\\/]\.agents[\\/]worktrees[\\/]([^\\/]+)/;
198
201
  /** gh invocations that create/open a PR. */
199
202
  const GH_PR_CREATE_RE = /\bgh\s+pr\s+(?:create|new)\b/;
200
203
  /** gh invocation that opens an issue — the created number is read from its result. */
@@ -39,6 +39,7 @@ export declare const loadPlugins: ModuleLoader;
39
39
  export declare const loadWorkflows: ModuleLoader;
40
40
  export declare const loadWorktree: ModuleLoader;
41
41
  export declare const loadVersions: ModuleLoader;
42
+ export declare const loadUpdate: ModuleLoader;
42
43
  export declare const loadImport: ModuleLoader;
43
44
  export declare const loadExport: ModuleLoader;
44
45
  export declare const loadPackages: ModuleLoader;
@@ -38,6 +38,7 @@ export const loadPlugins = async () => (await import('../../commands/plugins.js'
38
38
  export const loadWorkflows = async () => (await import('../../commands/workflows.js')).registerWorkflowsCommands;
39
39
  export const loadWorktree = async () => (await import('../../commands/worktree.js')).registerWorktreeCommands;
40
40
  export const loadVersions = async () => (await import('../../commands/versions.js')).registerVersionsCommands;
41
+ export const loadUpdate = async () => (await import('../../commands/update.js')).registerUpdateCommand;
41
42
  export const loadImport = async () => (await import('../../commands/import.js')).registerImportCommand;
42
43
  export const loadExport = async () => (await import('../../commands/export.js')).registerExportCommand;
43
44
  export const loadPackages = async () => (await import('../../commands/packages.js')).registerPackagesCommands;
@@ -168,6 +169,7 @@ export const COMMAND_LOADERS = {
168
169
  remove: [loadVersions],
169
170
  rm: [loadVersions],
170
171
  purge: [loadVersions],
172
+ update: [loadUpdate],
171
173
  prune: [loadVersions, loadPrune],
172
174
  import: [loadImport],
173
175
  export: [loadExport],
@@ -414,6 +414,30 @@ export declare function verifyInstalledBinaryLaunches(agent: AgentId, version: s
414
414
  ok: boolean;
415
415
  detail?: string;
416
416
  }>;
417
+ /**
418
+ * The launch probe itself, addressed by PATH rather than by installed version.
419
+ *
420
+ * `verifyInstalledBinaryLaunches` is this function applied to a version dir that
421
+ * is already live. `agents update` needs the identical check applied to a release
422
+ * that is still STAGED — probing it before the swap is what makes the update
423
+ * transactional, since a staged release that cannot launch is discarded instead
424
+ * of replacing a working one. Both callers must agree byte-for-byte on what
425
+ * "launches" means, hence one implementation.
426
+ *
427
+ * `posixBinary` is the extensionless launch target. The real launch target
428
+ * differs by platform, so we probe whatever `agents run` actually execs: on
429
+ * Windows that is the npm `.cmd` wrapper beside it (exec.ts uses
430
+ * `absPath + '.cmd'`), which chains to the native `.exe`; a gutted install
431
+ * (renamed/missing `.exe`) makes that wrapper emit "is not recognized" — the
432
+ * exact win-mini failure a vendor auto-update leaves behind. Probing the
433
+ * extensionless `.bin/<cli>` instead would ENOENT even on a HEALTHY Windows
434
+ * install, so we DON'T. On POSIX the `.bin/<cli>` binary is the launch target
435
+ * and is probed directly.
436
+ */
437
+ export declare function verifyBinaryLaunches(posixBinary: string, home: string): Promise<{
438
+ ok: boolean;
439
+ detail?: string;
440
+ }>;
417
441
  /**
418
442
  * Launch-path self-heal. Given the concrete version `agents run` is about to
419
443
  * spawn, make sure it will actually run — and if not, repair it instead of
@@ -34,6 +34,8 @@ import { discoverPermissionGroups, getActivePermissionPresetName, readPermission
34
34
  import { parseMcpServerConfig, isProjectMcpTrusted } from './mcp.js';
35
35
  import { createVersionedAlias, removeVersionedAlias, getConfigSymlinkVersion, ensureClaudeInsideSymlink, assertIsolationBoundary, } from './shims.js';
36
36
  import { importInstallScriptBinary } from './import.js';
37
+ import { createInstallation } from './installations/store.js';
38
+ import { INSTALLATION_RECORD_FILE } from './installations/types.js';
37
39
  import { IS_WINDOWS, composeWin32CommandLine } from './platform/index.js';
38
40
  import { listInstalledSubagents } from './subagents.js';
39
41
  import { listInstalledWorkflows } from './workflows.js';
@@ -1579,6 +1581,10 @@ export async function installVersion(agent, version, onProgress, opts) {
1579
1581
  correctly reports it uninstalled. */
1580
1582
  }
1581
1583
  createVersionedAlias(agent, installedVersion);
1584
+ // Freeze this installation's identity. The dir name is its stable label from
1585
+ // here on; the release it carries is recorded separately so `agents update`
1586
+ // can move the release without invalidating any reference to the label.
1587
+ createInstallation(agent, installedVersion, installedVersion);
1582
1588
  // The self-updating binary just changed on disk — drop the cached
1583
1589
  // `--version` so `agents view` reflects the freshly-installed release.
1584
1590
  invalidateLiveVersionCache(agent);
@@ -1633,6 +1639,9 @@ export async function installVersion(agent, version, onProgress, opts) {
1633
1639
  // load-bearing: it ensures `version` (which VERSION_RE permits to start with
1634
1640
  // `-`) is never passed as a standalone npm CLI flag.
1635
1641
  const packageSpec = `${agentConfig.npmPackage}@${version}`;
1642
+ // Set once the install has passed its integrity gate; read after the try so
1643
+ // the success path's bookkeeping sits outside the catch's cleanup.
1644
+ let healthyVersion;
1636
1645
  try {
1637
1646
  // Check npm is available
1638
1647
  const winShell = process.platform === 'win32';
@@ -1719,8 +1728,10 @@ export async function installVersion(agent, version, onProgress, opts) {
1719
1728
  + `The install is incomplete — the platform binary is missing. Re-run: agents add ${agent}@${installedVersion}`,
1720
1729
  };
1721
1730
  }
1722
- emit('version.install', { agent, version: installedVersion });
1723
- return { success: true, installedVersion };
1731
+ // The install is healthy from here. Identity is frozen AFTER the try (see
1732
+ // below) so a bookkeeping write failure cannot fall into the catch and wipe
1733
+ // a working install.
1734
+ healthyVersion = installedVersion;
1724
1735
  }
1725
1736
  catch (err) {
1726
1737
  // Clean up on failure — preserve `home/` in case a prior install left
@@ -1731,13 +1742,20 @@ export async function installVersion(agent, version, onProgress, opts) {
1731
1742
  emit('version.install', { agent, version, error: err.message });
1732
1743
  return { success: false, installedVersion: version, error: err.message };
1733
1744
  }
1745
+ // Freeze this installation's identity (see the installScript branch above).
1746
+ createInstallation(agent, healthyVersion, healthyVersion);
1747
+ emit('version.install', { agent, version: healthyVersion });
1748
+ return { success: true, installedVersion: healthyVersion };
1734
1749
  }
1735
1750
  // Version-dir entries that are STATE, not install output, and so must survive a
1736
- // clean reinstall: `home/`, and the `.isolated` marker that is the single source
1737
- // of truth for "this copy is walled off". Losing the marker would silently demote
1751
+ // clean reinstall: `home/`, the `.isolated` marker that is the single source
1752
+ // of truth for "this copy is walled off", and `installation.json`, which carries
1753
+ // this installation's frozen identity. Losing the marker would silently demote
1738
1754
  // an isolated copy to a normal one on its first repair — after which shim
1739
- // self-heal would hand it a bare `<agent>` shim and a PATH entry.
1740
- const PRESERVED_ON_CLEAN_REINSTALL = new Set(['home', '.isolated']);
1755
+ // self-heal would hand it a bare `<agent>` shim and a PATH entry. Losing the
1756
+ // installation record would mint a NEW id for the same install on its first
1757
+ // repair, discarding its release history.
1758
+ const PRESERVED_ON_CLEAN_REINSTALL = new Set(['home', '.isolated', INSTALLATION_RECORD_FILE]);
1741
1759
  /**
1742
1760
  * Remove install artifacts from a version directory, preserving `home/` which
1743
1761
  * contains the user's conversation history, sessions, history.jsonl, tasks,
@@ -2233,16 +2251,31 @@ export function probeSpawnSpec(binary, isWin) {
2233
2251
  return { command: binary, args: ['--version'], shell: false };
2234
2252
  }
2235
2253
  export async function verifyInstalledBinaryLaunches(agent, version) {
2236
- // The real launch target differs by platform, so probe whatever `agents run`
2237
- // actually execs. On Windows that's the npm `.cmd` wrapper (exec.ts uses
2238
- // `absPath + '.cmd'`), which chains to the native `.exe`; a gutted install
2239
- // (renamed/missing `.exe`) makes that wrapper emit "is not recognized" the
2240
- // exact win-mini failure a vendor auto-update leaves behind. Probing the
2241
- // extensionless `.bin/<cli>` instead would ENOENT even on a HEALTHY Windows
2242
- // install, so we DON'T. On POSIX the `.bin/<cli>` binary is the launch target
2243
- // and is probed directly.
2254
+ return verifyBinaryLaunches(getBinaryPath(agent, version), getVersionHomePath(agent, version));
2255
+ }
2256
+ /**
2257
+ * The launch probe itself, addressed by PATH rather than by installed version.
2258
+ *
2259
+ * `verifyInstalledBinaryLaunches` is this function applied to a version dir that
2260
+ * is already live. `agents update` needs the identical check applied to a release
2261
+ * that is still STAGED — probing it before the swap is what makes the update
2262
+ * transactional, since a staged release that cannot launch is discarded instead
2263
+ * of replacing a working one. Both callers must agree byte-for-byte on what
2264
+ * "launches" means, hence one implementation.
2265
+ *
2266
+ * `posixBinary` is the extensionless launch target. The real launch target
2267
+ * differs by platform, so we probe whatever `agents run` actually execs: on
2268
+ * Windows that is the npm `.cmd` wrapper beside it (exec.ts uses
2269
+ * `absPath + '.cmd'`), which chains to the native `.exe`; a gutted install
2270
+ * (renamed/missing `.exe`) makes that wrapper emit "is not recognized" — the
2271
+ * exact win-mini failure a vendor auto-update leaves behind. Probing the
2272
+ * extensionless `.bin/<cli>` instead would ENOENT even on a HEALTHY Windows
2273
+ * install, so we DON'T. On POSIX the `.bin/<cli>` binary is the launch target
2274
+ * and is probed directly.
2275
+ */
2276
+ export async function verifyBinaryLaunches(posixBinary, home) {
2244
2277
  const isWin = process.platform === 'win32';
2245
- const binary = isWin ? getBinaryPath(agent, version) + '.cmd' : getBinaryPath(agent, version);
2278
+ const binary = isWin ? posixBinary + '.cmd' : posixBinary;
2246
2279
  if (!fs.existsSync(binary)) {
2247
2280
  // Windows: a missing `.cmd` means a non-npm/global agent (droid.exe) we can't
2248
2281
  // safely probe — treat as healthy (isVersionInstalled validates presence).
@@ -2259,7 +2292,7 @@ export async function verifyInstalledBinaryLaunches(agent, version) {
2259
2292
  await execFileAsync(spec.command, spec.args, {
2260
2293
  timeout: 15000,
2261
2294
  shell: spec.shell,
2262
- env: { ...process.env, HOME: getVersionHomePath(agent, version) },
2295
+ env: { ...process.env, HOME: home },
2263
2296
  });
2264
2297
  return { ok: true };
2265
2298
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@phnx-labs/agents-cli",
3
- "version": "1.22.31",
3
+ "version": "1.22.32",
4
4
  "description": "One CLI for all your AI coding agents - versions, config, cloud dispatch, sessions, and teams (now with first-class Grok Build CLI support)",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -58,7 +58,7 @@
58
58
  "test": "node ./node_modules/vitest/vitest.mjs run",
59
59
  "bench": "node ./node_modules/vitest/vitest.mjs bench --run",
60
60
  "typecheck:bench": "tsc --noEmit --ignoreConfig --skipLibCheck --module esnext --moduleResolution bundler --target es2022 --strict --esModuleInterop --lib es2022 --types node src/lib/*.bench.ts src/lib/**/*.bench.ts",
61
- "test:remote": "scripts/sandbox.sh 'bun install && bun run build && bun run test'",
61
+ "test:remote": "scripts/sandbox.sh 'cd apps/cli && bun install && bun run build && bun run test'",
62
62
  "verify-docs": "scripts/verify-docs.sh",
63
63
  "test:watch": "node ./node_modules/vitest/vitest.mjs"
64
64
  },