@phnx-labs/agents-cli 1.22.30 → 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.
- package/CHANGELOG.md +72 -0
- package/README.md +13 -6
- package/dist/bin/agents +0 -0
- package/dist/commands/daemon.js +52 -12
- package/dist/commands/doctor.d.ts +19 -0
- package/dist/commands/doctor.js +119 -17
- package/dist/commands/focus.d.ts +4 -1
- package/dist/commands/focus.js +19 -4
- package/dist/commands/routines.js +164 -36
- package/dist/commands/secrets.d.ts +20 -0
- package/dist/commands/secrets.js +59 -93
- package/dist/commands/{sessions-favorite.d.ts → sessions-bookmark.d.ts} +7 -7
- package/dist/commands/{sessions-favorite.js → sessions-bookmark.js} +38 -38
- package/dist/commands/sessions-browser.d.ts +10 -8
- package/dist/commands/sessions-browser.js +61 -32
- package/dist/commands/sessions-stats.js +1 -1
- package/dist/commands/sessions.d.ts +11 -9
- package/dist/commands/sessions.js +114 -65
- package/dist/commands/update.d.ts +2 -0
- package/dist/commands/update.js +148 -0
- package/dist/index.js +4 -2
- package/dist/lib/catchup.js +4 -1
- package/dist/lib/daemon.d.ts +17 -0
- package/dist/lib/daemon.js +69 -3
- package/dist/lib/devices/doctor-findings.d.ts +7 -2
- package/dist/lib/devices/doctor-findings.js +53 -2
- package/dist/lib/devices/doctor-overview-cache.d.ts +7 -0
- package/dist/lib/devices/doctor-overview-cache.js +15 -0
- package/dist/lib/devices/fleet-divergence.d.ts +11 -0
- package/dist/lib/devices/fleet-divergence.js +6 -0
- package/dist/lib/devices/fleet-inventory.js +16 -2
- package/dist/lib/drift.d.ts +6 -1
- package/dist/lib/drift.js +9 -0
- package/dist/lib/hooks/cache.js +20 -1
- package/dist/lib/hooks.d.ts +91 -1
- package/dist/lib/hooks.js +289 -3
- package/dist/lib/hosts/passthrough.js +3 -0
- package/dist/lib/installations/index.d.ts +14 -0
- package/dist/lib/installations/index.js +14 -0
- package/dist/lib/installations/resolve.d.ts +43 -0
- package/dist/lib/installations/resolve.js +93 -0
- package/dist/lib/installations/store.d.ts +56 -0
- package/dist/lib/installations/store.js +196 -0
- package/dist/lib/installations/strategies.d.ts +73 -0
- package/dist/lib/installations/strategies.js +293 -0
- package/dist/lib/installations/types.d.ts +78 -0
- package/dist/lib/installations/types.js +8 -0
- package/dist/lib/installations/update.d.ts +40 -0
- package/dist/lib/installations/update.js +131 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
- package/dist/lib/migrate.d.ts +27 -0
- package/dist/lib/migrate.js +118 -2
- package/dist/lib/picker.d.ts +6 -3
- package/dist/lib/picker.js +7 -2
- package/dist/lib/routine-context.d.ts +144 -0
- package/dist/lib/routine-context.js +268 -0
- package/dist/lib/routine-readiness.d.ts +47 -0
- package/dist/lib/routine-readiness.js +239 -0
- package/dist/lib/routines.d.ts +97 -1
- package/dist/lib/routines.js +107 -1
- package/dist/lib/runner.d.ts +18 -4
- package/dist/lib/runner.js +291 -98
- package/dist/lib/scheduler.d.ts +7 -1
- package/dist/lib/scheduler.js +5 -2
- package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
- package/dist/lib/secrets/agent.d.ts +0 -1
- package/dist/lib/secrets/agent.js +0 -4
- package/dist/lib/secrets/session-store.d.ts +0 -4
- package/dist/lib/secrets/session-store.js +0 -5
- package/dist/lib/self-heal/checks/hook-runtime.d.ts +2 -0
- package/dist/lib/self-heal/checks/hook-runtime.js +16 -0
- package/dist/lib/self-heal/registry.js +5 -2
- package/dist/lib/self-heal/types.d.ts +1 -1
- package/dist/lib/session/{favorites.d.ts → bookmarks.d.ts} +14 -14
- package/dist/lib/session/{favorites.js → bookmarks.js} +22 -22
- package/dist/lib/session/state.js +4 -1
- package/dist/lib/startup/command-registry.d.ts +1 -0
- package/dist/lib/startup/command-registry.js +2 -0
- package/dist/lib/versions.d.ts +24 -0
- package/dist/lib/versions.js +49 -16
- package/package.json +2 -2
|
@@ -93,10 +93,6 @@ export declare function deleteBundleSessions(name: string): void;
|
|
|
93
93
|
/** Delete one bundle's session blob and prune it from the index. */
|
|
94
94
|
export declare function deleteSession(name: string, harness?: string): void;
|
|
95
95
|
export declare function deleteLeaseSession(leaseId: string): number;
|
|
96
|
-
export declare function activeLeaseSessions(now?: number): Array<{
|
|
97
|
-
name: string;
|
|
98
|
-
lease: SecretLease;
|
|
99
|
-
}>;
|
|
100
96
|
/** Delete every session blob + the index (for `secrets lock --all`). */
|
|
101
97
|
export declare function deleteAllSessions(): void;
|
|
102
98
|
/** Rehydrate every unexpired session into `[name, entry]` pairs for the broker to
|
|
@@ -208,11 +208,6 @@ export function deleteLeaseSession(leaseId) {
|
|
|
208
208
|
}
|
|
209
209
|
return deleted;
|
|
210
210
|
}
|
|
211
|
-
export function activeLeaseSessions(now = Date.now()) {
|
|
212
|
-
return rehydrateSessions(now)
|
|
213
|
-
.filter((item) => Boolean(item.entry.lease))
|
|
214
|
-
.map(({ name, entry }) => ({ name, lease: entry.lease }));
|
|
215
|
-
}
|
|
216
211
|
/** Delete every session blob + the index (for `secrets lock --all`). */
|
|
217
212
|
export function deleteAllSessions() {
|
|
218
213
|
if (!shouldPersist())
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// hook-runtime check — detects and bounded-repairs agents-managed generated
|
|
2
|
+
// hook shims (~/.agents/.cache/shims/hooks/<name>.sh). A native hook command can
|
|
3
|
+
// look wired while its generated wrapper is missing, non-executable, non-file,
|
|
4
|
+
// or broken — silent breakage. Repair runs at most once per unique path per
|
|
5
|
+
// pass (no retry, no sync recursion); unresolved findings stay needsAttention.
|
|
6
|
+
import { resultOf } from '../types.js';
|
|
7
|
+
import { repairManagedHookRuntimeArtifacts } from '../../hooks.js';
|
|
8
|
+
export const hookRuntimeCheck = {
|
|
9
|
+
id: 'hook-runtime',
|
|
10
|
+
title: 'Generated hook runtime shims',
|
|
11
|
+
cadence: 'frequent',
|
|
12
|
+
async run(ctx) {
|
|
13
|
+
const report = repairManagedHookRuntimeArtifacts({ dryRun: ctx.dryRun });
|
|
14
|
+
return resultOf(report.fixed, report.needsAttention);
|
|
15
|
+
},
|
|
16
|
+
};
|
|
@@ -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
|
|
12
|
-
// heavier resource reconciliation, so a freshly-
|
|
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 {
|
|
@@ -1,39 +1,39 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Bookmarked sessions — the durable "keep this one handy" mark a human puts on a
|
|
3
3
|
* session, deliberately kept OUT of the session index.
|
|
4
4
|
*
|
|
5
5
|
* `sessions.db` is a rebuildable CACHE: a reindex or a schema bump throws its
|
|
6
|
-
* rows away and re-derives them from the transcripts on disk. A
|
|
6
|
+
* rows away and re-derives them from the transcripts on disk. A bookmark is not
|
|
7
7
|
* derivable from a transcript — it is a human's choice — so a column there would
|
|
8
8
|
* be silently lost on the next rebuild. It lives in `~/.agents/.history/` instead,
|
|
9
9
|
* next to the actor sidecars, which is never pruned.
|
|
10
10
|
*
|
|
11
11
|
* One flat set of session ids. The id is the transcript's own uuid, so it is
|
|
12
12
|
* stable and machine-independent — but the file is NOT synced today: session sync
|
|
13
|
-
* carries `.history/backups/` (`lib/session/sync/agents.ts`), not this.
|
|
13
|
+
* carries `.history/backups/` (`lib/session/sync/agents.ts`), not this. Bookmarks
|
|
14
14
|
* are therefore per-machine; carrying them across the fleet would mean adding
|
|
15
15
|
* them to the sync manifest, which this does not do.
|
|
16
16
|
*
|
|
17
|
-
* Reads are memoized against the file's mtime — the picker asks `
|
|
17
|
+
* Reads are memoized against the file's mtime — the picker asks `isBookmarked` once
|
|
18
18
|
* per rendered row, and re-reading a JSON file per row on every keystroke is the
|
|
19
19
|
* kind of cost that makes a TUI feel broken.
|
|
20
20
|
*/
|
|
21
|
-
export declare function
|
|
21
|
+
export declare function bookmarksFilePath(): string;
|
|
22
22
|
/** Drop the memoized read. Tests that write the file directly need this; nothing
|
|
23
23
|
* in the CLI does, because every mutation here refreshes the cache itself. */
|
|
24
|
-
export declare function
|
|
24
|
+
export declare function clearBookmarksCache(): void;
|
|
25
25
|
/**
|
|
26
|
-
* Every
|
|
27
|
-
* unreadable, or malformed — a corrupt
|
|
26
|
+
* Every bookmarked session id. Empty (never throws) when the file is absent,
|
|
27
|
+
* unreadable, or malformed — a corrupt bookmarks file must not take down
|
|
28
28
|
* `agents sessions`.
|
|
29
29
|
*/
|
|
30
|
-
export declare function
|
|
31
|
-
export declare function
|
|
30
|
+
export declare function listBookmarks(): Set<string>;
|
|
31
|
+
export declare function isBookmarked(sessionId: string | undefined): boolean;
|
|
32
32
|
/**
|
|
33
|
-
* Set (or clear) the
|
|
33
|
+
* Set (or clear) the bookmark mark on a session. Returns the resulting state, so
|
|
34
34
|
* a caller can report it without a second read. A no-op write is skipped, which
|
|
35
35
|
* keeps the file's mtime — and every other process's memoized read — untouched.
|
|
36
36
|
*/
|
|
37
|
-
export declare function
|
|
38
|
-
/** Flip the mark; returns the new state (`true` = now
|
|
39
|
-
export declare function
|
|
37
|
+
export declare function setBookmark(sessionId: string, on: boolean): boolean;
|
|
38
|
+
/** Flip the mark; returns the new state (`true` = now bookmarked). */
|
|
39
|
+
export declare function toggleBookmark(sessionId: string): boolean;
|
|
@@ -1,28 +1,28 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Bookmarked sessions — the durable "keep this one handy" mark a human puts on a
|
|
3
3
|
* session, deliberately kept OUT of the session index.
|
|
4
4
|
*
|
|
5
5
|
* `sessions.db` is a rebuildable CACHE: a reindex or a schema bump throws its
|
|
6
|
-
* rows away and re-derives them from the transcripts on disk. A
|
|
6
|
+
* rows away and re-derives them from the transcripts on disk. A bookmark is not
|
|
7
7
|
* derivable from a transcript — it is a human's choice — so a column there would
|
|
8
8
|
* be silently lost on the next rebuild. It lives in `~/.agents/.history/` instead,
|
|
9
9
|
* next to the actor sidecars, which is never pruned.
|
|
10
10
|
*
|
|
11
11
|
* One flat set of session ids. The id is the transcript's own uuid, so it is
|
|
12
12
|
* stable and machine-independent — but the file is NOT synced today: session sync
|
|
13
|
-
* carries `.history/backups/` (`lib/session/sync/agents.ts`), not this.
|
|
13
|
+
* carries `.history/backups/` (`lib/session/sync/agents.ts`), not this. Bookmarks
|
|
14
14
|
* are therefore per-machine; carrying them across the fleet would mean adding
|
|
15
15
|
* them to the sync manifest, which this does not do.
|
|
16
16
|
*
|
|
17
|
-
* Reads are memoized against the file's mtime — the picker asks `
|
|
17
|
+
* Reads are memoized against the file's mtime — the picker asks `isBookmarked` once
|
|
18
18
|
* per rendered row, and re-reading a JSON file per row on every keystroke is the
|
|
19
19
|
* kind of cost that makes a TUI feel broken.
|
|
20
20
|
*/
|
|
21
21
|
import fs from 'node:fs';
|
|
22
22
|
import path from 'node:path';
|
|
23
23
|
import { getHistoryDir } from '../state.js';
|
|
24
|
-
export function
|
|
25
|
-
return path.join(getHistoryDir(), '
|
|
24
|
+
export function bookmarksFilePath() {
|
|
25
|
+
return path.join(getHistoryDir(), 'bookmarks.json');
|
|
26
26
|
}
|
|
27
27
|
/** Memoized parse, invalidated by the file's mtime+size (another process — or
|
|
28
28
|
* another machine's sync — can rewrite it under us). */
|
|
@@ -38,16 +38,16 @@ function statKey(file) {
|
|
|
38
38
|
}
|
|
39
39
|
/** Drop the memoized read. Tests that write the file directly need this; nothing
|
|
40
40
|
* in the CLI does, because every mutation here refreshes the cache itself. */
|
|
41
|
-
export function
|
|
41
|
+
export function clearBookmarksCache() {
|
|
42
42
|
cache = null;
|
|
43
43
|
}
|
|
44
44
|
/**
|
|
45
|
-
* Every
|
|
46
|
-
* unreadable, or malformed — a corrupt
|
|
45
|
+
* Every bookmarked session id. Empty (never throws) when the file is absent,
|
|
46
|
+
* unreadable, or malformed — a corrupt bookmarks file must not take down
|
|
47
47
|
* `agents sessions`.
|
|
48
48
|
*/
|
|
49
|
-
export function
|
|
50
|
-
const file =
|
|
49
|
+
export function listBookmarks() {
|
|
50
|
+
const file = bookmarksFilePath();
|
|
51
51
|
const key = statKey(file);
|
|
52
52
|
if (cache && cache.key === key)
|
|
53
53
|
return cache.ids;
|
|
@@ -64,14 +64,14 @@ export function listFavorites() {
|
|
|
64
64
|
cache = { key, ids };
|
|
65
65
|
return ids;
|
|
66
66
|
}
|
|
67
|
-
export function
|
|
67
|
+
export function isBookmarked(sessionId) {
|
|
68
68
|
if (!sessionId)
|
|
69
69
|
return false;
|
|
70
|
-
return
|
|
70
|
+
return listBookmarks().has(sessionId);
|
|
71
71
|
}
|
|
72
72
|
/** Atomic write (tmp + rename) so a concurrent reader never sees a half file. */
|
|
73
|
-
function
|
|
74
|
-
const file =
|
|
73
|
+
function writeBookmarks(ids) {
|
|
74
|
+
const file = bookmarksFilePath();
|
|
75
75
|
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
76
76
|
const body = { version: 1, sessionIds: [...ids].sort() };
|
|
77
77
|
const tmp = `${file}.${process.pid}.tmp`;
|
|
@@ -80,22 +80,22 @@ function writeFavorites(ids) {
|
|
|
80
80
|
cache = { key: statKey(file), ids };
|
|
81
81
|
}
|
|
82
82
|
/**
|
|
83
|
-
* Set (or clear) the
|
|
83
|
+
* Set (or clear) the bookmark mark on a session. Returns the resulting state, so
|
|
84
84
|
* a caller can report it without a second read. A no-op write is skipped, which
|
|
85
85
|
* keeps the file's mtime — and every other process's memoized read — untouched.
|
|
86
86
|
*/
|
|
87
|
-
export function
|
|
88
|
-
const ids = new Set(
|
|
87
|
+
export function setBookmark(sessionId, on) {
|
|
88
|
+
const ids = new Set(listBookmarks());
|
|
89
89
|
if (ids.has(sessionId) === on)
|
|
90
90
|
return on;
|
|
91
91
|
if (on)
|
|
92
92
|
ids.add(sessionId);
|
|
93
93
|
else
|
|
94
94
|
ids.delete(sessionId);
|
|
95
|
-
|
|
95
|
+
writeBookmarks(ids);
|
|
96
96
|
return on;
|
|
97
97
|
}
|
|
98
|
-
/** Flip the mark; returns the new state (`true` = now
|
|
99
|
-
export function
|
|
100
|
-
return
|
|
98
|
+
/** Flip the mark; returns the new state (`true` = now bookmarked). */
|
|
99
|
+
export function toggleBookmark(sessionId) {
|
|
100
|
+
return setBookmark(sessionId, !isBookmarked(sessionId));
|
|
101
101
|
}
|
|
@@ -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
|
-
|
|
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],
|
package/dist/lib/versions.d.ts
CHANGED
|
@@ -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
|
package/dist/lib/versions.js
CHANGED
|
@@ -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
|
-
|
|
1723
|
-
|
|
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/`,
|
|
1737
|
-
// of truth for "this copy is walled off"
|
|
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
|
-
|
|
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
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
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 ?
|
|
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:
|
|
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.
|
|
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
|
},
|