@phnx-labs/agents-cli 1.22.23 → 1.22.25
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 +359 -0
- package/README.md +19 -8
- package/dist/bin/agents +0 -0
- package/dist/commands/attach.d.ts +2 -0
- package/dist/commands/attach.js +32 -7
- package/dist/commands/cloud.js +9 -5
- package/dist/commands/defaults.js +2 -0
- package/dist/commands/doctor.d.ts +24 -0
- package/dist/commands/doctor.js +105 -9
- package/dist/commands/exec.js +99 -44
- package/dist/commands/feed.d.ts +23 -0
- package/dist/commands/feed.js +65 -2
- package/dist/commands/focus.d.ts +34 -3
- package/dist/commands/focus.js +301 -57
- package/dist/commands/go.d.ts +14 -1
- package/dist/commands/go.js +49 -5
- package/dist/commands/insights.js +6 -2
- package/dist/commands/inspect.js +39 -5
- package/dist/commands/menubar.js +14 -1
- package/dist/commands/models.js +1 -0
- package/dist/commands/modes.d.ts +12 -0
- package/dist/commands/modes.js +147 -0
- package/dist/commands/routines.d.ts +3 -0
- package/dist/commands/routines.js +70 -47
- package/dist/commands/run-cloud.js +1 -1
- package/dist/commands/secrets-sync.js +11 -13
- package/dist/commands/secrets.d.ts +2 -0
- package/dist/commands/secrets.js +54 -17
- package/dist/commands/sessions-browser.d.ts +36 -1
- package/dist/commands/sessions-browser.js +165 -19
- package/dist/commands/sessions-resume.d.ts +2 -0
- package/dist/commands/sessions-resume.js +28 -12
- package/dist/commands/sessions.d.ts +23 -1
- package/dist/commands/sessions.js +58 -61
- package/dist/commands/setup-secrets.js +1 -1
- package/dist/commands/setup-watchdog.js +5 -10
- package/dist/commands/setup.js +1 -1
- package/dist/commands/sync.js +246 -42
- package/dist/commands/teams.js +3 -2
- package/dist/commands/view.js +2 -0
- package/dist/commands/watchdog.d.ts +3 -4
- package/dist/commands/watchdog.js +26 -66
- package/dist/index.js +38 -2
- package/dist/lib/agent-modes.d.ts +49 -0
- package/dist/lib/agent-modes.js +70 -0
- package/dist/lib/agents.js +126 -21
- package/dist/lib/cloud/cursor.d.ts +79 -0
- package/dist/lib/cloud/cursor.js +228 -0
- package/dist/lib/cloud/registry.js +2 -0
- package/dist/lib/cloud/types.d.ts +7 -2
- package/dist/lib/cloud/types.js +14 -0
- package/dist/lib/crabbox/cli.d.ts +2 -2
- package/dist/lib/crabbox/config.d.ts +7 -8
- package/dist/lib/crabbox/config.js +14 -14
- package/dist/lib/crabbox/lease.d.ts +11 -4
- package/dist/lib/crabbox/lease.js +40 -8
- package/dist/lib/crabbox/setup-copy.d.ts +5 -0
- package/dist/lib/crabbox/setup-copy.js +17 -1
- package/dist/lib/daemon.js +27 -1
- package/dist/lib/device-config.js +7 -0
- package/dist/lib/devices/doctor-findings.d.ts +7 -1
- package/dist/lib/devices/doctor-findings.js +40 -1
- package/dist/lib/doctor-diff.d.ts +3 -0
- package/dist/lib/doctor-diff.js +15 -13
- package/dist/lib/event-stream.d.ts +3 -1
- package/dist/lib/event-stream.js +14 -1
- package/dist/lib/events.d.ts +9 -0
- package/dist/lib/events.js +58 -0
- package/dist/lib/exec.d.ts +15 -3
- package/dist/lib/exec.js +112 -19
- package/dist/lib/feed-outcome.d.ts +3 -0
- package/dist/lib/feed-outcome.js +18 -10
- package/dist/lib/feed.d.ts +4 -0
- package/dist/lib/hooks/cache.js +36 -3
- package/dist/lib/hooks.d.ts +19 -7
- package/dist/lib/hooks.js +88 -38
- package/dist/lib/hosts/passthrough.d.ts +21 -0
- package/dist/lib/hosts/passthrough.js +39 -12
- package/dist/lib/hosts/session-index.d.ts +4 -0
- package/dist/lib/hosts/session-index.js +7 -0
- package/dist/lib/manifest.d.ts +12 -2
- package/dist/lib/manifest.js +60 -5
- package/dist/lib/mcp.js +5 -1
- package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/Info.plist +5 -1
- package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/Resources/AppIcon.icns +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/_CodeSignature/CodeResources +15 -2
- package/dist/lib/menubar/install-menubar.d.ts +52 -2
- package/dist/lib/menubar/install-menubar.js +128 -6
- package/dist/lib/menubar/snapshot.d.ts +15 -0
- package/dist/lib/menubar/snapshot.js +40 -0
- package/dist/lib/plugins.js +13 -1
- package/dist/lib/refresh.d.ts +5 -0
- package/dist/lib/refresh.js +37 -33
- package/dist/lib/resource-inventory.d.ts +79 -0
- package/dist/lib/resource-inventory.js +122 -0
- package/dist/lib/resources/mcp.js +3 -0
- package/dist/lib/resources.js +8 -5
- package/dist/lib/routine-process-cleanup.d.ts +9 -0
- package/dist/lib/routine-process-cleanup.js +73 -0
- package/dist/lib/run-defaults.d.ts +2 -0
- package/dist/lib/run-defaults.js +23 -2
- package/dist/lib/runner.js +27 -22
- package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/Info.plist +2 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/Resources/AppIcon.icns +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/_CodeSignature/CodeResources +13 -1
- package/dist/lib/secrets/bundles.js +158 -85
- package/dist/lib/secrets/filestore.d.ts +7 -3
- package/dist/lib/secrets/filestore.js +25 -8
- package/dist/lib/secrets/index.js +34 -8
- package/dist/lib/secrets/rc-hygiene.d.ts +14 -0
- package/dist/lib/secrets/rc-hygiene.js +14 -1
- package/dist/lib/secrets/reaper.d.ts +10 -0
- package/dist/lib/secrets/reaper.js +50 -15
- package/dist/lib/secrets/sync-passphrase.d.ts +27 -0
- package/dist/lib/secrets/sync-passphrase.js +78 -0
- package/dist/lib/session/active.d.ts +4 -0
- package/dist/lib/session/db.js +8 -2
- package/dist/lib/session/recovery.d.ts +37 -0
- package/dist/lib/session/recovery.js +95 -0
- package/dist/lib/session/remote-list.d.ts +2 -0
- package/dist/lib/session/remote-list.js +1 -0
- package/dist/lib/session/session-cache.d.ts +4 -4
- package/dist/lib/session/session-cache.js +4 -4
- package/dist/lib/shims.d.ts +2 -2
- package/dist/lib/shims.js +45 -6
- package/dist/lib/signin-badge.js +2 -0
- package/dist/lib/startup/command-registry.d.ts +16 -0
- package/dist/lib/startup/command-registry.js +44 -0
- package/dist/lib/sync-umbrella.d.ts +5 -0
- package/dist/lib/sync-umbrella.js +5 -4
- package/dist/lib/teams/agents.js +1 -1
- package/dist/lib/teams/parsers.d.ts +1 -1
- package/dist/lib/tmux/session.d.ts +4 -2
- package/dist/lib/tmux/session.js +5 -5
- package/dist/lib/types.d.ts +3 -1
- package/dist/lib/versions.js +16 -1
- package/dist/lib/watchdog/service.d.ts +17 -0
- package/dist/lib/watchdog/service.js +39 -0
- package/package.json +1 -1
|
@@ -90,25 +90,47 @@ export function planKeychainReap(snapshots, now, prevCandidates) {
|
|
|
90
90
|
}
|
|
91
91
|
return { kill, nextCandidates };
|
|
92
92
|
}
|
|
93
|
+
/**
|
|
94
|
+
* Parse macOS `ps -o etime=` elapsed time into whole seconds.
|
|
95
|
+
*
|
|
96
|
+
* BSD `etime` renders as `[[dd-]hh:]mm:ss` (e.g. `05:03`, `01:02:03`,
|
|
97
|
+
* `14-04:10:52`). This is the portable keyword: `etimes` (raw seconds) is a
|
|
98
|
+
* GNU/Linux procps extension that macOS `ps` rejects with a non-zero exit, so
|
|
99
|
+
* the reaper — which only ever runs on darwin — must read `etime`.
|
|
100
|
+
* Returns null for an unparseable value so the caller drops the row.
|
|
101
|
+
*/
|
|
102
|
+
export function parseEtimeToSeconds(raw) {
|
|
103
|
+
const m = raw.match(/^(?:(\d+)-)?(?:(\d+):)?(\d+):(\d+)$/);
|
|
104
|
+
if (!m)
|
|
105
|
+
return null;
|
|
106
|
+
const days = m[1] ? parseInt(m[1], 10) : 0;
|
|
107
|
+
const hours = m[2] ? parseInt(m[2], 10) : 0;
|
|
108
|
+
const mins = parseInt(m[3], 10);
|
|
109
|
+
const secs = parseInt(m[4], 10);
|
|
110
|
+
if ([days, hours, mins, secs].some(isNaN))
|
|
111
|
+
return null;
|
|
112
|
+
return ((days * 24 + hours) * 60 + mins) * 60 + secs;
|
|
113
|
+
}
|
|
93
114
|
/**
|
|
94
115
|
* Parse one `ps` output line.
|
|
95
116
|
*
|
|
96
|
-
* Expected format from `ps -ax -o pid=,ppid=,
|
|
97
|
-
* "<pid> <ppid> <
|
|
98
|
-
*
|
|
117
|
+
* Expected format from `ps -ax -o pid=,ppid=,etime=,command=`:
|
|
118
|
+
* "<pid> <ppid> <etime> <command...>"
|
|
119
|
+
* where `<etime>` is BSD elapsed time (`[[dd-]hh:]mm:ss`). The command field is
|
|
120
|
+
* the remainder of the line and may contain spaces.
|
|
99
121
|
*/
|
|
100
122
|
function parsePsLine(line) {
|
|
101
123
|
const trimmed = line.trim();
|
|
102
124
|
if (!trimmed)
|
|
103
125
|
return null;
|
|
104
|
-
const m = trimmed.match(/^(\d+)\s+(\d+)\s+(\
|
|
126
|
+
const m = trimmed.match(/^(\d+)\s+(\d+)\s+(\S+)\s+(.*)$/);
|
|
105
127
|
if (!m)
|
|
106
128
|
return null;
|
|
107
129
|
const pid = parseInt(m[1], 10);
|
|
108
130
|
const ppid = parseInt(m[2], 10);
|
|
109
|
-
const elapsedSec =
|
|
131
|
+
const elapsedSec = parseEtimeToSeconds(m[3]);
|
|
110
132
|
const command = m[4];
|
|
111
|
-
if (isNaN(pid) || isNaN(ppid) ||
|
|
133
|
+
if (isNaN(pid) || isNaN(ppid) || elapsedSec == null)
|
|
112
134
|
return null;
|
|
113
135
|
return { pid, ppid, elapsedSec, command };
|
|
114
136
|
}
|
|
@@ -142,7 +164,7 @@ export function reapOrphanedKeychainProcesses() {
|
|
|
142
164
|
}
|
|
143
165
|
let out;
|
|
144
166
|
try {
|
|
145
|
-
out = execFileSync('ps', ['-ax', '-o', 'pid=,ppid=,
|
|
167
|
+
out = execFileSync('ps', ['-ax', '-o', 'pid=,ppid=,etime=,command='], {
|
|
146
168
|
encoding: 'utf-8',
|
|
147
169
|
stdio: ['ignore', 'pipe', 'ignore'],
|
|
148
170
|
});
|
|
@@ -150,7 +172,7 @@ export function reapOrphanedKeychainProcesses() {
|
|
|
150
172
|
catch (err) {
|
|
151
173
|
return { reaped: 0, details: [`ps failed: ${err.message}`], plan: { kill: [], nextCandidates: new Map() } };
|
|
152
174
|
}
|
|
153
|
-
const
|
|
175
|
+
const rows = [];
|
|
154
176
|
for (const line of out.split('\n')) {
|
|
155
177
|
const parsed = parsePsLine(line);
|
|
156
178
|
if (!parsed)
|
|
@@ -159,14 +181,27 @@ export function reapOrphanedKeychainProcesses() {
|
|
|
159
181
|
// Exact path-match: the helper invocation's command line begins with the
|
|
160
182
|
// absolute helper path, followed by a space and its arguments (or nothing).
|
|
161
183
|
const isHelper = command === helperPath || command.startsWith(`${helperPath} `);
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
184
|
+
rows.push({ pid, ppid, elapsedSec, isHelper, startTime: null });
|
|
185
|
+
}
|
|
186
|
+
const rowByPid = new Map(rows.map((r) => [r.pid, r]));
|
|
187
|
+
for (const row of rows) {
|
|
188
|
+
if (!row.isHelper)
|
|
189
|
+
continue;
|
|
190
|
+
row.startTime = captureProcessStartTime(row.pid);
|
|
191
|
+
if (row.ppid !== 1 && row.elapsedSec > STUCK_GRACE_SEC) {
|
|
192
|
+
const parent = rowByPid.get(row.ppid);
|
|
193
|
+
if (parent && parent.startTime === null) {
|
|
194
|
+
parent.startTime = captureProcessStartTime(row.ppid);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
169
197
|
}
|
|
198
|
+
const snapshots = rows.map((r) => ({
|
|
199
|
+
pid: r.pid,
|
|
200
|
+
ppid: r.ppid,
|
|
201
|
+
elapsedSec: r.elapsedSec,
|
|
202
|
+
startTime: r.startTime,
|
|
203
|
+
isHelper: r.isHelper,
|
|
204
|
+
}));
|
|
170
205
|
const plan = planKeychainReap(snapshots, Date.now(), stuckParentCandidates);
|
|
171
206
|
stuckParentCandidates = plan.nextCandidates;
|
|
172
207
|
for (const pid of plan.kill) {
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/** The current variable for transport/sync passphrases. */
|
|
2
|
+
export declare const SYNC_PASSPHRASE_ENV = "AGENTS_SYNC_PASSPHRASE";
|
|
3
|
+
/** The file-store master key, honoured for sync only as a deprecated fallback. */
|
|
4
|
+
export declare const LEGACY_PASSPHRASE_ENV = "AGENTS_SECRETS_PASSPHRASE";
|
|
5
|
+
/** Reset the one-shot warning latches. Tests only — production never re-warns. */
|
|
6
|
+
export declare function resetSyncPassphraseWarnings(): void;
|
|
7
|
+
/** Where a resolved sync passphrase came from, so callers can report honestly. */
|
|
8
|
+
export type SyncPassphraseSource = 'sync-env' | 'legacy-env' | null;
|
|
9
|
+
export interface ResolvedSyncPassphrase {
|
|
10
|
+
value: string | null;
|
|
11
|
+
source: SyncPassphraseSource;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Read the transport passphrase from the environment, preferring the current
|
|
15
|
+
* variable and falling back to the deprecated one with a single warning.
|
|
16
|
+
* Returns `{ value: null }` when neither is set — the caller decides whether to
|
|
17
|
+
* prompt (TTY) or fail (headless). Never prompts, never throws.
|
|
18
|
+
*/
|
|
19
|
+
export declare function resolveSyncPassphraseFromEnv(): ResolvedSyncPassphrase;
|
|
20
|
+
/**
|
|
21
|
+
* The one-shot reminder that an env-sourced passphrase is readable by other
|
|
22
|
+
* same-user processes. Separate from the deprecation notice above so a caller
|
|
23
|
+
* using the CURRENT variable still gets the readability warning exactly once.
|
|
24
|
+
*/
|
|
25
|
+
export declare function warnEnvPassphraseReadableOnce(): void;
|
|
26
|
+
/** The message a headless caller shows when no passphrase is available. */
|
|
27
|
+
export declare function missingSyncPassphraseMessage(): string;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The passphrase that seals a bundle for TRANSPORT — `secrets push`/`pull` and
|
|
3
|
+
* the portable `export --to-file` / `import --from-file` envelope.
|
|
4
|
+
*
|
|
5
|
+
* This is deliberately a DIFFERENT secret from the file store's master key
|
|
6
|
+
* (`AGENTS_SECRETS_PASSPHRASE`, `filestore.ts:getPassphrase`). The two were one
|
|
7
|
+
* variable, and that overload is what put a master key into `~/.zshenv` on seven
|
|
8
|
+
* worker boxes (RUSH-1968): the file store stopped needing a passphrase once it
|
|
9
|
+
* auto-provisioned a machine-local key, but headless `push`/`pull` still hard-
|
|
10
|
+
* failed without one, so an operator exported the master key fleet-wide to make
|
|
11
|
+
* sync work — handing every same-user process the key to the whole store.
|
|
12
|
+
*
|
|
13
|
+
* Splitting them means a box that only needs headless sync sets
|
|
14
|
+
* `AGENTS_SYNC_PASSPHRASE` and never has the store's master key in its
|
|
15
|
+
* environment at all.
|
|
16
|
+
*
|
|
17
|
+
* Resolution order:
|
|
18
|
+
* 1. `AGENTS_SYNC_PASSPHRASE` — the current name.
|
|
19
|
+
* 2. `AGENTS_SECRETS_PASSPHRASE` — deprecated fallback, warns once.
|
|
20
|
+
* 3. (caller prompts, or fails)
|
|
21
|
+
*
|
|
22
|
+
* The fallback exists so already-scripted CI and release automation keep working
|
|
23
|
+
* across the upgrade; it is not a second supported spelling.
|
|
24
|
+
*/
|
|
25
|
+
import chalk from 'chalk';
|
|
26
|
+
/** The current variable for transport/sync passphrases. */
|
|
27
|
+
export const SYNC_PASSPHRASE_ENV = 'AGENTS_SYNC_PASSPHRASE';
|
|
28
|
+
/** The file-store master key, honoured for sync only as a deprecated fallback. */
|
|
29
|
+
export const LEGACY_PASSPHRASE_ENV = 'AGENTS_SECRETS_PASSPHRASE';
|
|
30
|
+
/** Warn at most once per process: a `--all` push over many bundles must not
|
|
31
|
+
* flood stderr with the same notice. */
|
|
32
|
+
let deprecatedVarWarned = false;
|
|
33
|
+
/** Same, for the "this came from an env var at all" readability notice. */
|
|
34
|
+
let envPassphraseWarned = false;
|
|
35
|
+
/** Reset the one-shot warning latches. Tests only — production never re-warns. */
|
|
36
|
+
export function resetSyncPassphraseWarnings() {
|
|
37
|
+
deprecatedVarWarned = false;
|
|
38
|
+
envPassphraseWarned = false;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Read the transport passphrase from the environment, preferring the current
|
|
42
|
+
* variable and falling back to the deprecated one with a single warning.
|
|
43
|
+
* Returns `{ value: null }` when neither is set — the caller decides whether to
|
|
44
|
+
* prompt (TTY) or fail (headless). Never prompts, never throws.
|
|
45
|
+
*/
|
|
46
|
+
export function resolveSyncPassphraseFromEnv() {
|
|
47
|
+
const current = process.env[SYNC_PASSPHRASE_ENV];
|
|
48
|
+
if (current)
|
|
49
|
+
return { value: current, source: 'sync-env' };
|
|
50
|
+
const legacy = process.env[LEGACY_PASSPHRASE_ENV];
|
|
51
|
+
if (legacy) {
|
|
52
|
+
if (!deprecatedVarWarned) {
|
|
53
|
+
deprecatedVarWarned = true;
|
|
54
|
+
process.stderr.write(chalk.yellow(`warn: ${LEGACY_PASSPHRASE_ENV} is deprecated for sync — it is the file store's master key, ` +
|
|
55
|
+
`not a transport passphrase. Set ${SYNC_PASSPHRASE_ENV} instead; the old name still works ` +
|
|
56
|
+
'for now. Keeping the master key in the environment exposes the whole store to every ' +
|
|
57
|
+
'same-user process (RUSH-1968).\n'));
|
|
58
|
+
}
|
|
59
|
+
return { value: legacy, source: 'legacy-env' };
|
|
60
|
+
}
|
|
61
|
+
return { value: null, source: null };
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* The one-shot reminder that an env-sourced passphrase is readable by other
|
|
65
|
+
* same-user processes. Separate from the deprecation notice above so a caller
|
|
66
|
+
* using the CURRENT variable still gets the readability warning exactly once.
|
|
67
|
+
*/
|
|
68
|
+
export function warnEnvPassphraseReadableOnce() {
|
|
69
|
+
if (envPassphraseWarned)
|
|
70
|
+
return;
|
|
71
|
+
envPassphraseWarned = true;
|
|
72
|
+
process.stderr.write(chalk.yellow('warn: using a sync passphrase from the environment. Env vars are readable by other ' +
|
|
73
|
+
'same-user processes (/proc, ps, crash dumps, CI logs) — rotate after CI use.\n'));
|
|
74
|
+
}
|
|
75
|
+
/** The message a headless caller shows when no passphrase is available. */
|
|
76
|
+
export function missingSyncPassphraseMessage() {
|
|
77
|
+
return `A sync passphrase is required. Run from a TTY, or set ${SYNC_PASSPHRASE_ENV}.`;
|
|
78
|
+
}
|
|
@@ -126,6 +126,10 @@ export interface ActiveSession {
|
|
|
126
126
|
*/
|
|
127
127
|
lastActivityMs?: number;
|
|
128
128
|
status: ActiveStatus;
|
|
129
|
+
/** Indexed launch origin, backfilled by the sessions command for JSON consumers. */
|
|
130
|
+
origin?: 'cli' | 'routine';
|
|
131
|
+
/** Routine definition name when origin is `routine`. */
|
|
132
|
+
routineName?: string;
|
|
129
133
|
/**
|
|
130
134
|
* Foreground/background presence for the detach/attach model:
|
|
131
135
|
* `attached` — live interactive TUI you're watching;
|
package/dist/lib/session/db.js
CHANGED
|
@@ -12,7 +12,7 @@ import Database from '../sqlite.js';
|
|
|
12
12
|
import { parseSession } from './parse.js';
|
|
13
13
|
import { extractRecentDirectoriesTouched, extractTodoProgressFromEvents } from './state.js';
|
|
14
14
|
import { getSessionsDir, getSessionsDbPath } from '../state.js';
|
|
15
|
-
import { query as queryEvents } from '../events.js';
|
|
15
|
+
import { query as queryEvents, queryToolUsageForSessions } from '../events.js';
|
|
16
16
|
import { machineForSessionFile } from './origin-machine.js';
|
|
17
17
|
import { loadSessionActorIndex, readSessionActorRecord } from './actor-sidecar.js';
|
|
18
18
|
import { toolCallsFromEvents } from './tool-calls.js';
|
|
@@ -1648,6 +1648,12 @@ export function upsertSessionsBatch(entries) {
|
|
|
1648
1648
|
}
|
|
1649
1649
|
});
|
|
1650
1650
|
const writtenEntries = [];
|
|
1651
|
+
// Pre-compute browser/computer usage for all sessions outside the write
|
|
1652
|
+
// transaction. detectToolUsage scans all event log files (O(files) I/O
|
|
1653
|
+
// per call) and holding the SQLite write lock during that scan is what
|
|
1654
|
+
// causes the "DB locked" errors (RUSH-2006). One pass for the whole batch
|
|
1655
|
+
// costs O(files) total instead of O(N × files) inside the lock.
|
|
1656
|
+
const toolUsageBySession = queryToolUsageForSessions(new Set(enrichedEntries.map(e => e.meta.id)));
|
|
1651
1657
|
const txn = db.transaction((items) => {
|
|
1652
1658
|
// Re-read the ledger now that we hold the write lock. Any file committed
|
|
1653
1659
|
// by a concurrent process since our pre-scan is visible here.
|
|
@@ -1678,7 +1684,7 @@ export function upsertSessionsBatch(entries) {
|
|
|
1678
1684
|
// back when the error escapes `fn`, so catching + skipping here leaves the txn valid
|
|
1679
1685
|
// and committable. We deliberately do NOT stamp the ledger for a skipped row, so the
|
|
1680
1686
|
// next scan re-tries it (self-healing once the underlying parser is fixed).
|
|
1681
|
-
const toolUsage =
|
|
1687
|
+
const toolUsage = toolUsageBySession.get(meta.id) ?? { usedBrowser: false, usedComputer: false };
|
|
1682
1688
|
// claude/codex skip enrichCachedSessionMeta above (preserving their
|
|
1683
1689
|
// resumable-parse optimization) — write their pre-computed
|
|
1684
1690
|
// skillsUsed/slashCommandsUsed (folded incrementally by discover.ts's
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { type RotateCandidate } from '../rotate.js';
|
|
2
|
+
import type { AgentId } from '../types.js';
|
|
3
|
+
import type { SessionMeta } from './types.js';
|
|
4
|
+
export type SessionRecoveryTarget = {
|
|
5
|
+
mode: 'native';
|
|
6
|
+
agent: AgentId;
|
|
7
|
+
version: string;
|
|
8
|
+
reason: string;
|
|
9
|
+
} | {
|
|
10
|
+
mode: 'continue';
|
|
11
|
+
agent: AgentId;
|
|
12
|
+
version: string;
|
|
13
|
+
reason: string;
|
|
14
|
+
};
|
|
15
|
+
export declare class SessionRecoveryError extends Error {
|
|
16
|
+
constructor(message: string);
|
|
17
|
+
}
|
|
18
|
+
/** Canonical origin-device label for every recovery consumer. */
|
|
19
|
+
export declare function sessionOriginDevice(session: Pick<SessionMeta, 'machine'>, self?: string): string;
|
|
20
|
+
/** The peer that must execute recovery, or undefined when this is the origin. */
|
|
21
|
+
export declare function sessionRecoveryPeer(session: Pick<SessionMeta, 'machine'>, selfCheck?: (host: string) => boolean): string | undefined;
|
|
22
|
+
/** Whether an explicit placement names the session's origin device. */
|
|
23
|
+
export declare function sessionRecoveryDestinationMatches(session: Pick<SessionMeta, 'machine'>, requestedHost: string, self?: string): boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Decide how a durable session resumes on the device that owns it.
|
|
26
|
+
*
|
|
27
|
+
* Native resume is legal only in the exact origin version's isolated home and
|
|
28
|
+
* only while that account is healthy. Every other successful path stays on the
|
|
29
|
+
* same harness and uses `/continue`, whose indexed transcript reader can reach
|
|
30
|
+
* retained version trash. No healthy same-harness account is a loud failure.
|
|
31
|
+
*/
|
|
32
|
+
export declare function resolveSessionRecoveryFromCandidates(session: SessionMeta, candidates: RotateCandidate[], supportsNative?: (agent: AgentId, version?: string) => boolean): SessionRecoveryTarget;
|
|
33
|
+
export declare function resolveSessionRecovery(session: SessionMeta): Promise<SessionRecoveryTarget>;
|
|
34
|
+
/** Stable self-command used by focus, resume, and attach. The owning device runs
|
|
35
|
+
* the recovery resolver above; callers must not native-resume another version's
|
|
36
|
+
* isolated home themselves. */
|
|
37
|
+
export declare function sessionRecoveryRunArgs(session: Pick<SessionMeta, 'id'>): string[];
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { AGENTS } from '../agents.js';
|
|
2
|
+
import { isSelfHost } from '../devices/self-host.js';
|
|
3
|
+
import { nativeResume } from '../exec.js';
|
|
4
|
+
import { machineId, normalizeHost } from '../machine-id.js';
|
|
5
|
+
import { collectRunCandidates, formatNoHealthyAccountError, pickBalancedCandidate, readinessFromCandidate, } from '../rotate.js';
|
|
6
|
+
export class SessionRecoveryError extends Error {
|
|
7
|
+
constructor(message) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.name = 'SessionRecoveryError';
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
/** Canonical origin-device label for every recovery consumer. */
|
|
13
|
+
export function sessionOriginDevice(session, self = machineId()) {
|
|
14
|
+
return normalizeHost(session.machine ?? self);
|
|
15
|
+
}
|
|
16
|
+
/** The peer that must execute recovery, or undefined when this is the origin. */
|
|
17
|
+
export function sessionRecoveryPeer(session, selfCheck = isSelfHost) {
|
|
18
|
+
if (!session.machine || selfCheck(session.machine))
|
|
19
|
+
return undefined;
|
|
20
|
+
return normalizeHost(session.machine);
|
|
21
|
+
}
|
|
22
|
+
/** Whether an explicit placement names the session's origin device. */
|
|
23
|
+
export function sessionRecoveryDestinationMatches(session, requestedHost, self = machineId()) {
|
|
24
|
+
const requested = normalizeHost(requestedHost.split('@').pop() || requestedHost);
|
|
25
|
+
return requested === sessionOriginDevice(session, self);
|
|
26
|
+
}
|
|
27
|
+
function runnableSessionAgent(session) {
|
|
28
|
+
if (!(session.agent in AGENTS)) {
|
|
29
|
+
throw new SessionRecoveryError(`Session ${session.shortId} belongs to ${session.agent}, which is indexed but cannot be launched by agents run.`);
|
|
30
|
+
}
|
|
31
|
+
return session.agent;
|
|
32
|
+
}
|
|
33
|
+
function sourceReason(session, candidates) {
|
|
34
|
+
if (!session.version)
|
|
35
|
+
return 'the origin version was not recorded';
|
|
36
|
+
const source = candidates.find((candidate) => candidate.version === session.version);
|
|
37
|
+
if (!source)
|
|
38
|
+
return `origin ${session.agent}@${session.version} is not installed`;
|
|
39
|
+
const readiness = readinessFromCandidate(source);
|
|
40
|
+
return readiness.ready
|
|
41
|
+
? `origin ${session.agent}@${session.version} has no native resume form`
|
|
42
|
+
: `origin ${session.agent}@${session.version} is ${readiness.reason}`;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Decide how a durable session resumes on the device that owns it.
|
|
46
|
+
*
|
|
47
|
+
* Native resume is legal only in the exact origin version's isolated home and
|
|
48
|
+
* only while that account is healthy. Every other successful path stays on the
|
|
49
|
+
* same harness and uses `/continue`, whose indexed transcript reader can reach
|
|
50
|
+
* retained version trash. No healthy same-harness account is a loud failure.
|
|
51
|
+
*/
|
|
52
|
+
export function resolveSessionRecoveryFromCandidates(session, candidates, supportsNative = nativeResume) {
|
|
53
|
+
const agent = runnableSessionAgent(session);
|
|
54
|
+
const device = sessionOriginDevice(session);
|
|
55
|
+
const source = session.version
|
|
56
|
+
? candidates.find((candidate) => candidate.version === session.version)
|
|
57
|
+
: undefined;
|
|
58
|
+
const sourceReady = source ? readinessFromCandidate(source).ready : false;
|
|
59
|
+
// An exact healthy origin is deterministic: preserve its isolated home. If
|
|
60
|
+
// native resume is unavailable for that harness, /continue still launches in
|
|
61
|
+
// that same healthy home. Only an unusable/missing origin enters balanced
|
|
62
|
+
// account selection.
|
|
63
|
+
const selection = sourceReady
|
|
64
|
+
? { picked: source }
|
|
65
|
+
: pickBalancedCandidate(candidates);
|
|
66
|
+
if (!selection) {
|
|
67
|
+
const detail = formatNoHealthyAccountError(agent, 'balanced', candidates);
|
|
68
|
+
throw new SessionRecoveryError(`Cannot recover session ${session.shortId} on ${device}; origin ${agent}@${session.version ?? 'unknown'}. ${detail}`);
|
|
69
|
+
}
|
|
70
|
+
const version = selection.picked.version;
|
|
71
|
+
if (session.version === version && supportsNative(agent, version)) {
|
|
72
|
+
return {
|
|
73
|
+
mode: 'native',
|
|
74
|
+
agent,
|
|
75
|
+
version,
|
|
76
|
+
reason: `origin ${agent}@${version} is installed and healthy`,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
mode: 'continue',
|
|
81
|
+
agent,
|
|
82
|
+
version,
|
|
83
|
+
reason: `${sourceReason(session, candidates)}; continuing with healthy ${agent}@${version}`,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
export async function resolveSessionRecovery(session) {
|
|
87
|
+
const agent = runnableSessionAgent(session);
|
|
88
|
+
return resolveSessionRecoveryFromCandidates(session, await collectRunCandidates(agent));
|
|
89
|
+
}
|
|
90
|
+
/** Stable self-command used by focus, resume, and attach. The owning device runs
|
|
91
|
+
* the recovery resolver above; callers must not native-resume another version's
|
|
92
|
+
* isolated home themselves. */
|
|
93
|
+
export function sessionRecoveryRunArgs(session) {
|
|
94
|
+
return ['run', 'auto', '--resume', session.id, '--interactive'];
|
|
95
|
+
}
|
|
@@ -81,6 +81,8 @@ export interface GatherRemoteListOptions {
|
|
|
81
81
|
* know whether the match is unique or conflicting.
|
|
82
82
|
*/
|
|
83
83
|
isDefinitive?: (session: SessionMeta, machine: string) => boolean;
|
|
84
|
+
/** Per-peer deadline for slower indexed browse queries. */
|
|
85
|
+
timeoutMs?: number;
|
|
84
86
|
}
|
|
85
87
|
export declare function gatherRemoteList(forwardedArgs: string[], hosts?: string[], opts?: GatherRemoteListOptions): Promise<RemoteListResult>;
|
|
86
88
|
export interface RemoteToolSearchResult {
|
|
@@ -197,6 +197,7 @@ export async function gatherRemoteList(forwardedArgs, hosts, opts) {
|
|
|
197
197
|
args: forwardedArgs,
|
|
198
198
|
noFanoutEnv: NO_FANOUT_ENV,
|
|
199
199
|
hosts,
|
|
200
|
+
timeoutMs: opts?.timeoutMs,
|
|
200
201
|
earlyExit: opts?.isDefinitive ? { isDefinitive: opts.isDefinitive } : undefined,
|
|
201
202
|
parse: (stdout, machine) => parseRemoteListPayload(stdout, machine, safeResolver),
|
|
202
203
|
});
|
|
@@ -4,11 +4,11 @@ import type { ActiveSession } from './active.js';
|
|
|
4
4
|
* Short on purpose: live status (running/idle/waiting) must not go stale.
|
|
5
5
|
* The daemon warm tick uses the same cadence (see {@link SESSION_CACHE_WARM_INTERVAL_MS}).
|
|
6
6
|
*/
|
|
7
|
-
export declare const DEFAULT_ACTIVE_CACHE_MAX_AGE_MS
|
|
7
|
+
export declare const DEFAULT_ACTIVE_CACHE_MAX_AGE_MS: number;
|
|
8
8
|
/** Daemon warm interval — keep in sync with the setInterval in `lib/daemon.ts`. */
|
|
9
|
-
export declare const SESSION_CACHE_WARM_INTERVAL_MS
|
|
10
|
-
/** Kick off the first warm
|
|
11
|
-
export declare const SESSION_CACHE_WARM_KICKOFF_MS =
|
|
9
|
+
export declare const SESSION_CACHE_WARM_INTERVAL_MS: number;
|
|
10
|
+
/** Kick off the first warm 30s after daemon start (staggered off other ticks). */
|
|
11
|
+
export declare const SESSION_CACHE_WARM_KICKOFF_MS = 30000;
|
|
12
12
|
/** Snapshot scope: this host only, or a fleet-wide merge written by a reader. */
|
|
13
13
|
export type ActiveCacheScope = 'local' | 'fleet';
|
|
14
14
|
export interface ActiveSessionsSnapshot {
|
|
@@ -34,11 +34,11 @@ const IMMUTABLE_FILE = '.active-session-immutable.json';
|
|
|
34
34
|
* Short on purpose: live status (running/idle/waiting) must not go stale.
|
|
35
35
|
* The daemon warm tick uses the same cadence (see {@link SESSION_CACHE_WARM_INTERVAL_MS}).
|
|
36
36
|
*/
|
|
37
|
-
export const DEFAULT_ACTIVE_CACHE_MAX_AGE_MS =
|
|
37
|
+
export const DEFAULT_ACTIVE_CACHE_MAX_AGE_MS = 4 * 60_000;
|
|
38
38
|
/** Daemon warm interval — keep in sync with the setInterval in `lib/daemon.ts`. */
|
|
39
|
-
export const SESSION_CACHE_WARM_INTERVAL_MS =
|
|
40
|
-
/** Kick off the first warm
|
|
41
|
-
export const SESSION_CACHE_WARM_KICKOFF_MS =
|
|
39
|
+
export const SESSION_CACHE_WARM_INTERVAL_MS = 3 * 60_000;
|
|
40
|
+
/** Kick off the first warm 30s after daemon start (staggered off other ticks). */
|
|
41
|
+
export const SESSION_CACHE_WARM_KICKOFF_MS = 30_000;
|
|
42
42
|
/** Keys stored in the immutable memo (transcript-stable). */
|
|
43
43
|
export const IMMUTABLE_FIELD_KEYS = [
|
|
44
44
|
'topic',
|
package/dist/lib/shims.d.ts
CHANGED
|
@@ -77,7 +77,7 @@ export interface ConflictInfo {
|
|
|
77
77
|
* top-level entry add/remove — deep edits to plugin contents won't
|
|
78
78
|
* trigger auto-resync, run `agents sync` for that.
|
|
79
79
|
*/
|
|
80
|
-
export declare const SHIM_SCHEMA_VERSION =
|
|
80
|
+
export declare const SHIM_SCHEMA_VERSION = 29;
|
|
81
81
|
/**
|
|
82
82
|
* Generate the full bash shim script for the given agent. The returned string
|
|
83
83
|
* is written to ~/.agents/shims/{cliCommand} and made executable.
|
|
@@ -160,7 +160,7 @@ export declare function removeShim(agent: AgentId): boolean;
|
|
|
160
160
|
* the versioned home (installer run with GROK_HOME set, or grok
|
|
161
161
|
* self-update under the shim).
|
|
162
162
|
*/
|
|
163
|
-
export declare const VERSIONED_ALIAS_SCHEMA_VERSION =
|
|
163
|
+
export declare const VERSIONED_ALIAS_SCHEMA_VERSION = 15;
|
|
164
164
|
/**
|
|
165
165
|
* Agents whose config directory can be relocated by an environment variable
|
|
166
166
|
* (the per-agent `managedEnv` block in `generateVersionedAliasScript` below).
|
package/dist/lib/shims.js
CHANGED
|
@@ -223,7 +223,7 @@ async function promptConflictStrategy(conflictInfos) {
|
|
|
223
223
|
// The old dispatcher checked only the global dir, so a pinned grok that
|
|
224
224
|
// installed into the versioned home fell through to the "not installed"
|
|
225
225
|
// error.
|
|
226
|
-
export const SHIM_SCHEMA_VERSION =
|
|
226
|
+
export const SHIM_SCHEMA_VERSION = 29;
|
|
227
227
|
/** Internal marker string used to embed the schema version in shim scripts. */
|
|
228
228
|
const SHIM_VERSION_MARKER = 'agents-shim-version:';
|
|
229
229
|
function shellQuote(value) {
|
|
@@ -299,7 +299,18 @@ export OPENCODE_CONFIG_DIR="$VERSION_DIR/home/.config/opencode"
|
|
|
299
299
|
# mcp.json, sessions, skills, hooks). Point it at the versioned home.
|
|
300
300
|
export KIMI_CODE_HOME="$VERSION_DIR/home/${configDirName}"
|
|
301
301
|
`
|
|
302
|
-
: ''
|
|
302
|
+
: agent === 'muse'
|
|
303
|
+
? `
|
|
304
|
+
# Muse Code has no MUSE_CONFIG_DIR. It resolves config via XDG:
|
|
305
|
+
# $XDG_CONFIG_HOME/muse (settings, skills, hooks, auth)
|
|
306
|
+
# $XDG_DATA_HOME/muse (sessions, plugins)
|
|
307
|
+
# Pin XDG into the version home so managed runs never walk the adopt-time
|
|
308
|
+
# ~/.config/muse -> version-home symlink — Muse refuses agent-definition
|
|
309
|
+
# sources that are SymlinkOrReparse (exit 1). Same idea as CLAUDE_CONFIG_DIR.
|
|
310
|
+
export XDG_CONFIG_HOME="$VERSION_DIR/home/.config"
|
|
311
|
+
export XDG_DATA_HOME="$VERSION_DIR/home/.local/share"
|
|
312
|
+
`
|
|
313
|
+
: '';
|
|
303
314
|
const launchArgs = agent === 'codex' ? ` ${codexShimLaunchArgs()}` : '';
|
|
304
315
|
return `#!/bin/bash
|
|
305
316
|
# Auto-generated by agents-cli - do not edit
|
|
@@ -573,6 +584,18 @@ elif [ "$AGENT" = "muse" ]; then
|
|
|
573
584
|
esac
|
|
574
585
|
fi
|
|
575
586
|
fi
|
|
587
|
+
elif [ "$AGENT" = "warp" ]; then
|
|
588
|
+
# Warp Agent CLI installs a global, self-updating oz binary (brew cask on
|
|
589
|
+
# macOS, the oz-stable apt|yum|pacman package on Linux) -- a platform/package
|
|
590
|
+
# specific location, not ~/.local/bin -- so resolve it from PATH with the same
|
|
591
|
+
# shims-dir re-exec guard as droid/muse.
|
|
592
|
+
BINARY=$(adopted_original_bin || echo "")
|
|
593
|
+
if [ -z "$BINARY" ]; then
|
|
594
|
+
BINARY=$(command -v oz 2>/dev/null || echo "")
|
|
595
|
+
case "$(readlink -f "$BINARY" 2>/dev/null)" in
|
|
596
|
+
"$AGENTS_USER_DIR/.cache/shims/"*) BINARY="" ;;
|
|
597
|
+
esac
|
|
598
|
+
fi
|
|
576
599
|
else
|
|
577
600
|
BINARY="$VERSION_DIR/node_modules/.bin/$CLI_COMMAND"
|
|
578
601
|
fi
|
|
@@ -871,7 +894,7 @@ export function removeShim(agent) {
|
|
|
871
894
|
* the versioned home (installer run with GROK_HOME set, or grok
|
|
872
895
|
* self-update under the shim).
|
|
873
896
|
*/
|
|
874
|
-
export const VERSIONED_ALIAS_SCHEMA_VERSION =
|
|
897
|
+
export const VERSIONED_ALIAS_SCHEMA_VERSION = 15;
|
|
875
898
|
/** Internal marker string used to embed the schema version in versioned alias scripts. */
|
|
876
899
|
const VERSIONED_ALIAS_VERSION_MARKER = 'agents-versioned-alias-version:';
|
|
877
900
|
// The version string is interpolated into a generated bash script and into
|
|
@@ -898,7 +921,7 @@ function assertSafeVersion(version) {
|
|
|
898
921
|
* KEEP IN SYNC with the `managedEnv` switch in `generateVersionedAliasScript`.
|
|
899
922
|
* The colocated test `shims.isolation-capability.test.ts` enforces this.
|
|
900
923
|
*/
|
|
901
|
-
export const CONFIG_ENV_ISOLATED_AGENTS = ['claude', 'codex', 'copilot', 'grok', 'kimi', 'opencode'];
|
|
924
|
+
export const CONFIG_ENV_ISOLATED_AGENTS = ['claude', 'codex', 'copilot', 'grok', 'kimi', 'opencode', 'muse'];
|
|
902
925
|
/**
|
|
903
926
|
* Whether an agent supports a clean `--isolated` install — i.e. its config
|
|
904
927
|
* location can be redirected by an env var so the isolated copy stays fully
|
|
@@ -956,7 +979,15 @@ export OPENCODE_CONFIG_DIR="$HOME/.agents/.history/versions/${agent}/${version}/
|
|
|
956
979
|
# mcp.json, sessions, skills, hooks). Point direct aliases at the versioned home.
|
|
957
980
|
export KIMI_CODE_HOME="$HOME/.agents/.history/versions/${agent}/${version}/home/${configDirName}"
|
|
958
981
|
`
|
|
959
|
-
: ''
|
|
982
|
+
: agent === 'muse'
|
|
983
|
+
? `
|
|
984
|
+
# Muse Code: no dedicated config env var. Pin XDG so config/sessions live under
|
|
985
|
+
# the version home as real directories (not via the adopt symlink at
|
|
986
|
+
# ~/.config/muse, which Muse rejects with SymlinkOrReparse).
|
|
987
|
+
export XDG_CONFIG_HOME="$HOME/.agents/.history/versions/${agent}/${version}/home/.config"
|
|
988
|
+
export XDG_DATA_HOME="$HOME/.agents/.history/versions/${agent}/${version}/home/.local/share"
|
|
989
|
+
`
|
|
990
|
+
: '';
|
|
960
991
|
const launchArgs = agent === 'codex' ? ` ${codexShimLaunchArgs()}` : '';
|
|
961
992
|
// Resolve the binary the same way the main shim does (see generateShimScript).
|
|
962
993
|
// Grok and Droid do NOT ship into node_modules/.bin — Grok downloads a native
|
|
@@ -1022,7 +1053,15 @@ else
|
|
|
1022
1053
|
"$HOME/.agents/.cache/shims/"*) BINARY="" ;;
|
|
1023
1054
|
esac
|
|
1024
1055
|
fi`
|
|
1025
|
-
:
|
|
1056
|
+
: agent === 'warp'
|
|
1057
|
+
? `# Warp Agent CLI installs a global self-updating \`oz\` binary (brew cask on
|
|
1058
|
+
# macOS, oz-stable apt|yum|pacman on Linux) — a platform-specific location, not
|
|
1059
|
+
# ~/.local/bin — so resolve it from PATH, refusing anything under our shims dir.
|
|
1060
|
+
BINARY=$(command -v oz 2>/dev/null || echo "")
|
|
1061
|
+
case "$BINARY" in
|
|
1062
|
+
"$HOME/.agents/.cache/shims/"*) BINARY="" ;;
|
|
1063
|
+
esac`
|
|
1064
|
+
: `BINARY="${versionDir}/node_modules/.bin/${agentConfig.cliCommand}"`;
|
|
1026
1065
|
return `#!/bin/bash
|
|
1027
1066
|
# Auto-generated by agents-cli - do not edit
|
|
1028
1067
|
# ${VERSIONED_ALIAS_VERSION_MARKER} ${VERSIONED_ALIAS_SCHEMA_VERSION}
|
package/dist/lib/signin-badge.js
CHANGED
|
@@ -24,6 +24,8 @@ export function loginHint(agentId) {
|
|
|
24
24
|
return `${cli}, then /login`;
|
|
25
25
|
case 'codex':
|
|
26
26
|
case 'grok':
|
|
27
|
+
// Warp Agent CLI: `oz login` opens a browser sign-in (or set WARP_API_KEY).
|
|
28
|
+
case 'warp':
|
|
27
29
|
return `${cli} login`;
|
|
28
30
|
case 'opencode':
|
|
29
31
|
return `${cli} auth login`;
|
|
@@ -51,6 +51,7 @@ export declare const loadFork: ModuleLoader;
|
|
|
51
51
|
export declare const loadDefaults: ModuleLoader;
|
|
52
52
|
export declare const loadSet: ModuleLoader;
|
|
53
53
|
export declare const loadModels: ModuleLoader;
|
|
54
|
+
export declare const loadModes: ModuleLoader;
|
|
54
55
|
export declare const loadPrune: ModuleLoader;
|
|
55
56
|
export declare const loadTrash: ModuleLoader;
|
|
56
57
|
export declare const loadRestore: ModuleLoader;
|
|
@@ -130,3 +131,18 @@ export declare const LAZY_COMMAND_NAMES: ReadonlySet<string>;
|
|
|
130
131
|
* are handled directly in src/index.ts.
|
|
131
132
|
*/
|
|
132
133
|
export declare const COMMAND_LOADERS: Record<string, ModuleLoader[]>;
|
|
134
|
+
/**
|
|
135
|
+
* Every top-level command name the CLI answers to — the loader table plus the
|
|
136
|
+
* inline aliases/tombstones above. This is the "does this command exist?"
|
|
137
|
+
* predicate for code that runs BEFORE commander parses, most importantly the
|
|
138
|
+
* `--host`/`--device` router (lib/hosts/passthrough.ts): without it a typo'd
|
|
139
|
+
* command carrying `--host` reported a flag-support error instead of
|
|
140
|
+
* `unknown command` (RUSH-2022).
|
|
141
|
+
*
|
|
142
|
+
* Commander sub-aliases (`sessions ls`, `teams rm`, …) are deliberately absent —
|
|
143
|
+
* this set is top-level only. `command-registry.test.ts` pins it against the real
|
|
144
|
+
* registered command tree so a new command can never drift out of it.
|
|
145
|
+
*/
|
|
146
|
+
export declare const KNOWN_TOP_LEVEL_COMMANDS: ReadonlySet<string>;
|
|
147
|
+
/** Whether `name` is a top-level command this CLI registers. See {@link KNOWN_TOP_LEVEL_COMMANDS}. */
|
|
148
|
+
export declare function isKnownTopLevelCommand(name: string): boolean;
|