@phnx-labs/agents-cli 1.22.33 → 1.22.35
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 +55 -0
- package/README.md +9 -1
- package/dist/bin/agents +0 -0
- package/dist/commands/accounts.js +3 -3
- package/dist/commands/browser-sessions-picker.d.ts +16 -0
- package/dist/commands/browser-sessions-picker.js +179 -0
- package/dist/commands/browser.js +9 -4
- package/dist/commands/hosts.js +1 -5
- package/dist/commands/inspect.js +174 -41
- package/dist/commands/message.d.ts +6 -1
- package/dist/commands/message.js +60 -3
- package/dist/commands/sessions.d.ts +54 -4
- package/dist/commands/sessions.js +252 -46
- package/dist/commands/share.d.ts +18 -0
- package/dist/commands/share.js +108 -0
- package/dist/commands/ssh.js +17 -5
- package/dist/commands/teams.d.ts +28 -0
- package/dist/commands/teams.js +148 -13
- package/dist/commands/upgrade.d.ts +7 -0
- package/dist/commands/upgrade.js +10 -0
- package/dist/commands/watchdog.d.ts +2 -0
- package/dist/commands/watchdog.js +112 -27
- package/dist/index.js +51 -59
- package/dist/lib/agents.js +6 -0
- package/dist/lib/browser/sessions-list.d.ts +81 -0
- package/dist/lib/browser/sessions-list.js +179 -4
- package/dist/lib/codex-policy.d.ts +9 -1
- package/dist/lib/codex-policy.js +17 -2
- package/dist/lib/daemon.js +45 -5
- package/dist/lib/devices/connect.d.ts +33 -0
- package/dist/lib/devices/connect.js +61 -3
- package/dist/lib/devices/doctor-findings.d.ts +4 -2
- package/dist/lib/devices/doctor-findings.js +4 -2
- package/dist/lib/exec.js +65 -8
- package/dist/lib/help.d.ts +3 -2
- package/dist/lib/help.js +4 -0
- package/dist/lib/hosts/dispatch.d.ts +2 -32
- package/dist/lib/hosts/dispatch.js +6 -61
- package/dist/lib/hosts/tasks.d.ts +7 -0
- package/dist/lib/hosts/tasks.js +9 -0
- package/dist/lib/mailbox-target.d.ts +27 -0
- package/dist/lib/mailbox-target.js +21 -0
- package/dist/lib/mcp.d.ts +10 -0
- package/dist/lib/mcp.js +21 -2
- 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 +11 -0
- package/dist/lib/migrate.js +40 -0
- package/dist/lib/project-key.d.ts +17 -0
- package/dist/lib/project-key.js +26 -0
- package/dist/lib/project-root.d.ts +47 -0
- package/dist/lib/project-root.js +68 -0
- 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 +9 -2
- package/dist/lib/secrets/agent.js +52 -7
- package/dist/lib/secrets/reaper.d.ts +24 -3
- package/dist/lib/secrets/reaper.js +55 -6
- package/dist/lib/session/discover.js +12 -0
- package/dist/lib/session/render.d.ts +2 -0
- package/dist/lib/session/render.js +1 -1
- package/dist/lib/share/delete.d.ts +93 -0
- package/dist/lib/share/delete.js +127 -0
- package/dist/lib/shims.js +48 -4
- package/dist/lib/startup/command-registry.d.ts +1 -0
- package/dist/lib/startup/command-registry.js +11 -3
- package/dist/lib/startup/root-command.d.ts +3 -0
- package/dist/lib/startup/root-command.js +10 -0
- package/dist/lib/teams/agents.d.ts +136 -6
- package/dist/lib/teams/agents.js +324 -58
- package/dist/lib/teams/worktree.d.ts +39 -2
- package/dist/lib/teams/worktree.js +60 -4
- package/dist/lib/types.d.ts +11 -0
- package/dist/lib/versions.js +2 -2
- package/dist/lib/watchdog/history.d.ts +20 -0
- package/dist/lib/watchdog/history.js +46 -0
- package/dist/lib/watchdog/log.d.ts +16 -1
- package/dist/lib/watchdog/log.js +82 -2
- package/dist/lib/watchdog/runner.d.ts +12 -0
- package/dist/lib/watchdog/runner.js +20 -0
- package/package.json +1 -1
package/dist/lib/shims.js
CHANGED
|
@@ -236,6 +236,43 @@ function codexShimLaunchArgs() {
|
|
|
236
236
|
...codexPolicyArgs('edit'),
|
|
237
237
|
].map(shellQuote).join(' ');
|
|
238
238
|
}
|
|
239
|
+
/**
|
|
240
|
+
* The `exec` tail for a generated shim. For most agents this is a plain
|
|
241
|
+
* `exec "$BINARY"<launchArgs> "$@"`.
|
|
242
|
+
*
|
|
243
|
+
* Codex is special: its `workspace-write` sandbox hardcodes any `.agents/` (and
|
|
244
|
+
* `.codex/`) directory read-only, but agents-cli keeps every worktree at
|
|
245
|
+
* `<repo>/.agents/worktrees/<slug>`, so an in-repo build under a static
|
|
246
|
+
* shim would hit `EROFS`. The `agents run codex` path fixes this by adding
|
|
247
|
+
* `<repo>/.agents` to the profile's `workspace_roots` (see codexEditWritableRoots
|
|
248
|
+
* / repoAgentsDirForCwd), but a static shim has no cwd at generation time. So the
|
|
249
|
+
* shim resolves the repo's `.agents` from `$PWD` at RUN time — worktree-aware,
|
|
250
|
+
* mirroring repoAgentsDirForCwd — and passes it via Codex's own `--add-dir`
|
|
251
|
+
* (verified to compose with the `agents-edit` profile). The resolution is inline
|
|
252
|
+
* bash because the shim is the launch hot path and must not spawn Node to compute
|
|
253
|
+
* one directory. `--add-dir` is added only when the resolved `.agents` exists.
|
|
254
|
+
*/
|
|
255
|
+
function shimExecTail(agent, launchArgs) {
|
|
256
|
+
if (agent !== 'codex')
|
|
257
|
+
return `exec "$BINARY"${launchArgs} "$@"`;
|
|
258
|
+
return `_repo_agents=""
|
|
259
|
+
case "$PWD" in
|
|
260
|
+
*/.agents/worktrees/*) _repo_agents="\${PWD%%/.agents/worktrees/*}/.agents" ;;
|
|
261
|
+
*)
|
|
262
|
+
_d="$PWD"
|
|
263
|
+
# Stop before $HOME so a dotfiles repo at $HOME is not treated as the project
|
|
264
|
+
# root (mirrors repoRootForCwd's home exclusion in project-key.ts).
|
|
265
|
+
while [ -n "$_d" ] && [ "$_d" != "/" ] && [ "$_d" != "$HOME" ]; do
|
|
266
|
+
if [ -e "$_d/.git" ]; then _repo_agents="$_d/.agents"; break; fi
|
|
267
|
+
_d=$(dirname "$_d")
|
|
268
|
+
done
|
|
269
|
+
;;
|
|
270
|
+
esac
|
|
271
|
+
if [ -n "$_repo_agents" ] && [ -d "$_repo_agents" ]; then
|
|
272
|
+
exec "$BINARY"${launchArgs} --add-dir "$_repo_agents" "$@"
|
|
273
|
+
fi
|
|
274
|
+
exec "$BINARY"${launchArgs} "$@"`;
|
|
275
|
+
}
|
|
239
276
|
function getAgentsBinForGeneratedShim() {
|
|
240
277
|
return path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', 'index.js');
|
|
241
278
|
}
|
|
@@ -707,7 +744,7 @@ if [ "\$LAUNCH_SKIP" = "0" ]; then
|
|
|
707
744
|
"\$AGENTS_BIN" sync --agent "\$AGENT" --agent-version "\$VERSION" --launch --cwd "\$PWD" --quiet 2>/dev/null || true
|
|
708
745
|
fi
|
|
709
746
|
|
|
710
|
-
|
|
747
|
+
${shimExecTail(agent, launchArgs)}
|
|
711
748
|
`;
|
|
712
749
|
}
|
|
713
750
|
/**
|
|
@@ -933,7 +970,7 @@ function assertSafeVersion(version) {
|
|
|
933
970
|
* KEEP IN SYNC with the `managedEnv` switch in `generateVersionedAliasScript`.
|
|
934
971
|
* The colocated test `shims.isolation-capability.test.ts` enforces this.
|
|
935
972
|
*/
|
|
936
|
-
export const CONFIG_ENV_ISOLATED_AGENTS = ['claude', 'codex', 'copilot', 'grok', 'kimi', 'opencode', 'muse'];
|
|
973
|
+
export const CONFIG_ENV_ISOLATED_AGENTS = ['claude', 'codex', 'copilot', 'cursor', 'grok', 'kimi', 'opencode', 'muse'];
|
|
937
974
|
/**
|
|
938
975
|
* Whether an agent supports a clean `--isolated` install — i.e. its config
|
|
939
976
|
* location can be redirected by an env var so the isolated copy stays fully
|
|
@@ -999,7 +1036,14 @@ export KIMI_CODE_HOME="$HOME/.agents/.history/versions/${agent}/${version}/home/
|
|
|
999
1036
|
export XDG_CONFIG_HOME="$HOME/.agents/.history/versions/${agent}/${version}/home/.config"
|
|
1000
1037
|
export XDG_DATA_HOME="$HOME/.agents/.history/versions/${agent}/${version}/home/.local/share"
|
|
1001
1038
|
`
|
|
1002
|
-
: ''
|
|
1039
|
+
: agent === 'cursor'
|
|
1040
|
+
? `
|
|
1041
|
+
# Cursor: no config-dir env var. Its OAuth token (the login gate) lives at
|
|
1042
|
+
# $XDG_CONFIG_HOME/cursor/auth.json, so pin XDG_CONFIG_HOME at the version home
|
|
1043
|
+
# to isolate each account's login for direct aliases (parity with buildExecEnv).
|
|
1044
|
+
export XDG_CONFIG_HOME="$HOME/.agents/.history/versions/${agent}/${version}/home/.config"
|
|
1045
|
+
`
|
|
1046
|
+
: '';
|
|
1003
1047
|
const launchArgs = agent === 'codex' ? ` ${codexShimLaunchArgs()}` : '';
|
|
1004
1048
|
// Resolve the binary the same way the main shim does (see generateShimScript).
|
|
1005
1049
|
// Grok and Droid do NOT ship into node_modules/.bin — Grok downloads a native
|
|
@@ -1087,7 +1131,7 @@ if [ -z "$BINARY" ] || [ ! -x "$BINARY" ]; then
|
|
|
1087
1131
|
fi
|
|
1088
1132
|
${managedEnv}
|
|
1089
1133
|
|
|
1090
|
-
|
|
1134
|
+
${shimExecTail(agent, launchArgs)}
|
|
1091
1135
|
`;
|
|
1092
1136
|
}
|
|
1093
1137
|
/**
|
|
@@ -98,6 +98,7 @@ export declare const loadPush: ModuleLoader;
|
|
|
98
98
|
export declare const loadRepo: ModuleLoader;
|
|
99
99
|
export declare const loadSetup: ModuleLoader;
|
|
100
100
|
export declare const loadUninstall: ModuleLoader;
|
|
101
|
+
export declare const loadUpgrade: ModuleLoader;
|
|
101
102
|
export declare const loadSessions: ModuleLoader;
|
|
102
103
|
export declare const loadTeams: ModuleLoader;
|
|
103
104
|
export declare const loadCloud: ModuleLoader;
|
|
@@ -19,6 +19,9 @@
|
|
|
19
19
|
* `cleanup` subcommand to it), in that order — see commands/prune.ts.
|
|
20
20
|
*/
|
|
21
21
|
import { Command } from 'commander';
|
|
22
|
+
import { readFileSync } from 'node:fs';
|
|
23
|
+
import { fileURLToPath } from 'node:url';
|
|
24
|
+
import { configureRootCommand } from './root-command.js';
|
|
22
25
|
// One loader per command module. Each dynamically imports the module and hands
|
|
23
26
|
// back its register function. Kept as named consts so src/index.ts can compose
|
|
24
27
|
// them into the exact main-branch registration order for the slow path.
|
|
@@ -98,6 +101,7 @@ export const loadPush = async () => (await import('../../commands/push.js')).reg
|
|
|
98
101
|
export const loadRepo = async () => (await import('../../commands/repo.js')).registerRepoCommands;
|
|
99
102
|
export const loadSetup = async () => (await import('../../commands/setup.js')).registerSetupCommand;
|
|
100
103
|
export const loadUninstall = async () => (await import('../../commands/uninstall.js')).registerUninstallCommands;
|
|
104
|
+
export const loadUpgrade = async () => (await import('../../commands/upgrade.js')).registerUpgradeCommand;
|
|
101
105
|
export const loadSessions = async () => (await import('../../commands/sessions.js')).registerSessionsCommands;
|
|
102
106
|
export const loadTeams = async () => (await import('../../commands/teams.js')).registerTeamsCommands;
|
|
103
107
|
export const loadCloud = async () => (await import('../../commands/cloud.js')).registerCloudCommands;
|
|
@@ -247,6 +251,7 @@ export const COMMAND_LOADERS = {
|
|
|
247
251
|
repo: [loadRepo],
|
|
248
252
|
setup: [loadSetup],
|
|
249
253
|
uninstall: [loadUninstall],
|
|
254
|
+
upgrade: [loadUpgrade],
|
|
250
255
|
sessions: [loadSessions],
|
|
251
256
|
// Observe-umbrella alias of sessions --active (same lazy module).
|
|
252
257
|
roster: [loadSessions],
|
|
@@ -263,6 +268,9 @@ export const COMMAND_LOADERS = {
|
|
|
263
268
|
mailbox: [loadMailboxes],
|
|
264
269
|
serve: [loadServe],
|
|
265
270
|
share: [loadShare],
|
|
271
|
+
// `unshare` is a top-level convenience alias of `share delete` (see
|
|
272
|
+
// commands/share.ts) — same module, registered as its own program.command().
|
|
273
|
+
unshare: [loadShare],
|
|
266
274
|
audit: [loadAudit],
|
|
267
275
|
webhook: [loadWebhook],
|
|
268
276
|
funnel: [loadFunnel],
|
|
@@ -272,7 +280,7 @@ export const COMMAND_LOADERS = {
|
|
|
272
280
|
/**
|
|
273
281
|
* Top-level names that {@link COMMAND_LOADERS} does not carry because they are
|
|
274
282
|
* registered inline in src/index.ts — closures over entry-point state (the
|
|
275
|
-
* deprecated aliases and tombstones) plus the internal
|
|
283
|
+
* deprecated aliases and tombstones) plus the internal command. They are
|
|
276
284
|
* real commands, so anything that asks "does this command exist?" must count them.
|
|
277
285
|
*/
|
|
278
286
|
const INLINE_COMMAND_NAMES = [
|
|
@@ -284,7 +292,6 @@ const INLINE_COMMAND_NAMES = [
|
|
|
284
292
|
'resources', // tombstone -> view --merged
|
|
285
293
|
'hq', // tombstone
|
|
286
294
|
'_internal',
|
|
287
|
-
'upgrade',
|
|
288
295
|
];
|
|
289
296
|
/**
|
|
290
297
|
* Every top-level command name the CLI answers to — the loader table plus the
|
|
@@ -318,7 +325,8 @@ export function isKnownTopLevelCommand(name) {
|
|
|
318
325
|
* are closures over entry-point state that src/index.ts registers directly.
|
|
319
326
|
*/
|
|
320
327
|
export async function buildFullCommandTree() {
|
|
321
|
-
const
|
|
328
|
+
const packageJson = JSON.parse(readFileSync(fileURLToPath(new URL('../../../package.json', import.meta.url)), 'utf8'));
|
|
329
|
+
const program = configureRootCommand(new Command(), 'agents', packageJson.version);
|
|
322
330
|
const done = new Set();
|
|
323
331
|
for (const loaders of Object.values(COMMAND_LOADERS)) {
|
|
324
332
|
for (const loader of loaders) {
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** Configure the public root surface shared by the live CLI and reference generator. */
|
|
2
|
+
export function configureRootCommand(program, name, version) {
|
|
3
|
+
return program
|
|
4
|
+
.name(name)
|
|
5
|
+
.description('Environment manager for AI agents')
|
|
6
|
+
.version(version)
|
|
7
|
+
.option('--verbose', 'Show startup self-heal details on stderr')
|
|
8
|
+
.helpOption('-h, --help', 'Show help')
|
|
9
|
+
.addHelpCommand(false);
|
|
10
|
+
}
|
|
@@ -15,6 +15,45 @@ export declare enum AgentStatus {
|
|
|
15
15
|
FAILED = "failed",
|
|
16
16
|
STOPPED = "stopped"
|
|
17
17
|
}
|
|
18
|
+
/**
|
|
19
|
+
* The statuses a teammate can never leave — its process has run and finished
|
|
20
|
+
* (or been stopped). Everything else (pending, running) is still live work.
|
|
21
|
+
*
|
|
22
|
+
* This is the ONLY set that retention (cleanupOldAgents) may reap: a `pending`
|
|
23
|
+
* teammate has not launched yet and a `running` one is doing work, so deleting
|
|
24
|
+
* either is data loss. Treating "not running" as "completed" was the RUSH-2356
|
|
25
|
+
* bug — it swept live `pending` `--after` teammates past the 50-record cap.
|
|
26
|
+
*/
|
|
27
|
+
export declare const TERMINAL_STATUSES: ReadonlySet<AgentStatus>;
|
|
28
|
+
/** True when a teammate has reached a terminal (completed/failed/stopped) status. */
|
|
29
|
+
export declare function isTerminalStatus(status: AgentStatus): boolean;
|
|
30
|
+
/**
|
|
31
|
+
* One remote teammate's liveness, resolved by a single host probe. Three states,
|
|
32
|
+
* kept distinct on purpose (RUSH-2366):
|
|
33
|
+
* - alive=true → process still running.
|
|
34
|
+
* - exitFilePresent=true → the `.exit` sentinel exists;
|
|
35
|
+
* `exit` is its (possibly empty, mid-write) contents.
|
|
36
|
+
* - alive=false && !exitFilePresent ("GONE") → the process is gone AND the
|
|
37
|
+
* wrapper never recorded a sentinel — it was killed / the box died. There is
|
|
38
|
+
* no exit code coming, so this MUST resolve terminal instead of "running
|
|
39
|
+
* forever". Collapsing GONE into the empty-`.exit` case is exactly the bug
|
|
40
|
+
* that left a dead `--device` teammate RUNNING indefinitely.
|
|
41
|
+
*/
|
|
42
|
+
export interface RemoteLivenessSnapshot {
|
|
43
|
+
alive: boolean;
|
|
44
|
+
exit: string | null;
|
|
45
|
+
exitFilePresent: boolean;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* The per-teammate shell that emits `<id> <ALIVE|EXITED|GONE> <codeOrEmpty>`.
|
|
49
|
+
* Shared by the batched prefetch (many teammates, one round-trip) and the
|
|
50
|
+
* direct single-teammate probe, so both classify liveness identically.
|
|
51
|
+
* `exitFile` is interpolated UNQUOTED so `$HOME` in the dispatch path expands on
|
|
52
|
+
* the remote shell (shellQuote would defeat the `[ -f ]` test).
|
|
53
|
+
*/
|
|
54
|
+
export declare function remoteLivenessSnippet(id: string, exitFile: string, pid: number): string;
|
|
55
|
+
/** Parse one `<STATE> <codeOrEmpty>` reading into a snapshot. */
|
|
56
|
+
export declare function parseRemoteLivenessState(state: string, code: string | undefined): RemoteLivenessSnapshot;
|
|
18
57
|
/** Task type label for Software Factory workflows. Drives planner fan-out. Optional — teammates without a task_type work exactly as before. */
|
|
19
58
|
export type TaskType = 'plan' | 'implement' | 'test' | 'review' | 'bugfix' | 'docs';
|
|
20
59
|
export declare const VALID_TASK_TYPES: readonly TaskType[];
|
|
@@ -162,10 +201,7 @@ export declare class AgentProcess {
|
|
|
162
201
|
remoteLog: string | null;
|
|
163
202
|
remoteExit: string | null;
|
|
164
203
|
remoteLogOffset: number;
|
|
165
|
-
remotePollSnapshot:
|
|
166
|
-
alive: boolean;
|
|
167
|
-
exit: string | null;
|
|
168
|
-
} | null;
|
|
204
|
+
remotePollSnapshot: RemoteLivenessSnapshot | null;
|
|
169
205
|
private eventsCache;
|
|
170
206
|
private lastReadPos;
|
|
171
207
|
private baseDir;
|
|
@@ -231,6 +267,13 @@ export declare class AgentProcess {
|
|
|
231
267
|
* --active --local` take ~4.3s on a box with 30 completed teammates.
|
|
232
268
|
*/
|
|
233
269
|
private syncRemoteMirror;
|
|
270
|
+
/**
|
|
271
|
+
* One-shot direct liveness probe for a single remote teammate — the fallback
|
|
272
|
+
* used outside a batched supervisor wave (a bare `teams status`, `mgr.get()`
|
|
273
|
+
* for `teams resume`). Returns null on a transient ssh failure so the caller
|
|
274
|
+
* leaves the teammate RUNNING rather than reaping it on a dropped connection.
|
|
275
|
+
*/
|
|
276
|
+
private probeRemoteLiveness;
|
|
234
277
|
/** Reset the local stdout cursor for a newly truncated resume log. */
|
|
235
278
|
resetLogReadPosition(): number;
|
|
236
279
|
/** Restore the cursor when a resume transaction puts the prior log back. */
|
|
@@ -263,6 +306,24 @@ export declare class AgentProcess {
|
|
|
263
306
|
saveMeta(): Promise<void>;
|
|
264
307
|
static loadFromDisk(agentId: string, baseDir?: string | null): Promise<AgentProcess | null>;
|
|
265
308
|
isProcessAlive(): boolean;
|
|
309
|
+
/**
|
|
310
|
+
* Read just the persisted status + completion time from meta.json, without
|
|
311
|
+
* reconstructing the whole teammate. Returns null when there is no readable
|
|
312
|
+
* record on disk. Used to detect that ANOTHER process (a `teams stop`, a
|
|
313
|
+
* sibling supervisor) has already moved this teammate to a terminal status.
|
|
314
|
+
*/
|
|
315
|
+
private readDiskStatus;
|
|
316
|
+
/**
|
|
317
|
+
* If this in-memory teammate is still non-terminal but disk already shows a
|
|
318
|
+
* terminal status, adopt the disk state. Returns true when it did.
|
|
319
|
+
*
|
|
320
|
+
* This is the guard against the stale-manager race (RUSH-2366): a long-lived
|
|
321
|
+
* supervisor holding a teammate as `running` must never re-persist that stale
|
|
322
|
+
* `running` over a `stopped`/`failed`/`completed` another process just wrote
|
|
323
|
+
* (e.g. an explicit `teams stop` in a separate CLI invocation). A terminal
|
|
324
|
+
* status is a one-way latch, so disk-terminal always wins over memory-running.
|
|
325
|
+
*/
|
|
326
|
+
private adoptDiskTerminalIfNewer;
|
|
266
327
|
/**
|
|
267
328
|
* @param opts.skipRemote A `--local` caller (RUSH-2118): a distributed
|
|
268
329
|
* teammate is never dialed — its in-memory state (already loaded from
|
|
@@ -333,6 +394,13 @@ export declare class AgentManager {
|
|
|
333
394
|
*/
|
|
334
395
|
private localOnly;
|
|
335
396
|
private constructorAgentsDir;
|
|
397
|
+
/**
|
|
398
|
+
* One-shot memo of the last `validateAddPreconditions` result, so the
|
|
399
|
+
* command-layer pre-worktree call and spawn()'s own call don't each pay a
|
|
400
|
+
* full `listAll()` status refresh (a round of SSH probes on a `--device`
|
|
401
|
+
* team). Consumed by the first matching call — see that method.
|
|
402
|
+
*/
|
|
403
|
+
private validatedAdd;
|
|
336
404
|
constructor(maxAgents?: number, agentsDir?: string | null, defaultMode?: Mode | null, filterByCwd?: string | null, cleanupAgeDays?: number, localOnly?: boolean);
|
|
337
405
|
private initialize;
|
|
338
406
|
private doInitialize;
|
|
@@ -350,11 +418,66 @@ export declare class AgentManager {
|
|
|
350
418
|
* manager is alive — the supervisor loop calls this each wave so
|
|
351
419
|
* dynamically-added teammates get picked up.
|
|
352
420
|
*
|
|
353
|
-
*
|
|
354
|
-
*
|
|
421
|
+
* For a teammate ALREADY cached, refreshes it only when disk has latched it
|
|
422
|
+
* terminal while the cache still holds it non-terminal — the case where
|
|
423
|
+
* another process (e.g. `agents teams stop` in a separate CLI invocation)
|
|
424
|
+
* moved it to `stopped`/`failed` and this long-lived manager would otherwise
|
|
425
|
+
* never see it and re-persist a stale `running` (RUSH-2366). A still-live
|
|
426
|
+
* cached teammate is left untouched; updateStatusFromProcess() owns that path.
|
|
355
427
|
*/
|
|
356
428
|
rescanFromDisk(): Promise<number>;
|
|
357
429
|
private loadExistingAgents;
|
|
430
|
+
/**
|
|
431
|
+
* Validate an add's name uniqueness and `--after` dependency graph, without
|
|
432
|
+
* any side effects. Throws a user-facing error on: a duplicate name, `--after`
|
|
433
|
+
* without `--name`, an unknown dependency, or a cycle. Returns the cleaned
|
|
434
|
+
* (whitespace-filtered) `after` list.
|
|
435
|
+
*
|
|
436
|
+
* Extracted from spawn() so the command layer can run it BEFORE creating a
|
|
437
|
+
* worktree — a rejected add must not leave an orphan `agents/<name>` branch
|
|
438
|
+
* that then breaks the retry with `fatal: a branch ... already exists`
|
|
439
|
+
* (RUSH-2356). spawn() calls it too, so validation lives in exactly one place.
|
|
440
|
+
*
|
|
441
|
+
* The result is cached for exactly ONE subsequent call with the same
|
|
442
|
+
* arguments, which spawn() then consumes. `listByTask()` → `listAll()`
|
|
443
|
+
* refreshes every sibling's status, and on a `--device` team that is a full
|
|
444
|
+
* round of SSH liveness probes — running it twice per `teams add` would
|
|
445
|
+
* double that cost for no gain, since the second pass reads the same snapshot
|
|
446
|
+
* and cannot catch anything the first missed. The cache is single-use so any
|
|
447
|
+
* later spawn (a `teams start --watch` supervisor launching staged teammates)
|
|
448
|
+
* still validates against fresh state and still rejects a duplicate name.
|
|
449
|
+
*/
|
|
450
|
+
validateAddPreconditions(taskName: string, name: string | null, after: string[]): Promise<string[]>;
|
|
451
|
+
/**
|
|
452
|
+
* Does any LIVE teammate — in any team — already own `worktreeName`?
|
|
453
|
+
*
|
|
454
|
+
* A RAW disk scan: no status probing, no cache, no `listAll()`. The caller is
|
|
455
|
+
* the `teams add` failure path, where the manager's own status refresh can be
|
|
456
|
+
* the very thing that threw (`cleanupOldAgents()` → `listAll()` →
|
|
457
|
+
* `updateStatusFromProcess()` runs AFTER the staged record is saved), so a
|
|
458
|
+
* check that re-entered that machinery would throw again and answer nothing.
|
|
459
|
+
*
|
|
460
|
+
* `teams add` asks this before removing a worktree, to tell an ORPHAN from
|
|
461
|
+
* someone's live checkout (RUSH-2356). Two deliberate scoping choices:
|
|
462
|
+
*
|
|
463
|
+
* - **Any team, not just the one being added to.** Worktree names are global
|
|
464
|
+
* to the repo but records are per-team, so a same-named worktree owned by
|
|
465
|
+
* another team's teammate must also block the removal.
|
|
466
|
+
* - **Non-terminal records only.** A completed/failed/stopped teammate's
|
|
467
|
+
* worktree was already cleaned up at `teams stop`, and its record lingers
|
|
468
|
+
* until retention reaps it — counting those would leave a genuine orphan
|
|
469
|
+
* branch stranded forever, which is the bug this all exists to fix.
|
|
470
|
+
* - **Fails CLOSED.** This guards a `git worktree remove --force`, so the two
|
|
471
|
+
* errors are not symmetric: a false "claimed" strands an orphan branch that
|
|
472
|
+
* a human can delete, while a false "unclaimed" deletes a live agent's
|
|
473
|
+
* checkout and its uncommitted work. Only `ENOENT` proves absence — no
|
|
474
|
+
* agents dir means no records, and a record with no `meta.json` is not a
|
|
475
|
+
* record. Any other failure (EACCES, EIO, half-written or invalid JSON,
|
|
476
|
+
* a race with a writer) means we could not READ the records, which is not
|
|
477
|
+
* the same as there being none, so it answers `true`. This is deliberate
|
|
478
|
+
* asymmetry, not defensive coding: the caller acts destructively on `false`.
|
|
479
|
+
*/
|
|
480
|
+
isWorktreeClaimed(worktreeName: string): Promise<boolean>;
|
|
358
481
|
spawn(taskName: string, agentType: AgentType, prompt: string, cwd?: string | null, mode?: Mode | null, effort?: EffortLevel, parentSessionId?: string | null, workspaceDir?: string | null, version?: string | null, name?: string | null, after?: string[], model?: string | null, envOverrides?: Record<string, string> | null, taskType?: TaskType | null, cloudProvider?: string | null, cloudSessionId?: string | null, cloudRepo?: string | null, cloudBranch?: string | null, worktreeName?: string | null, worktreePath?: string | null, profileName?: string | null, hostName?: string | null, hostTarget?: string | null, repoPath?: string | null): Promise<AgentProcess>;
|
|
359
482
|
/**
|
|
360
483
|
* Resume a STOPPED teammate (completed / failed / stopped) by re-entering its
|
|
@@ -481,6 +604,13 @@ export declare class AgentManager {
|
|
|
481
604
|
}>;
|
|
482
605
|
listAll(): Promise<AgentProcess[]>;
|
|
483
606
|
listRunning(): Promise<AgentProcess[]>;
|
|
607
|
+
/**
|
|
608
|
+
* Teammates that have reached a terminal status (completed/failed/stopped) —
|
|
609
|
+
* the ONLY records retention may reap. A `pending` teammate has not launched
|
|
610
|
+
* and a `running` one is working, so neither is "completed"; classifying them
|
|
611
|
+
* as such let cleanupOldAgents sweep live `pending` `--after` teammates past
|
|
612
|
+
* the cap (RUSH-2356). Filter on `isTerminalStatus`, never `!== RUNNING`.
|
|
613
|
+
*/
|
|
484
614
|
listCompleted(): Promise<AgentProcess[]>;
|
|
485
615
|
listByTask(taskName: string): Promise<AgentProcess[]>;
|
|
486
616
|
listByParentSession(parentSessionId: string): Promise<AgentProcess[]>;
|