@phnx-labs/agents-cli 1.20.52 → 1.20.53
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 +57 -2
- package/README.md +12 -0
- package/dist/bin/agents +0 -0
- package/dist/commands/cloud.d.ts +3 -0
- package/dist/commands/cloud.js +2 -1
- package/dist/commands/exec.js +65 -1
- package/dist/commands/feed.d.ts +29 -0
- package/dist/commands/feed.js +237 -32
- package/dist/commands/memory.d.ts +9 -0
- package/dist/commands/memory.js +164 -0
- package/dist/commands/message.d.ts +11 -6
- package/dist/commands/message.js +140 -5
- package/dist/commands/routines.js +12 -0
- package/dist/commands/secrets-migrate.d.ts +2 -1
- package/dist/commands/secrets-migrate.js +88 -13
- package/dist/commands/secrets.js +4 -1
- package/dist/commands/sessions.js +10 -1
- package/dist/commands/worktree.js +4 -2
- package/dist/index.js +16 -21
- package/dist/lib/agents.js +249 -17
- package/dist/lib/answer-router.d.ts +75 -0
- package/dist/lib/answer-router.js +149 -0
- package/dist/lib/ask-classifier.d.ts +71 -0
- package/dist/lib/ask-classifier.js +197 -0
- package/dist/lib/cloud/antigravity.d.ts +0 -2
- package/dist/lib/cloud/antigravity.js +2 -17
- package/dist/lib/cloud/codex.js +3 -18
- package/dist/lib/cloud/rush.js +3 -15
- package/dist/lib/cloud/stream.js +2 -0
- package/dist/lib/cloud/types.d.ts +21 -0
- package/dist/lib/cloud/types.js +81 -0
- package/dist/lib/crabbox/cli.d.ts +1 -1
- package/dist/lib/crabbox/cli.js +12 -2
- package/dist/lib/crabbox/lease.d.ts +13 -0
- package/dist/lib/crabbox/lease.js +11 -2
- package/dist/lib/crabbox/progress.d.ts +62 -0
- package/dist/lib/crabbox/progress.js +129 -0
- package/dist/lib/events.js +4 -1
- package/dist/lib/exec.js +19 -1
- package/dist/lib/feed-outcome.d.ts +101 -0
- package/dist/lib/feed-outcome.js +244 -0
- package/dist/lib/feed-policy.d.ts +30 -0
- package/dist/lib/feed-policy.js +133 -0
- package/dist/lib/feed.d.ts +127 -3
- package/dist/lib/feed.js +416 -40
- package/dist/lib/git.d.ts +17 -1
- package/dist/lib/git.js +20 -1
- package/dist/lib/hooks.js +522 -12
- package/dist/lib/hosts/passthrough.d.ts +3 -3
- package/dist/lib/hosts/passthrough.js +3 -4
- package/dist/lib/mailbox-gc.d.ts +22 -0
- package/dist/lib/mailbox-gc.js +161 -0
- package/dist/lib/mailbox.d.ts +26 -2
- package/dist/lib/mailbox.js +80 -5
- package/dist/lib/mcp.js +82 -0
- package/dist/lib/memory.d.ts +55 -0
- package/dist/lib/memory.js +274 -0
- package/dist/lib/notify.d.ts +16 -0
- package/dist/lib/notify.js +61 -0
- package/dist/lib/operator.d.ts +26 -0
- package/dist/lib/operator.js +107 -0
- package/dist/lib/plugins.d.ts +35 -0
- package/dist/lib/plugins.js +217 -0
- package/dist/lib/remote-agents-json.d.ts +14 -0
- package/dist/lib/remote-agents-json.js +94 -0
- package/dist/lib/resources/mcp.js +44 -0
- package/dist/lib/resources/memory.d.ts +15 -0
- package/dist/lib/resources/memory.js +46 -0
- package/dist/lib/resources/types.d.ts +2 -2
- package/dist/lib/runner.d.ts +43 -0
- package/dist/lib/runner.js +323 -74
- package/dist/lib/sandbox.js +6 -0
- package/dist/lib/secrets/bundles.js +38 -13
- package/dist/lib/secrets/icloud-import.d.ts +12 -3
- package/dist/lib/secrets/icloud-import.js +37 -7
- package/dist/lib/secrets/index.d.ts +106 -2
- package/dist/lib/secrets/index.js +603 -28
- package/dist/lib/session/active.d.ts +18 -0
- package/dist/lib/session/active.js +47 -17
- package/dist/lib/session/db.d.ts +9 -1
- package/dist/lib/session/db.js +18 -3
- package/dist/lib/session/discover.d.ts +13 -0
- package/dist/lib/session/discover.js +31 -0
- package/dist/lib/session/parse.d.ts +8 -0
- package/dist/lib/session/parse.js +42 -21
- package/dist/lib/session/remote-active.js +8 -89
- package/dist/lib/session/state.d.ts +11 -0
- package/dist/lib/session/state.js +37 -0
- package/dist/lib/session/tail.d.ts +23 -4
- package/dist/lib/session/tail.js +34 -16
- package/dist/lib/session/throughput.d.ts +30 -0
- package/dist/lib/session/throughput.js +86 -0
- package/dist/lib/shim-heal.d.ts +12 -3
- package/dist/lib/shim-heal.js +12 -6
- package/dist/lib/staleness/detectors/subagents.js +57 -3
- package/dist/lib/staleness/writers/hooks.js +7 -3
- package/dist/lib/staleness/writers/subagents.js +37 -6
- package/dist/lib/startup/command-registry.d.ts +1 -0
- package/dist/lib/startup/command-registry.js +2 -0
- package/dist/lib/subagents.d.ts +52 -0
- package/dist/lib/subagents.js +315 -12
- package/dist/lib/teams/worktree.d.ts +8 -0
- package/dist/lib/teams/worktree.js +8 -0
- package/dist/lib/types.d.ts +10 -2
- package/dist/lib/versions.js +38 -48
- package/package.json +4 -3
- package/scripts/postinstall.js +61 -1
package/dist/lib/cloud/types.js
CHANGED
|
@@ -5,6 +5,87 @@
|
|
|
5
5
|
* Factory) implement, plus the shared task and event types that flow through
|
|
6
6
|
* the dispatch pipeline.
|
|
7
7
|
*/
|
|
8
|
+
/**
|
|
9
|
+
* Normalize a provider's raw wire status into the canonical `CloudTaskStatus`.
|
|
10
|
+
*
|
|
11
|
+
* Each cloud backend speaks its own status vocabulary and had its own copy of
|
|
12
|
+
* this mapping, which had drifted. This dispatches per provider while keeping
|
|
13
|
+
* provider-specific defaults explicit:
|
|
14
|
+
* - `rush` — switch over the Factory Floor's known strings; includes
|
|
15
|
+
* `allocating` and stopped/resumable `idle` states, has no
|
|
16
|
+
* `queued`, default `running`.
|
|
17
|
+
* - `codex` — substring match on the lowercased CLI status; default
|
|
18
|
+
* `running`.
|
|
19
|
+
* - `antigravity` — substring match on the (possibly `undefined`) Interactions
|
|
20
|
+
* API status; default `completed` (its synchronous response
|
|
21
|
+
* is terminal), and `undefined`-safe.
|
|
22
|
+
*
|
|
23
|
+
* Factory's `mapResultStatus` is structurally different (it maps a droid *exit*
|
|
24
|
+
* result, not a lifecycle string) and deliberately stays in `factory.ts`.
|
|
25
|
+
*/
|
|
26
|
+
export function normalizeProviderStatus(provider, wireStatus) {
|
|
27
|
+
switch (provider) {
|
|
28
|
+
case 'rush':
|
|
29
|
+
return normalizeRushStatus(wireStatus ?? '');
|
|
30
|
+
case 'codex':
|
|
31
|
+
return normalizeCodexStatus(wireStatus ?? '');
|
|
32
|
+
case 'antigravity':
|
|
33
|
+
return normalizeAntigravityStatus(wireStatus);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/** Rush Factory Floor status → canonical enum. Default `running`; no `queued`. */
|
|
37
|
+
function normalizeRushStatus(s) {
|
|
38
|
+
switch (s) {
|
|
39
|
+
case 'allocating': return 'allocating';
|
|
40
|
+
case 'running': return 'running';
|
|
41
|
+
case 'idle':
|
|
42
|
+
case 'paused':
|
|
43
|
+
case 'needs_review': return 'idle';
|
|
44
|
+
case 'input_required': return 'input_required';
|
|
45
|
+
case 'completed': return 'completed';
|
|
46
|
+
case 'failed': return 'failed';
|
|
47
|
+
case 'cancelled': return 'cancelled';
|
|
48
|
+
default: return 'running';
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/** Codex Cloud CLI status → canonical enum. Substring match; default `running`. */
|
|
52
|
+
function normalizeCodexStatus(s) {
|
|
53
|
+
const lower = s.toLowerCase();
|
|
54
|
+
if (lower.includes('queued') || lower.includes('pending'))
|
|
55
|
+
return 'queued';
|
|
56
|
+
if (lower.includes('running') || lower.includes('in_progress'))
|
|
57
|
+
return 'running';
|
|
58
|
+
if (lower.includes('idle') || lower.includes('paused') || lower.includes('needs_review'))
|
|
59
|
+
return 'idle';
|
|
60
|
+
if (lower.includes('completed') || lower.includes('succeeded') || lower.includes('success'))
|
|
61
|
+
return 'completed';
|
|
62
|
+
if (lower.includes('failed') || lower.includes('error'))
|
|
63
|
+
return 'failed';
|
|
64
|
+
if (lower.includes('cancelled') || lower.includes('canceled'))
|
|
65
|
+
return 'cancelled';
|
|
66
|
+
return 'running';
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Antigravity Interactions API status → canonical enum. Substring match,
|
|
70
|
+
* `undefined`-safe; default `completed` because the synchronous response is
|
|
71
|
+
* already terminal.
|
|
72
|
+
*/
|
|
73
|
+
function normalizeAntigravityStatus(s) {
|
|
74
|
+
const lower = (s ?? '').toLowerCase();
|
|
75
|
+
if (lower.includes('queue') || lower.includes('pending'))
|
|
76
|
+
return 'queued';
|
|
77
|
+
if (lower.includes('run') || lower.includes('progress'))
|
|
78
|
+
return 'running';
|
|
79
|
+
if (lower.includes('idle') || lower.includes('paused') || lower.includes('needs_review'))
|
|
80
|
+
return 'idle';
|
|
81
|
+
if (lower.includes('complete') || lower.includes('success'))
|
|
82
|
+
return 'completed';
|
|
83
|
+
if (lower.includes('fail') || lower.includes('error'))
|
|
84
|
+
return 'failed';
|
|
85
|
+
if (lower.includes('cancel'))
|
|
86
|
+
return 'cancelled';
|
|
87
|
+
return 'completed';
|
|
88
|
+
}
|
|
8
89
|
/** Maximum images allowed per dispatch (matches Cursor Background Agents). */
|
|
9
90
|
export const MAX_IMAGES_PER_DISPATCH = 5;
|
|
10
91
|
/**
|
|
@@ -62,7 +62,7 @@ export interface WarmupOptions extends CrabboxOptions {
|
|
|
62
62
|
* created even if warmup's stdout format changes — the new lease id is the one
|
|
63
63
|
* that wasn't present before.
|
|
64
64
|
*/
|
|
65
|
-
export declare function crabboxWarmup(opts?: WarmupOptions): CrabboxBox
|
|
65
|
+
export declare function crabboxWarmup(opts?: WarmupOptions): Promise<CrabboxBox>;
|
|
66
66
|
/**
|
|
67
67
|
* Poll until the box reports ready, or throw after timeoutMs.
|
|
68
68
|
* `sleep` is injectable so tests don't wall-clock wait.
|
package/dist/lib/crabbox/cli.js
CHANGED
|
@@ -87,7 +87,7 @@ export function crabboxFind(slug, opts = {}) {
|
|
|
87
87
|
* created even if warmup's stdout format changes — the new lease id is the one
|
|
88
88
|
* that wasn't present before.
|
|
89
89
|
*/
|
|
90
|
-
export function crabboxWarmup(opts = {}) {
|
|
90
|
+
export async function crabboxWarmup(opts = {}) {
|
|
91
91
|
findCrabbox();
|
|
92
92
|
const env = crabboxEnv(opts);
|
|
93
93
|
const before = new Set(crabboxList(opts).map((b) => b.lease));
|
|
@@ -100,7 +100,17 @@ export function crabboxWarmup(opts = {}) {
|
|
|
100
100
|
args.push('--provider', opts.provider);
|
|
101
101
|
if (opts.code)
|
|
102
102
|
args.push('--code');
|
|
103
|
-
|
|
103
|
+
// Async spawn (not spawnSync): provisioning takes 30-90s and a blocking call
|
|
104
|
+
// would freeze any caller's progress spinner. Output is captured, not streamed.
|
|
105
|
+
const r = await new Promise((resolve) => {
|
|
106
|
+
const proc = spawn('crabbox', args, { env, stdio: ['ignore', 'pipe', 'pipe'] });
|
|
107
|
+
let stdout = '';
|
|
108
|
+
let stderr = '';
|
|
109
|
+
proc.stdout.on('data', (c) => (stdout += c.toString('utf-8')));
|
|
110
|
+
proc.stderr.on('data', (c) => (stderr += c.toString('utf-8')));
|
|
111
|
+
proc.on('error', () => resolve({ status: null, stdout, stderr }));
|
|
112
|
+
proc.on('close', (code) => resolve({ status: code, stdout, stderr }));
|
|
113
|
+
});
|
|
104
114
|
if (r.status !== 0) {
|
|
105
115
|
const detail = (r.stderr || r.stdout || '').trim();
|
|
106
116
|
throw new Error(`crabbox warmup failed: ${detail || 'unknown error'}. ` +
|
|
@@ -9,6 +9,17 @@
|
|
|
9
9
|
import type { AgentId } from '../types.js';
|
|
10
10
|
import { type CrabboxBox } from './cli.js';
|
|
11
11
|
import { type DetectedRuntime } from './runtimes.js';
|
|
12
|
+
/** Phase signal for a lease run, so the command layer can drive a progress UI. */
|
|
13
|
+
export type LeasePhase = {
|
|
14
|
+
kind: 'warmup';
|
|
15
|
+
backend?: string;
|
|
16
|
+
} | {
|
|
17
|
+
kind: 'ready';
|
|
18
|
+
box: CrabboxBox;
|
|
19
|
+
elapsedMs: number;
|
|
20
|
+
} | {
|
|
21
|
+
kind: 'teardown';
|
|
22
|
+
};
|
|
12
23
|
export interface LeaseRunOptions {
|
|
13
24
|
agent: string;
|
|
14
25
|
prompt: string;
|
|
@@ -24,6 +35,8 @@ export interface LeaseRunOptions {
|
|
|
24
35
|
/** Secrets bundle providing crabbox's provider token. */
|
|
25
36
|
secretsBundle?: string;
|
|
26
37
|
onData?: (s: string) => void;
|
|
38
|
+
/** Progress phases (warmup → ready → teardown) for a command-layer spinner. */
|
|
39
|
+
onPhase?: (phase: LeasePhase) => void;
|
|
27
40
|
/** Keep the box after the run instead of stopping it. */
|
|
28
41
|
keep?: boolean;
|
|
29
42
|
/**
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { crabboxWarmup, crabboxWaitReady, crabboxRunScript, crabboxStop } from './cli.js';
|
|
10
10
|
import { buildCredentialScript, CLAUDE_TOKEN_REMOTE } from './runtimes.js';
|
|
11
|
+
import { LEASE_AGENT_MARKER } from './progress.js';
|
|
11
12
|
/** POSIX single-quote for safe embedding in the generated bootstrap script. */
|
|
12
13
|
function q(s) {
|
|
13
14
|
return "'" + s.replace(/'/g, "'\\''") + "'";
|
|
@@ -68,6 +69,9 @@ export function buildBootstrapScript(opts) {
|
|
|
68
69
|
ENSURE_AGENTS_CLI,
|
|
69
70
|
installRuntimes,
|
|
70
71
|
credScript,
|
|
72
|
+
// Marker on its own line: the command layer shows everything before this as
|
|
73
|
+
// setup progress and everything after (the agent's output) verbatim.
|
|
74
|
+
`echo ${q(LEASE_AGENT_MARKER)}`,
|
|
71
75
|
`${runParts.join(' ')}`,
|
|
72
76
|
'rc=$?',
|
|
73
77
|
shred,
|
|
@@ -77,13 +81,16 @@ export function buildBootstrapScript(opts) {
|
|
|
77
81
|
.join('\n');
|
|
78
82
|
}
|
|
79
83
|
export async function leaseAndRun(opts) {
|
|
80
|
-
const
|
|
84
|
+
const startedAt = Date.now();
|
|
85
|
+
opts.onPhase?.({ kind: 'warmup', backend: opts.backend });
|
|
86
|
+
const box = await crabboxWarmup({
|
|
81
87
|
class: opts.boxClass,
|
|
82
88
|
profile: opts.profile,
|
|
83
89
|
provider: opts.backend,
|
|
84
90
|
secretsBundle: opts.secretsBundle,
|
|
85
91
|
});
|
|
86
92
|
await crabboxWaitReady(box.slug, { secretsBundle: opts.secretsBundle });
|
|
93
|
+
opts.onPhase?.({ kind: 'ready', box, elapsedMs: Date.now() - startedAt });
|
|
87
94
|
const script = buildBootstrapScript(opts);
|
|
88
95
|
let exitCode = null;
|
|
89
96
|
let toreDown = false;
|
|
@@ -96,8 +103,10 @@ export async function leaseAndRun(opts) {
|
|
|
96
103
|
finally {
|
|
97
104
|
// Always attempt teardown (bounds credential lifetime to the run) unless the
|
|
98
105
|
// caller explicitly asked to keep the box.
|
|
99
|
-
if (!opts.keep)
|
|
106
|
+
if (!opts.keep) {
|
|
107
|
+
opts.onPhase?.({ kind: 'teardown' });
|
|
100
108
|
toreDown = crabboxStop(box.slug, { secretsBundle: opts.secretsBundle });
|
|
109
|
+
}
|
|
101
110
|
}
|
|
102
111
|
return { box, exitCode, toreDown };
|
|
103
112
|
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Progress routing for `agents run --lease`.
|
|
3
|
+
*
|
|
4
|
+
* The box-side bootstrap emits `LEASE_AGENT_MARKER` on its own line right before
|
|
5
|
+
* `agents run`. Everything the crabbox run streams BEFORE that marker is setup
|
|
6
|
+
* noise (sync bytes, node/agents install, `agents setup`) — shown as spinner
|
|
7
|
+
* text and captured for a failure dump. Everything AFTER is the agent's own
|
|
8
|
+
* output — printed through verbatim. The marker line itself is swallowed.
|
|
9
|
+
*/
|
|
10
|
+
/** Sentinel echoed on the box right before `agents run`. Distinctive + collision-proof. */
|
|
11
|
+
export declare const LEASE_AGENT_MARKER = "___AGENTS_LEASE_AGENT_OUTPUT_b1f4c2___";
|
|
12
|
+
export interface Spinner {
|
|
13
|
+
/** Begin animating a phase (renders `frame text` on one line, TTY only). */
|
|
14
|
+
start(text: string): void;
|
|
15
|
+
/** Change the text shown; rendered on the next throttled tick (no write itself). */
|
|
16
|
+
update(text: string): void;
|
|
17
|
+
/** Finalize the current line with a symbol (e.g. ✔) and a newline. */
|
|
18
|
+
stopAndPersist(symbol: string, text: string): void;
|
|
19
|
+
/** Clear the current animated line without persisting anything. */
|
|
20
|
+
stop(): void;
|
|
21
|
+
/** True while a phase is active. */
|
|
22
|
+
readonly active: boolean;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* A deliberately minimal, self-throttled spinner. Unlike `ora`, it does NOT hook
|
|
26
|
+
* the stream's `write` to re-render on external output — it writes exactly one
|
|
27
|
+
* short line per fixed tick and nowhere else, so it is structurally incapable of
|
|
28
|
+
* a re-render feedback loop (the failure mode that made `ora` blow up when a
|
|
29
|
+
* lease streamed output past a live spinner). On a non-TTY it prints each phase
|
|
30
|
+
* label once and stays silent on `update`, so piped/CI output never floods.
|
|
31
|
+
*
|
|
32
|
+
* Only ever run ONE phase at a time, and never stream other output to the same
|
|
33
|
+
* stream while a phase is active — stop it first.
|
|
34
|
+
*/
|
|
35
|
+
export declare function createSpinner(opts?: {
|
|
36
|
+
stream?: {
|
|
37
|
+
write(s: string): unknown;
|
|
38
|
+
isTTY?: boolean;
|
|
39
|
+
};
|
|
40
|
+
enabled?: boolean;
|
|
41
|
+
intervalMs?: number;
|
|
42
|
+
}): Spinner;
|
|
43
|
+
export interface LeaseOutputRouter {
|
|
44
|
+
/** Feed a raw chunk of the crabbox run's combined stdout/stderr. */
|
|
45
|
+
push(chunk: string): void;
|
|
46
|
+
/** Flush any buffered partial line (call once the stream closes). */
|
|
47
|
+
end(): void;
|
|
48
|
+
/** True once the agent-output marker has been seen. */
|
|
49
|
+
sawAgent(): boolean;
|
|
50
|
+
/** The setup lines seen so far (for a failure dump). */
|
|
51
|
+
setupLines(): string[];
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Split the crabbox run stream at `LEASE_AGENT_MARKER`. `onSetupLine` fires for
|
|
55
|
+
* each complete non-empty line before the marker; `onAgentChunk` fires with raw
|
|
56
|
+
* text after it (streamed promptly, not line-buffered, so agent output is live).
|
|
57
|
+
*/
|
|
58
|
+
export declare function createLeaseOutputRouter(cb: {
|
|
59
|
+
onSetupLine: (line: string) => void;
|
|
60
|
+
onAgentChunk: (chunk: string) => void;
|
|
61
|
+
marker?: string;
|
|
62
|
+
}): LeaseOutputRouter;
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Progress routing for `agents run --lease`.
|
|
3
|
+
*
|
|
4
|
+
* The box-side bootstrap emits `LEASE_AGENT_MARKER` on its own line right before
|
|
5
|
+
* `agents run`. Everything the crabbox run streams BEFORE that marker is setup
|
|
6
|
+
* noise (sync bytes, node/agents install, `agents setup`) — shown as spinner
|
|
7
|
+
* text and captured for a failure dump. Everything AFTER is the agent's own
|
|
8
|
+
* output — printed through verbatim. The marker line itself is swallowed.
|
|
9
|
+
*/
|
|
10
|
+
/** Sentinel echoed on the box right before `agents run`. Distinctive + collision-proof. */
|
|
11
|
+
export const LEASE_AGENT_MARKER = '___AGENTS_LEASE_AGENT_OUTPUT_b1f4c2___';
|
|
12
|
+
const SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
13
|
+
/**
|
|
14
|
+
* A deliberately minimal, self-throttled spinner. Unlike `ora`, it does NOT hook
|
|
15
|
+
* the stream's `write` to re-render on external output — it writes exactly one
|
|
16
|
+
* short line per fixed tick and nowhere else, so it is structurally incapable of
|
|
17
|
+
* a re-render feedback loop (the failure mode that made `ora` blow up when a
|
|
18
|
+
* lease streamed output past a live spinner). On a non-TTY it prints each phase
|
|
19
|
+
* label once and stays silent on `update`, so piped/CI output never floods.
|
|
20
|
+
*
|
|
21
|
+
* Only ever run ONE phase at a time, and never stream other output to the same
|
|
22
|
+
* stream while a phase is active — stop it first.
|
|
23
|
+
*/
|
|
24
|
+
export function createSpinner(opts = {}) {
|
|
25
|
+
const stream = opts.stream ?? process.stderr;
|
|
26
|
+
const enabled = opts.enabled ?? !!stream.isTTY;
|
|
27
|
+
const intervalMs = opts.intervalMs ?? 120;
|
|
28
|
+
const CLEAR = '\r\u001b[2K';
|
|
29
|
+
let text = '';
|
|
30
|
+
let frame = 0;
|
|
31
|
+
let timer;
|
|
32
|
+
let running = false;
|
|
33
|
+
const write = (s) => stream.write(s);
|
|
34
|
+
return {
|
|
35
|
+
start(t) {
|
|
36
|
+
text = t;
|
|
37
|
+
running = true;
|
|
38
|
+
if (!enabled) {
|
|
39
|
+
write(`${t}\n`);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
if (timer)
|
|
43
|
+
return;
|
|
44
|
+
frame = 0;
|
|
45
|
+
write(`${CLEAR}${SPINNER_FRAMES[0]} ${text}`);
|
|
46
|
+
timer = setInterval(() => {
|
|
47
|
+
frame = (frame + 1) % SPINNER_FRAMES.length;
|
|
48
|
+
write(`${CLEAR}${SPINNER_FRAMES[frame]} ${text}`);
|
|
49
|
+
}, intervalMs);
|
|
50
|
+
},
|
|
51
|
+
update(t) {
|
|
52
|
+
text = t; // next tick renders it; on a non-TTY we stay silent (no flood)
|
|
53
|
+
},
|
|
54
|
+
stopAndPersist(symbol, t) {
|
|
55
|
+
if (timer) {
|
|
56
|
+
clearInterval(timer);
|
|
57
|
+
timer = undefined;
|
|
58
|
+
}
|
|
59
|
+
running = false;
|
|
60
|
+
write(enabled ? `${CLEAR}${symbol} ${t}\n` : `${symbol} ${t}\n`);
|
|
61
|
+
},
|
|
62
|
+
stop() {
|
|
63
|
+
if (timer) {
|
|
64
|
+
clearInterval(timer);
|
|
65
|
+
timer = undefined;
|
|
66
|
+
if (enabled)
|
|
67
|
+
write(CLEAR);
|
|
68
|
+
}
|
|
69
|
+
running = false;
|
|
70
|
+
},
|
|
71
|
+
get active() {
|
|
72
|
+
return running;
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Split the crabbox run stream at `LEASE_AGENT_MARKER`. `onSetupLine` fires for
|
|
78
|
+
* each complete non-empty line before the marker; `onAgentChunk` fires with raw
|
|
79
|
+
* text after it (streamed promptly, not line-buffered, so agent output is live).
|
|
80
|
+
*/
|
|
81
|
+
export function createLeaseOutputRouter(cb) {
|
|
82
|
+
const marker = cb.marker ?? LEASE_AGENT_MARKER;
|
|
83
|
+
let seen = false;
|
|
84
|
+
let buf = '';
|
|
85
|
+
const setup = [];
|
|
86
|
+
const emitLine = (line) => {
|
|
87
|
+
const t = line.replace(/\r$/, '');
|
|
88
|
+
if (t.trim()) {
|
|
89
|
+
setup.push(t);
|
|
90
|
+
cb.onSetupLine(t);
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
return {
|
|
94
|
+
push(chunk) {
|
|
95
|
+
if (seen) {
|
|
96
|
+
cb.onAgentChunk(chunk);
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
buf += chunk;
|
|
100
|
+
let idx;
|
|
101
|
+
while ((idx = buf.indexOf('\n')) !== -1) {
|
|
102
|
+
const line = buf.slice(0, idx);
|
|
103
|
+
buf = buf.slice(idx + 1);
|
|
104
|
+
if (line.includes(marker)) {
|
|
105
|
+
seen = true;
|
|
106
|
+
// Anything already buffered past the marker is agent output.
|
|
107
|
+
if (buf) {
|
|
108
|
+
cb.onAgentChunk(buf);
|
|
109
|
+
buf = '';
|
|
110
|
+
}
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
emitLine(line);
|
|
114
|
+
}
|
|
115
|
+
// A trailing partial line stays in `buf` — it may be the marker forming.
|
|
116
|
+
},
|
|
117
|
+
end() {
|
|
118
|
+
if (!seen && buf)
|
|
119
|
+
emitLine(buf);
|
|
120
|
+
buf = '';
|
|
121
|
+
},
|
|
122
|
+
sawAgent() {
|
|
123
|
+
return seen;
|
|
124
|
+
},
|
|
125
|
+
setupLines() {
|
|
126
|
+
return setup;
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
}
|
package/dist/lib/events.js
CHANGED
|
@@ -22,9 +22,12 @@ import { getUserAgentsDir } from './state.js';
|
|
|
22
22
|
// ─── Constants ────────────────────────────────────────────────────────────────
|
|
23
23
|
// Resolved lazily: events.ts is imported transitively by most CLI surfaces, and
|
|
24
24
|
// import itself must stay side-effect free. Tests may override the exact path.
|
|
25
|
+
// AGENTS_EVENTS_PATH redirects the sink — a test seam like AGENTS_SECRETS_AGENT_DIR:
|
|
26
|
+
// unlike _resetForTest it survives a bare reset AND propagates to CLI subprocesses
|
|
27
|
+
// a test spawns, so fixture events can never land in the user's real log (#910).
|
|
25
28
|
let _eventsPath;
|
|
26
29
|
function eventsPath() {
|
|
27
|
-
return (_eventsPath ??= path.join(getUserAgentsDir(), 'events.jsonl'));
|
|
30
|
+
return (_eventsPath ??= process.env.AGENTS_EVENTS_PATH || path.join(getUserAgentsDir(), 'events.jsonl'));
|
|
28
31
|
}
|
|
29
32
|
function eventsDir() {
|
|
30
33
|
return path.dirname(eventsPath());
|
package/dist/lib/exec.js
CHANGED
|
@@ -398,7 +398,10 @@ export const AGENT_COMMANDS = {
|
|
|
398
398
|
modelFlag: '--model',
|
|
399
399
|
},
|
|
400
400
|
kiro: {
|
|
401
|
-
|
|
401
|
+
// Standalone hooks live under ~/.kiro/hooks/*.json and only fire on the
|
|
402
|
+
// v3 engine (opt-in via --v3; see https://kiro.dev/docs/cli/v3/hooks/).
|
|
403
|
+
// Without this flag agents-cli would write v3 hook files that never run.
|
|
404
|
+
base: ['kiro-cli', '--v3'],
|
|
402
405
|
promptFlag: 'positional',
|
|
403
406
|
modeFlags: {
|
|
404
407
|
// kiro-cli has no permission flags — edit is the default behavior.
|
|
@@ -471,6 +474,21 @@ export const AGENT_COMMANDS = {
|
|
|
471
474
|
jsonFlags: ['-o', 'stream-json'],
|
|
472
475
|
modelFlag: '-m',
|
|
473
476
|
},
|
|
477
|
+
hermes: {
|
|
478
|
+
base: ['hermes', 'chat'],
|
|
479
|
+
promptFlag: 'positional',
|
|
480
|
+
modeFlags: {
|
|
481
|
+
edit: [],
|
|
482
|
+
},
|
|
483
|
+
modelFlag: '--model',
|
|
484
|
+
},
|
|
485
|
+
forge: {
|
|
486
|
+
base: ['forge'],
|
|
487
|
+
promptFlag: 'positional',
|
|
488
|
+
modeFlags: {
|
|
489
|
+
edit: [],
|
|
490
|
+
},
|
|
491
|
+
},
|
|
474
492
|
};
|
|
475
493
|
/**
|
|
476
494
|
* Whether `agent` has a native resume form (Tier 1). Derived solely from the
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import type { OpenBlock } from './feed.js';
|
|
2
|
+
export type OutcomeKind = 'ticket' | 'pr' | 'worktree' | 'unassigned';
|
|
3
|
+
/** Stable attribution of a block (or agent) to one deliverable. */
|
|
4
|
+
export interface OutcomeRef {
|
|
5
|
+
/** Map/JSON key, e.g. `ticket:RUSH-1125`, `pr:#534`, `worktree:headless-secrets`, `unassigned`. */
|
|
6
|
+
key: string;
|
|
7
|
+
kind: OutcomeKind;
|
|
8
|
+
/** Human header label, e.g. `RUSH-1125`, `PR#534`, `headless-secrets`, `Unassigned`. */
|
|
9
|
+
label: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Signals used to derive an outcome. All optional — callers pass whatever they
|
|
13
|
+
* already know (block fields, session meta, free-text questions).
|
|
14
|
+
*/
|
|
15
|
+
export interface OutcomeSignals {
|
|
16
|
+
ticket?: string | null;
|
|
17
|
+
pr?: string | null;
|
|
18
|
+
worktreeSlug?: string | null;
|
|
19
|
+
/** Branch name — scanned for `rush-1125`-style ticket slugs. */
|
|
20
|
+
branch?: string | null;
|
|
21
|
+
/** Epic / team label when no ticket/PR/worktree is known. */
|
|
22
|
+
epic?: string | null;
|
|
23
|
+
/** Free text (question headers + bodies) scanned for ticket/PR refs. */
|
|
24
|
+
text?: string | null;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Normalize a PR ref.
|
|
28
|
+
* - Full GitHub URL → `owner/repo#N` (repo identity included)
|
|
29
|
+
* - Bare `#123` / `PR#123` → `#123` (no repo known)
|
|
30
|
+
*/
|
|
31
|
+
export declare function normalizePrRef(raw: string | null | undefined): string | undefined;
|
|
32
|
+
/** `https://github.com/owner/repo/pull/N` → `owner/repo`. */
|
|
33
|
+
export declare function repoFromPrUrl(url: string | null | undefined): string | undefined;
|
|
34
|
+
/** Canonical ticket id (uppercase team key). */
|
|
35
|
+
export declare function normalizeTicketRef(raw: string | null | undefined): string | undefined;
|
|
36
|
+
/**
|
|
37
|
+
* Derive the single outcome a block/agent belongs to.
|
|
38
|
+
* Exactly one outcome per call — never ambiguous, never empty.
|
|
39
|
+
*/
|
|
40
|
+
export declare function deriveOutcome(signals: OutcomeSignals): OutcomeRef;
|
|
41
|
+
/** Join question headers + bodies into one scan target for ticket/PR detection. */
|
|
42
|
+
export declare function blockScanText(block: Pick<OpenBlock, 'questions'>): string;
|
|
43
|
+
/** Outcome for a stored open block (uses stamped fields + question text). */
|
|
44
|
+
export declare function outcomeForBlock(block: OpenBlock): OutcomeRef;
|
|
45
|
+
/** Per-outcome rollup used by the default `agents feed` view. */
|
|
46
|
+
export interface OutcomeGroup {
|
|
47
|
+
outcome: OutcomeRef;
|
|
48
|
+
blocks: OpenBlock[];
|
|
49
|
+
counts: {
|
|
50
|
+
/** Distinct mailbox ids under this outcome. */
|
|
51
|
+
agents: number;
|
|
52
|
+
/** Still open: unanswered and not hard-parked. */
|
|
53
|
+
open: number;
|
|
54
|
+
answered: number;
|
|
55
|
+
parked: number;
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Collapse blocks under their outcome. Order: needs-you outcomes first
|
|
60
|
+
* (open count desc), then by label. Unassigned always last among ties of 0 open.
|
|
61
|
+
*/
|
|
62
|
+
export declare function groupBlocksByOutcome(blocks: OpenBlock[]): OutcomeGroup[];
|
|
63
|
+
/**
|
|
64
|
+
* Stamp each block with its derived outcome for JSON consumers.
|
|
65
|
+
* Does not mutate the input records.
|
|
66
|
+
*/
|
|
67
|
+
export declare function stampBlockOutcomes(blocks: OpenBlock[]): Array<OpenBlock & {
|
|
68
|
+
outcome: OutcomeRef;
|
|
69
|
+
}>;
|
|
70
|
+
/**
|
|
71
|
+
* True when every still-open block under the outcome asks the same question
|
|
72
|
+
* (same cluster of header+text). Fan-out answers are only safe when this holds
|
|
73
|
+
* — otherwise the operator must pick a specific agent.
|
|
74
|
+
*/
|
|
75
|
+
export declare function isUnambiguousOutcomeAnswer(group: OutcomeGroup): boolean;
|
|
76
|
+
/** Still-open blocks under an outcome (candidates for a fan-out reply). */
|
|
77
|
+
export declare function openBlocksForOutcome(group: OutcomeGroup): OpenBlock[];
|
|
78
|
+
/**
|
|
79
|
+
* Lightweight session signals used to fill missing ticket/PR/worktree on a
|
|
80
|
+
* block at list time (the publish hook may not have had them yet).
|
|
81
|
+
*/
|
|
82
|
+
export interface SessionOutcomeHint {
|
|
83
|
+
sessionId?: string | null;
|
|
84
|
+
agentId?: string | null;
|
|
85
|
+
mailboxId?: string | null;
|
|
86
|
+
ticketId?: string | null;
|
|
87
|
+
prNumber?: number | null;
|
|
88
|
+
prUrl?: string | null;
|
|
89
|
+
worktreeSlug?: string | null;
|
|
90
|
+
branch?: string | null;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Overlay session meta onto a block when the block itself is missing ticket/PR/
|
|
94
|
+
* worktree. Never overwrites a field the block already carries.
|
|
95
|
+
*/
|
|
96
|
+
export declare function enrichBlockFromSession(block: OpenBlock, hint: SessionOutcomeHint): OpenBlock;
|
|
97
|
+
/**
|
|
98
|
+
* Build a mailboxId → session-hint index and enrich every block. Pure.
|
|
99
|
+
* Matching order: mailboxId, then sessionId, then agentId.
|
|
100
|
+
*/
|
|
101
|
+
export declare function enrichBlocksFromSessions(blocks: OpenBlock[], sessions: SessionOutcomeHint[]): OpenBlock[];
|