@phnx-labs/agents-cli 1.20.51 → 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 +71 -0
- package/README.md +12 -0
- package/dist/bin/agents +0 -0
- package/dist/commands/browser.js +215 -7
- package/dist/commands/cloud.d.ts +3 -0
- package/dist/commands/cloud.js +8 -1
- package/dist/commands/events.d.ts +1 -1
- package/dist/commands/events.js +2 -3
- package/dist/commands/exec.js +82 -3
- package/dist/commands/factory.js +8 -0
- package/dist/commands/feed.d.ts +38 -0
- package/dist/commands/feed.js +274 -0
- package/dist/commands/logs.d.ts +5 -1
- package/dist/commands/logs.js +248 -3
- package/dist/commands/mcp.js +7 -0
- 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.d.ts +22 -0
- package/dist/commands/secrets.js +176 -42
- package/dist/commands/sessions.js +10 -1
- package/dist/commands/teams.js +4 -0
- package/dist/commands/worktree.js +4 -2
- package/dist/index.js +21 -22
- 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/browser/login-detection.d.ts +94 -0
- package/dist/lib/browser/login-detection.js +274 -0
- package/dist/lib/browser/profiles.d.ts +17 -8
- package/dist/lib/browser/profiles.js +27 -8
- package/dist/lib/browser/secret-ref.d.ts +10 -0
- package/dist/lib/browser/secret-ref.js +14 -0
- package/dist/lib/browser/service.js +14 -12
- 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.d.ts +15 -0
- package/dist/lib/cloud/rush.js +10 -16
- 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 +19 -0
- package/dist/lib/crabbox/lease.js +22 -11
- package/dist/lib/crabbox/progress.d.ts +62 -0
- package/dist/lib/crabbox/progress.js +129 -0
- package/dist/lib/crabbox/runtimes.d.ts +38 -1
- package/dist/lib/crabbox/runtimes.js +98 -5
- package/dist/lib/daemon.d.ts +12 -9
- package/dist/lib/daemon.js +32 -17
- package/dist/lib/events.d.ts +31 -5
- package/dist/lib/events.js +291 -101
- package/dist/lib/exec.js +20 -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 +180 -0
- package/dist/lib/feed.js +627 -0
- package/dist/lib/git.d.ts +17 -1
- package/dist/lib/git.js +20 -1
- package/dist/lib/hooks.js +529 -14
- package/dist/lib/hosts/passthrough.d.ts +3 -3
- package/dist/lib/hosts/passthrough.js +3 -3
- 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/rotate.js +2 -0
- 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/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 +21 -0
- package/dist/lib/secrets/agent.js +63 -1
- package/dist/lib/secrets/bundles.d.ts +33 -1
- package/dist/lib/secrets/bundles.js +76 -21
- package/dist/lib/secrets/icloud-import.d.ts +79 -0
- package/dist/lib/secrets/icloud-import.js +203 -0
- package/dist/lib/secrets/index.d.ts +142 -2
- package/dist/lib/secrets/index.js +695 -30
- package/dist/lib/secrets/remote.js +1 -1
- package/dist/lib/secrets/sync.js +1 -1
- 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 +32 -2
- 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 +50 -1
- 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 +2 -0
- package/dist/lib/startup/command-registry.js +4 -0
- package/dist/lib/state.d.ts +2 -0
- package/dist/lib/state.js +25 -8
- package/dist/lib/subagents.d.ts +52 -0
- package/dist/lib/subagents.js +315 -12
- package/dist/lib/teams/agents.js +6 -3
- package/dist/lib/teams/worktree.d.ts +8 -0
- package/dist/lib/teams/worktree.js +8 -0
- package/dist/lib/types.d.ts +20 -2
- package/dist/lib/versions.js +38 -48
- package/dist/lib/whats-new.d.ts +5 -3
- package/dist/lib/whats-new.js +25 -5
- package/package.json +4 -3
- package/scripts/postinstall.js +61 -1
|
@@ -15,8 +15,35 @@
|
|
|
15
15
|
* shape + mtime — same function, driven off the normalized events.
|
|
16
16
|
*/
|
|
17
17
|
import { summarizeToolUse } from './parse.js';
|
|
18
|
+
/**
|
|
19
|
+
* Detect per-session rate-limit / usage-limit signals in assistant or error
|
|
20
|
+
* text (RUSH-1523). Matches the same shapes Factory's prewarm detectBlockingPrompt
|
|
21
|
+
* uses, plus common Claude/Codex/Gemini limit strings.
|
|
22
|
+
*/
|
|
23
|
+
export function detectRateLimited(text) {
|
|
24
|
+
if (!text)
|
|
25
|
+
return false;
|
|
26
|
+
const t = text.toLowerCase();
|
|
27
|
+
return (/\brate[- ]?limit(ed|s)?\b/.test(t) ||
|
|
28
|
+
/\btoo many requests\b/.test(t) ||
|
|
29
|
+
/\b429\b/.test(t) ||
|
|
30
|
+
/\busage[- ]?limit(ed)?\b/.test(t) ||
|
|
31
|
+
/\bhit your (usage |rate )?limit\b/.test(t) ||
|
|
32
|
+
/\byou('ve| have) (hit|reached|exceeded) (your |the )?(rate |usage )?limit\b/.test(t) ||
|
|
33
|
+
/\bout of (credits|quota)\b/.test(t) ||
|
|
34
|
+
/\bquota exceeded\b/.test(t) ||
|
|
35
|
+
/\btry again (in|later|after)\b/.test(t) && /\b(limit|rate|quota|throttl)\b/.test(t));
|
|
36
|
+
}
|
|
18
37
|
/** A healthy live session writes several times a minute; 2 min ⇒ "recently active". */
|
|
19
38
|
const ACTIVE_WINDOW_MS = 2 * 60_000;
|
|
39
|
+
/**
|
|
40
|
+
* A prose trailing question ("…?") is a HEURISTIC, so it decays: past this long
|
|
41
|
+
* with no session writes it stops classifying as waiting_input — otherwise a
|
|
42
|
+
* finished session that signed off with "anything else?" reads as needing input
|
|
43
|
+
* forever (RUSH-1522). The structural ExitPlanMode / AskUserQuestion signals are
|
|
44
|
+
* exempt: they are precise, still-unanswered decisions.
|
|
45
|
+
*/
|
|
46
|
+
const PROSE_QUESTION_FRESH_MS = 30 * 60_000;
|
|
20
47
|
/** Claude tool names that structurally mean "the agent handed control back to you". */
|
|
21
48
|
const PLAN_TOOL = 'ExitPlanMode';
|
|
22
49
|
const ASK_TOOL = 'AskUserQuestion';
|
|
@@ -299,7 +326,11 @@ export function inferActivity(events, ctx = {}) {
|
|
|
299
326
|
}
|
|
300
327
|
// Assistant spoke last and stopped. A trailing question → waiting; else idle.
|
|
301
328
|
// A prose question takes a free-text reply (no select-list), so no options/keys.
|
|
302
|
-
|
|
329
|
+
// Unlike the structural plan/ask signals above, the prose heuristic DECAYS: a
|
|
330
|
+
// question nobody answered within PROSE_QUESTION_FRESH_MS is a session that
|
|
331
|
+
// ended, not one that needs you (RUSH-1522). Unknown mtime keeps the question.
|
|
332
|
+
const questionFresh = ctx.mtimeMs == null || Date.now() - ctx.mtimeMs < PROSE_QUESTION_FRESH_MS;
|
|
333
|
+
if (questionFresh && looksLikeQuestion(last.content ?? '')) {
|
|
303
334
|
const text = oneLine(last.content ?? '');
|
|
304
335
|
return { ...base, activity: 'waiting_input', awaitingReason: 'question', question: { text, reason: 'question' } };
|
|
305
336
|
}
|
|
@@ -366,6 +397,23 @@ export function inferSessionState(events, ctx = {}) {
|
|
|
366
397
|
const state = inferActivity(events, ctx);
|
|
367
398
|
const { pr, ticket, createdTickets, spawnedTeam } = detectDurableSignals(events);
|
|
368
399
|
const worktree = detectWorktree(ctx.cwd, ctx.gitBranch);
|
|
400
|
+
// Rate-limit: scan the most recent assistant messages + tool errors (tail-first).
|
|
401
|
+
let rateLimited = false;
|
|
402
|
+
for (let i = events.length - 1; i >= 0 && i >= events.length - 12; i--) {
|
|
403
|
+
const e = events[i];
|
|
404
|
+
if (!e)
|
|
405
|
+
continue;
|
|
406
|
+
if (e.type === 'message' && e.role === 'assistant' && detectRateLimited(e.content)) {
|
|
407
|
+
rateLimited = true;
|
|
408
|
+
break;
|
|
409
|
+
}
|
|
410
|
+
if (e.type === 'tool_result' && detectRateLimited(e.output)) {
|
|
411
|
+
rateLimited = true;
|
|
412
|
+
break;
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
if (!rateLimited && detectRateLimited(state.preview))
|
|
416
|
+
rateLimited = true;
|
|
369
417
|
return {
|
|
370
418
|
...state,
|
|
371
419
|
pr: pr ?? state.pr,
|
|
@@ -373,5 +421,6 @@ export function inferSessionState(events, ctx = {}) {
|
|
|
373
421
|
ticket: ticket ?? detectTicket(undefined, ctx.gitBranch) ?? state.ticket,
|
|
374
422
|
createdTickets,
|
|
375
423
|
spawnedTeam,
|
|
424
|
+
rateLimited: rateLimited || undefined,
|
|
376
425
|
};
|
|
377
426
|
}
|
|
@@ -8,11 +8,30 @@
|
|
|
8
8
|
* chunk to the existing content parsers so there's zero duplicated parse logic.
|
|
9
9
|
*/
|
|
10
10
|
import type { SessionAgentId, SessionEvent } from './types.js';
|
|
11
|
+
/** A tail read: the last few normalized events plus the raw text they came from. */
|
|
12
|
+
export interface SessionTail {
|
|
13
|
+
events: SessionEvent[];
|
|
14
|
+
/** The raw JSONL chunk (leading partial line dropped), for content-level math. */
|
|
15
|
+
content: string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Read the last `maxBytes` of a JSONL transcript as cleaned text. A tail that
|
|
19
|
+
* begins mid-file yields one malformed leading line, which is dropped here so
|
|
20
|
+
* downstream per-line parsers only see whole lines. Returns '' on any error or
|
|
21
|
+
* empty file.
|
|
22
|
+
*/
|
|
23
|
+
export declare function readSessionTailContent(filePath: string, maxBytes?: number): string;
|
|
11
24
|
/**
|
|
12
25
|
* Read the last `maxBytes` of a JSONL transcript and return its last
|
|
13
|
-
* `maxEvents` normalized events
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* for live state); other agents return
|
|
26
|
+
* `maxEvents` normalized events *and* the raw text they were parsed from — the
|
|
27
|
+
* raw text feeds content-level readouts (e.g. token throughput) that need the
|
|
28
|
+
* lines the event model discards (Codex `token_count`). Only Claude and Codex
|
|
29
|
+
* are supported (the prioritized harnesses for live state); other agents return
|
|
30
|
+
* an empty tail.
|
|
31
|
+
*/
|
|
32
|
+
export declare function readSessionTailWithRaw(filePath: string, agent: SessionAgentId, maxBytes?: number, maxEvents?: number): SessionTail;
|
|
33
|
+
/**
|
|
34
|
+
* Read the last `maxEvents` normalized events from a JSONL transcript tail. Thin
|
|
35
|
+
* wrapper over {@link readSessionTailWithRaw} for callers that only need events.
|
|
17
36
|
*/
|
|
18
37
|
export declare function readSessionTail(filePath: string, agent: SessionAgentId, maxBytes?: number, maxEvents?: number): SessionEvent[];
|
package/dist/lib/session/tail.js
CHANGED
|
@@ -12,26 +12,23 @@ import { parseClaudeContent, parseCodexContent, sanitizeEvents } from './parse.j
|
|
|
12
12
|
const DEFAULT_MAX_BYTES = 128 * 1024;
|
|
13
13
|
const DEFAULT_MAX_EVENTS = 60;
|
|
14
14
|
/**
|
|
15
|
-
* Read the last `maxBytes` of a JSONL transcript
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* for live state); other agents return `[]`.
|
|
15
|
+
* Read the last `maxBytes` of a JSONL transcript as cleaned text. A tail that
|
|
16
|
+
* begins mid-file yields one malformed leading line, which is dropped here so
|
|
17
|
+
* downstream per-line parsers only see whole lines. Returns '' on any error or
|
|
18
|
+
* empty file.
|
|
20
19
|
*/
|
|
21
|
-
export function
|
|
22
|
-
if (agent !== 'claude' && agent !== 'codex')
|
|
23
|
-
return [];
|
|
20
|
+
export function readSessionTailContent(filePath, maxBytes = DEFAULT_MAX_BYTES) {
|
|
24
21
|
let fd;
|
|
25
22
|
try {
|
|
26
23
|
fd = fs.openSync(filePath, 'r');
|
|
27
24
|
}
|
|
28
25
|
catch {
|
|
29
|
-
return
|
|
26
|
+
return '';
|
|
30
27
|
}
|
|
31
28
|
try {
|
|
32
29
|
const size = fs.fstatSync(fd).size;
|
|
33
30
|
if (size === 0)
|
|
34
|
-
return
|
|
31
|
+
return '';
|
|
35
32
|
const start = Math.max(0, size - maxBytes);
|
|
36
33
|
const len = size - start;
|
|
37
34
|
const buf = Buffer.alloc(len);
|
|
@@ -42,16 +39,37 @@ export function readSessionTail(filePath, agent, maxBytes = DEFAULT_MAX_BYTES, m
|
|
|
42
39
|
const nl = content.indexOf('\n');
|
|
43
40
|
content = nl >= 0 ? content.slice(nl + 1) : '';
|
|
44
41
|
}
|
|
45
|
-
|
|
46
|
-
return [];
|
|
47
|
-
const events = agent === 'codex' ? parseCodexContent(content) : parseClaudeContent(content);
|
|
48
|
-
sanitizeEvents(events);
|
|
49
|
-
return events.length > maxEvents ? events.slice(-maxEvents) : events;
|
|
42
|
+
return content;
|
|
50
43
|
}
|
|
51
44
|
catch {
|
|
52
|
-
return
|
|
45
|
+
return '';
|
|
53
46
|
}
|
|
54
47
|
finally {
|
|
55
48
|
fs.closeSync(fd);
|
|
56
49
|
}
|
|
57
50
|
}
|
|
51
|
+
/**
|
|
52
|
+
* Read the last `maxBytes` of a JSONL transcript and return its last
|
|
53
|
+
* `maxEvents` normalized events *and* the raw text they were parsed from — the
|
|
54
|
+
* raw text feeds content-level readouts (e.g. token throughput) that need the
|
|
55
|
+
* lines the event model discards (Codex `token_count`). Only Claude and Codex
|
|
56
|
+
* are supported (the prioritized harnesses for live state); other agents return
|
|
57
|
+
* an empty tail.
|
|
58
|
+
*/
|
|
59
|
+
export function readSessionTailWithRaw(filePath, agent, maxBytes = DEFAULT_MAX_BYTES, maxEvents = DEFAULT_MAX_EVENTS) {
|
|
60
|
+
if (agent !== 'claude' && agent !== 'codex')
|
|
61
|
+
return { events: [], content: '' };
|
|
62
|
+
const content = readSessionTailContent(filePath, maxBytes);
|
|
63
|
+
if (!content.trim())
|
|
64
|
+
return { events: [], content: '' };
|
|
65
|
+
const events = agent === 'codex' ? parseCodexContent(content) : parseClaudeContent(content);
|
|
66
|
+
sanitizeEvents(events);
|
|
67
|
+
return { events: events.length > maxEvents ? events.slice(-maxEvents) : events, content };
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Read the last `maxEvents` normalized events from a JSONL transcript tail. Thin
|
|
71
|
+
* wrapper over {@link readSessionTailWithRaw} for callers that only need events.
|
|
72
|
+
*/
|
|
73
|
+
export function readSessionTail(filePath, agent, maxBytes = DEFAULT_MAX_BYTES, maxEvents = DEFAULT_MAX_EVENTS) {
|
|
74
|
+
return readSessionTailWithRaw(filePath, agent, maxBytes, maxEvents).events;
|
|
75
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Live output-token throughput.
|
|
3
|
+
*
|
|
4
|
+
* Computes a rolling tokens-per-second readout from a session transcript's raw
|
|
5
|
+
* content. This is the single source of truth for the throughput number the
|
|
6
|
+
* Factory Floor shows next to a running agent — the extension used to carry its
|
|
7
|
+
* own copy of this math (`computeOutputTokensPerSec`); it now reads `tokPerSec`
|
|
8
|
+
* straight off `agents sessions --active --json` instead (issue #741).
|
|
9
|
+
*/
|
|
10
|
+
/** Agents whose transcript formats report per-turn output-token usage. */
|
|
11
|
+
export type ThroughputAgent = 'claude' | 'codex' | 'gemini';
|
|
12
|
+
/** Rolling window (seconds) the throughput average is computed over. */
|
|
13
|
+
export declare const DEFAULT_THROUGHPUT_WINDOW_SEC = 60;
|
|
14
|
+
/**
|
|
15
|
+
* Output-token throughput (tokens/sec) over the last `windowSec` seconds.
|
|
16
|
+
*
|
|
17
|
+
* Sums output tokens (plus reasoning/thoughts tokens when the format reports
|
|
18
|
+
* them separately) from entries whose timestamp falls within the window, and
|
|
19
|
+
* divides by the window length.
|
|
20
|
+
*
|
|
21
|
+
* Formats:
|
|
22
|
+
* - Claude: JSONL. Each assistant turn is `{type: 'assistant', timestamp,
|
|
23
|
+
* message: {usage: {output_tokens}}}`.
|
|
24
|
+
* - Codex: JSONL. Each token_count event is `{type: 'event_msg', timestamp,
|
|
25
|
+
* payload: {type: 'token_count', info: {last_token_usage: {output_tokens,
|
|
26
|
+
* reasoning_output_tokens}}}}`. `last_token_usage` is per-turn (not cumulative).
|
|
27
|
+
* - Gemini: single JSON object. `{messages: [{type: 'gemini', timestamp,
|
|
28
|
+
* tokens: {output, thoughts}}]}`. Caller must pass the whole file.
|
|
29
|
+
*/
|
|
30
|
+
export declare function computeTokPerSec(sessionContent: string, agent: ThroughputAgent, windowSec?: number, now?: number): number;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Live output-token throughput.
|
|
3
|
+
*
|
|
4
|
+
* Computes a rolling tokens-per-second readout from a session transcript's raw
|
|
5
|
+
* content. This is the single source of truth for the throughput number the
|
|
6
|
+
* Factory Floor shows next to a running agent — the extension used to carry its
|
|
7
|
+
* own copy of this math (`computeOutputTokensPerSec`); it now reads `tokPerSec`
|
|
8
|
+
* straight off `agents sessions --active --json` instead (issue #741).
|
|
9
|
+
*/
|
|
10
|
+
/** Rolling window (seconds) the throughput average is computed over. */
|
|
11
|
+
export const DEFAULT_THROUGHPUT_WINDOW_SEC = 60;
|
|
12
|
+
/**
|
|
13
|
+
* Output-token throughput (tokens/sec) over the last `windowSec` seconds.
|
|
14
|
+
*
|
|
15
|
+
* Sums output tokens (plus reasoning/thoughts tokens when the format reports
|
|
16
|
+
* them separately) from entries whose timestamp falls within the window, and
|
|
17
|
+
* divides by the window length.
|
|
18
|
+
*
|
|
19
|
+
* Formats:
|
|
20
|
+
* - Claude: JSONL. Each assistant turn is `{type: 'assistant', timestamp,
|
|
21
|
+
* message: {usage: {output_tokens}}}`.
|
|
22
|
+
* - Codex: JSONL. Each token_count event is `{type: 'event_msg', timestamp,
|
|
23
|
+
* payload: {type: 'token_count', info: {last_token_usage: {output_tokens,
|
|
24
|
+
* reasoning_output_tokens}}}}`. `last_token_usage` is per-turn (not cumulative).
|
|
25
|
+
* - Gemini: single JSON object. `{messages: [{type: 'gemini', timestamp,
|
|
26
|
+
* tokens: {output, thoughts}}]}`. Caller must pass the whole file.
|
|
27
|
+
*/
|
|
28
|
+
export function computeTokPerSec(sessionContent, agent, windowSec = DEFAULT_THROUGHPUT_WINDOW_SEC, now = Date.now()) {
|
|
29
|
+
const cutoff = now - windowSec * 1000;
|
|
30
|
+
let total = 0;
|
|
31
|
+
if (agent === 'gemini') {
|
|
32
|
+
try {
|
|
33
|
+
const d = JSON.parse(sessionContent);
|
|
34
|
+
const messages = Array.isArray(d?.messages) ? d.messages : [];
|
|
35
|
+
for (const m of messages) {
|
|
36
|
+
if (m?.type !== 'gemini')
|
|
37
|
+
continue;
|
|
38
|
+
const ts = typeof m.timestamp === 'string' ? Date.parse(m.timestamp) : 0;
|
|
39
|
+
if (!ts || ts < cutoff)
|
|
40
|
+
continue;
|
|
41
|
+
const out = typeof m?.tokens?.output === 'number' ? m.tokens.output : 0;
|
|
42
|
+
const thoughts = typeof m?.tokens?.thoughts === 'number' ? m.tokens.thoughts : 0;
|
|
43
|
+
total += out + thoughts;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
catch { /* malformed gemini file → 0 */ }
|
|
47
|
+
return total / windowSec;
|
|
48
|
+
}
|
|
49
|
+
const lines = sessionContent.split(/\r?\n/);
|
|
50
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
51
|
+
const line = lines[i];
|
|
52
|
+
if (!line || line[0] !== '{')
|
|
53
|
+
continue;
|
|
54
|
+
if (!line.includes('output_tokens'))
|
|
55
|
+
continue;
|
|
56
|
+
try {
|
|
57
|
+
const d = JSON.parse(line);
|
|
58
|
+
const ts = typeof d.timestamp === 'string' ? Date.parse(d.timestamp) : 0;
|
|
59
|
+
if (!ts)
|
|
60
|
+
continue;
|
|
61
|
+
if (ts < cutoff)
|
|
62
|
+
break;
|
|
63
|
+
if (agent === 'claude') {
|
|
64
|
+
if (d?.type !== 'assistant')
|
|
65
|
+
continue;
|
|
66
|
+
const out = typeof d?.message?.usage?.output_tokens === 'number' ? d.message.usage.output_tokens : 0;
|
|
67
|
+
total += out;
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
if (d?.type !== 'event_msg')
|
|
71
|
+
continue;
|
|
72
|
+
const payload = d?.payload;
|
|
73
|
+
if (payload?.type !== 'token_count')
|
|
74
|
+
continue;
|
|
75
|
+
const last = payload?.info?.last_token_usage;
|
|
76
|
+
if (!last)
|
|
77
|
+
continue;
|
|
78
|
+
const out = typeof last.output_tokens === 'number' ? last.output_tokens : 0;
|
|
79
|
+
const reasoning = typeof last.reasoning_output_tokens === 'number' ? last.reasoning_output_tokens : 0;
|
|
80
|
+
total += out + reasoning;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
catch { /* skip malformed line */ }
|
|
84
|
+
}
|
|
85
|
+
return total / windowSec;
|
|
86
|
+
}
|
package/dist/lib/shim-heal.d.ts
CHANGED
|
@@ -1,7 +1,16 @@
|
|
|
1
|
+
import type { SelfHealReport } from './self-heal/types.js';
|
|
2
|
+
export interface InteractiveShimHealResult {
|
|
3
|
+
noticeLines: string[] | null;
|
|
4
|
+
report: SelfHealReport;
|
|
5
|
+
}
|
|
1
6
|
/**
|
|
2
|
-
* Heal the shim/shadow/PATH conditions silently, then return the
|
|
3
|
-
* print ONCE for whatever is left (a real-binary shadow we
|
|
4
|
-
* that was just added / needs a reload)
|
|
7
|
+
* Heal the shim/shadow/PATH conditions silently, then return both the raw report
|
|
8
|
+
* and the notice lines to print ONCE for whatever is left (a real-binary shadow we
|
|
9
|
+
* won't move; a PATH entry that was just added / needs a reload).
|
|
10
|
+
*/
|
|
11
|
+
export declare function runInteractiveShimHeal(): Promise<InteractiveShimHealResult>;
|
|
12
|
+
/**
|
|
13
|
+
* Back-compat wrapper for callers that only need the persistent one-time notice.
|
|
5
14
|
*/
|
|
6
15
|
export declare function healShimsInteractive(): Promise<string[] | null>;
|
|
7
16
|
type PathNoticeState = 'ok' | 'added' | 'reload';
|
package/dist/lib/shim-heal.js
CHANGED
|
@@ -12,11 +12,11 @@ import fs from 'node:fs';
|
|
|
12
12
|
import path from 'node:path';
|
|
13
13
|
import { getRuntimeStateDir } from './state.js';
|
|
14
14
|
/**
|
|
15
|
-
* Heal the shim/shadow/PATH conditions silently, then return the
|
|
16
|
-
* print ONCE for whatever is left (a real-binary shadow we
|
|
17
|
-
* that was just added / needs a reload)
|
|
15
|
+
* Heal the shim/shadow/PATH conditions silently, then return both the raw report
|
|
16
|
+
* and the notice lines to print ONCE for whatever is left (a real-binary shadow we
|
|
17
|
+
* won't move; a PATH entry that was just added / needs a reload).
|
|
18
18
|
*/
|
|
19
|
-
export async function
|
|
19
|
+
export async function runInteractiveShimHeal() {
|
|
20
20
|
const { runSelfHeal } = await import('./self-heal/registry.js');
|
|
21
21
|
const report = await runSelfHeal({ checks: ['shims', 'shadowing', 'path'], mode: 'safe' });
|
|
22
22
|
const shadowNotes = [];
|
|
@@ -37,7 +37,7 @@ export async function healShimsInteractive() {
|
|
|
37
37
|
const pathState = pathAdded ? 'added' : pathReload ? 'reload' : 'ok';
|
|
38
38
|
const signature = computeShimNoticeSignature({ shadowNotes, pathState });
|
|
39
39
|
if (!shouldSurfaceShimNotice(signature))
|
|
40
|
-
return null;
|
|
40
|
+
return { noticeLines: null, report };
|
|
41
41
|
const lines = [];
|
|
42
42
|
if (pathAdded) {
|
|
43
43
|
lines.push(pathAdded);
|
|
@@ -52,7 +52,13 @@ export async function healShimsInteractive() {
|
|
|
52
52
|
lines.push(` ${note}`);
|
|
53
53
|
lines.push("It's a real binary (not a symlink), so agents-cli won't move it — reorder PATH or remove it to hand it over.");
|
|
54
54
|
}
|
|
55
|
-
return lines.length > 0 ? lines : null;
|
|
55
|
+
return { noticeLines: lines.length > 0 ? lines : null, report };
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Back-compat wrapper for callers that only need the persistent one-time notice.
|
|
59
|
+
*/
|
|
60
|
+
export async function healShimsInteractive() {
|
|
61
|
+
return (await runInteractiveShimHeal()).noticeLines;
|
|
56
62
|
}
|
|
57
63
|
function noticeStatePath() {
|
|
58
64
|
return path.join(getRuntimeStateDir(), 'shim-notice.json');
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Subagents detector. Claude: flat .md files under `<agentDir>/agents/`.
|
|
3
|
+
* Codex: flat .toml files under `<versionHome>/.codex/agents/`.
|
|
3
4
|
* Droid: flat .md files under `<versionHome>/.factory/droids/`.
|
|
4
5
|
* OpenClaw: subdirectories containing AGENTS.md under `<versionHome>/.openclaw/`.
|
|
5
6
|
* Mirrors versions.ts:521-539.
|
|
@@ -8,12 +9,12 @@ import * as fs from 'fs';
|
|
|
8
9
|
import * as path from 'path';
|
|
9
10
|
import { capableAgents } from '../../capabilities.js';
|
|
10
11
|
import { lazyAgentMap } from '../writers/lazy-map.js';
|
|
11
|
-
function
|
|
12
|
+
function buildFlatMdAgentsDetector(agent, agentsRoot) {
|
|
12
13
|
return {
|
|
13
14
|
kind: 'subagents',
|
|
14
|
-
agent
|
|
15
|
+
agent,
|
|
15
16
|
list({ versionHome }) {
|
|
16
|
-
const agentsDir = path.join(versionHome,
|
|
17
|
+
const agentsDir = path.join(versionHome, agentsRoot, 'agents');
|
|
17
18
|
if (!fs.existsSync(agentsDir))
|
|
18
19
|
return [];
|
|
19
20
|
return fs.readdirSync(agentsDir)
|
|
@@ -22,6 +23,26 @@ function buildClaudeDetector() {
|
|
|
22
23
|
},
|
|
23
24
|
};
|
|
24
25
|
}
|
|
26
|
+
function buildClaudeDetector() {
|
|
27
|
+
return buildFlatMdAgentsDetector('claude', '.claude');
|
|
28
|
+
}
|
|
29
|
+
function buildGrokDetector() {
|
|
30
|
+
return buildFlatMdAgentsDetector('grok', '.grok');
|
|
31
|
+
}
|
|
32
|
+
function buildCodexDetector() {
|
|
33
|
+
return {
|
|
34
|
+
kind: 'subagents',
|
|
35
|
+
agent: 'codex',
|
|
36
|
+
list({ versionHome }) {
|
|
37
|
+
const agentsDir = path.join(versionHome, '.codex', 'agents');
|
|
38
|
+
if (!fs.existsSync(agentsDir))
|
|
39
|
+
return [];
|
|
40
|
+
return fs.readdirSync(agentsDir)
|
|
41
|
+
.filter(f => f.endsWith('.toml'))
|
|
42
|
+
.map(f => f.replace(/\.toml$/, ''));
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
}
|
|
25
46
|
function buildDroidDetector() {
|
|
26
47
|
return {
|
|
27
48
|
kind: 'subagents',
|
|
@@ -50,8 +71,41 @@ function buildOpenclawDetector() {
|
|
|
50
71
|
},
|
|
51
72
|
};
|
|
52
73
|
}
|
|
74
|
+
function buildKimiDetector() {
|
|
75
|
+
return {
|
|
76
|
+
kind: 'subagents',
|
|
77
|
+
agent: 'kimi',
|
|
78
|
+
list({ versionHome }) {
|
|
79
|
+
const agentsDir = path.join(versionHome, '.kimi-code', 'agents');
|
|
80
|
+
if (!fs.existsSync(agentsDir))
|
|
81
|
+
return [];
|
|
82
|
+
// Parent is `_agents-cli.yaml` (underscore-prefixed reserved name).
|
|
83
|
+
return fs.readdirSync(agentsDir)
|
|
84
|
+
.filter(f => f.endsWith('.yaml') && !f.startsWith('_'))
|
|
85
|
+
.map(f => f.replace(/\.yaml$/, ''));
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
function buildOpenCodeDetector() {
|
|
90
|
+
return {
|
|
91
|
+
kind: 'subagents',
|
|
92
|
+
agent: 'opencode',
|
|
93
|
+
list({ versionHome }) {
|
|
94
|
+
const agentsDir = path.join(versionHome, '.config', 'opencode', 'agents');
|
|
95
|
+
if (!fs.existsSync(agentsDir))
|
|
96
|
+
return [];
|
|
97
|
+
return fs.readdirSync(agentsDir)
|
|
98
|
+
.filter(f => f.endsWith('.md'))
|
|
99
|
+
.map(f => f.replace(/\.md$/, ''));
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
}
|
|
53
103
|
const handlers = {
|
|
54
104
|
claude: buildClaudeDetector,
|
|
105
|
+
grok: buildGrokDetector,
|
|
106
|
+
codex: buildCodexDetector,
|
|
107
|
+
kimi: buildKimiDetector,
|
|
108
|
+
opencode: buildOpenCodeDetector,
|
|
55
109
|
droid: buildDroidDetector,
|
|
56
110
|
openclaw: buildOpenclawDetector,
|
|
57
111
|
};
|
|
@@ -30,9 +30,13 @@ function buildHooksWriter(agent) {
|
|
|
30
30
|
fs.chmodSync(destFile, 0o755);
|
|
31
31
|
synced.push(hook);
|
|
32
32
|
}
|
|
33
|
-
// Native hook registration in settings.json/hooks.json. Grok
|
|
34
|
-
//
|
|
35
|
-
|
|
33
|
+
// Native hook registration in settings.json/hooks.json. Grok is included
|
|
34
|
+
// so subrule-bundled guards (absolute paths outside the central hooks/
|
|
35
|
+
// copy set) get registered into ~/.grok/hooks/hooks.json via
|
|
36
|
+
// registerHooksForGrok — file copy alone only sees top-level available.hooks
|
|
37
|
+
// names (RUSH-1353). Copilot/Kiro/Goose load managed *.json under their
|
|
38
|
+
// hooks dirs the same way.
|
|
39
|
+
if (agent === 'claude' || agent === 'codex' || agent === 'gemini' || agent === 'antigravity' || agent === 'kimi' || agent === 'droid' || agent === 'copilot' || agent === 'kiro' || agent === 'goose' || agent === 'cursor' || agent === 'grok') {
|
|
36
40
|
registerHooksToSettings(agent, versionHome);
|
|
37
41
|
}
|
|
38
42
|
return { synced };
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Subagents writer. Claude flattens each subagent into a single .md file
|
|
3
|
-
* under `<agentDir>/agents/`.
|
|
4
|
-
*
|
|
5
|
-
*
|
|
3
|
+
* under `<agentDir>/agents/`. Codex writes TOML under `.codex/agents/`.
|
|
4
|
+
* Droid (Factory AI) flattens each into a custom droid .md under
|
|
5
|
+
* `<versionHome>/.factory/droids/`. OpenClaw copies the full subagent
|
|
6
|
+
* directory (with AGENT.md renamed to AGENTS.md) into
|
|
6
7
|
* `<versionHome>/.openclaw/<name>/`.
|
|
7
8
|
*
|
|
8
9
|
* Source-side discovery is `listInstalledSubagents` from lib/subagents.ts —
|
|
@@ -12,7 +13,7 @@
|
|
|
12
13
|
import * as fs from 'fs';
|
|
13
14
|
import * as path from 'path';
|
|
14
15
|
import { capableAgents } from '../../capabilities.js';
|
|
15
|
-
import { listInstalledSubagents, transformSubagentForClaude, transformSubagentForDroid, syncSubagentToOpenclaw } from '../../subagents.js';
|
|
16
|
+
import { listInstalledSubagents, transformSubagentForClaude, transformSubagentForCodex, writeKimiSubagentFiles, buildKimiSubagentsParentYaml, KIMI_SUBAGENTS_PARENT_FILE, transformSubagentForOpenCode, transformSubagentForDroid, syncSubagentToOpenclaw, parseSubagentFrontmatter, } from '../../subagents.js';
|
|
16
17
|
import { safeJoin } from '../../paths.js';
|
|
17
18
|
import { lazyAgentMap } from './lazy-map.js';
|
|
18
19
|
function buildSubagentsWriter(agent) {
|
|
@@ -28,12 +29,28 @@ function buildSubagentsWriter(agent) {
|
|
|
28
29
|
if (!sub)
|
|
29
30
|
continue;
|
|
30
31
|
try {
|
|
31
|
-
if (agent === 'claude') {
|
|
32
|
-
const agentsDir = path.join(versionHome, '.claude', 'agents');
|
|
32
|
+
if (agent === 'claude' || agent === 'grok') {
|
|
33
|
+
const agentsDir = path.join(versionHome, agent === 'grok' ? '.grok' : '.claude', 'agents');
|
|
33
34
|
fs.mkdirSync(agentsDir, { recursive: true });
|
|
34
35
|
fs.writeFileSync(safeJoin(agentsDir, `${sub.name}.md`), transformSubagentForClaude(sub.path));
|
|
35
36
|
synced.push(sub.name);
|
|
36
37
|
}
|
|
38
|
+
else if (agent === 'codex') {
|
|
39
|
+
const agentsDir = path.join(versionHome, '.codex', 'agents');
|
|
40
|
+
fs.mkdirSync(agentsDir, { recursive: true });
|
|
41
|
+
fs.writeFileSync(safeJoin(agentsDir, `${sub.name}.toml`), transformSubagentForCodex(sub.path));
|
|
42
|
+
synced.push(sub.name);
|
|
43
|
+
}
|
|
44
|
+
else if (agent === 'kimi') {
|
|
45
|
+
writeKimiSubagentFiles(path.join(versionHome, '.kimi-code', 'agents'), sub.path, sub.name);
|
|
46
|
+
synced.push(sub.name);
|
|
47
|
+
}
|
|
48
|
+
else if (agent === 'opencode') {
|
|
49
|
+
const agentsDir = path.join(versionHome, '.config', 'opencode', 'agents');
|
|
50
|
+
fs.mkdirSync(agentsDir, { recursive: true });
|
|
51
|
+
fs.writeFileSync(safeJoin(agentsDir, `${sub.name}.md`), transformSubagentForOpenCode(sub.path));
|
|
52
|
+
synced.push(sub.name);
|
|
53
|
+
}
|
|
37
54
|
else if (agent === 'droid') {
|
|
38
55
|
const droidsDir = path.join(versionHome, '.factory', 'droids');
|
|
39
56
|
fs.mkdirSync(droidsDir, { recursive: true });
|
|
@@ -49,6 +66,20 @@ function buildSubagentsWriter(agent) {
|
|
|
49
66
|
}
|
|
50
67
|
catch { /* per-item sync failure: skip */ }
|
|
51
68
|
}
|
|
69
|
+
// Kimi parent agent file listing all synced subagents for --agent-file.
|
|
70
|
+
if (agent === 'kimi' && synced.length > 0) {
|
|
71
|
+
const agentsDir = path.join(versionHome, '.kimi-code', 'agents');
|
|
72
|
+
const entries = synced.map((name) => {
|
|
73
|
+
const sub = map.get(name);
|
|
74
|
+
const fm = parseSubagentFrontmatter(path.join(sub.path, 'AGENT.md'));
|
|
75
|
+
return {
|
|
76
|
+
name,
|
|
77
|
+
description: fm?.description ?? name,
|
|
78
|
+
relativePath: `./${name}.yaml`,
|
|
79
|
+
};
|
|
80
|
+
});
|
|
81
|
+
fs.writeFileSync(safeJoin(agentsDir, KIMI_SUBAGENTS_PARENT_FILE), buildKimiSubagentsParentYaml(entries));
|
|
82
|
+
}
|
|
52
83
|
return { synced };
|
|
53
84
|
},
|
|
54
85
|
};
|
|
@@ -30,6 +30,7 @@ export declare const loadCommands: ModuleLoader;
|
|
|
30
30
|
export declare const loadHooks: ModuleLoader;
|
|
31
31
|
export declare const loadSkills: ModuleLoader;
|
|
32
32
|
export declare const loadRules: ModuleLoader;
|
|
33
|
+
export declare const loadMemory: ModuleLoader;
|
|
33
34
|
export declare const loadPermissions: ModuleLoader;
|
|
34
35
|
export declare const loadMcp: ModuleLoader;
|
|
35
36
|
export declare const loadCli: ModuleLoader;
|
|
@@ -83,6 +84,7 @@ export declare const loadSessions: ModuleLoader;
|
|
|
83
84
|
export declare const loadTeams: ModuleLoader;
|
|
84
85
|
export declare const loadCloud: ModuleLoader;
|
|
85
86
|
export declare const loadMessage: ModuleLoader;
|
|
87
|
+
export declare const loadFeed: ModuleLoader;
|
|
86
88
|
export declare const loadServe: ModuleLoader;
|
|
87
89
|
export declare const loadAudit: ModuleLoader;
|
|
88
90
|
/**
|
|
@@ -8,6 +8,7 @@ export const loadCommands = async () => (await import('../../commands/commands.j
|
|
|
8
8
|
export const loadHooks = async () => (await import('../../commands/hooks.js')).registerHooksCommands;
|
|
9
9
|
export const loadSkills = async () => (await import('../../commands/skills.js')).registerSkillsCommands;
|
|
10
10
|
export const loadRules = async () => (await import('../../commands/rules.js')).registerRulesCommands;
|
|
11
|
+
export const loadMemory = async () => (await import('../../commands/memory.js')).registerMemoryCommands;
|
|
11
12
|
export const loadPermissions = async () => (await import('../../commands/permissions.js')).registerPermissionsCommands;
|
|
12
13
|
export const loadMcp = async () => (await import('../../commands/mcp.js')).registerMcpCommands;
|
|
13
14
|
export const loadCli = async () => (await import('../../commands/cli.js')).registerCliCommands;
|
|
@@ -61,6 +62,7 @@ export const loadSessions = async () => (await import('../../commands/sessions.j
|
|
|
61
62
|
export const loadTeams = async () => (await import('../../commands/teams.js')).registerTeamsCommands;
|
|
62
63
|
export const loadCloud = async () => (await import('../../commands/cloud.js')).registerCloudCommands;
|
|
63
64
|
export const loadMessage = async () => (await import('../../commands/message.js')).registerMessageCommand;
|
|
65
|
+
export const loadFeed = async () => (await import('../../commands/feed.js')).registerFeedCommand;
|
|
64
66
|
export const loadServe = async () => (await import('../../commands/serve.js')).registerServeCommand;
|
|
65
67
|
export const loadAudit = async () => (await import('../../commands/audit.js')).registerAuditCommands;
|
|
66
68
|
/**
|
|
@@ -93,6 +95,7 @@ export const COMMAND_LOADERS = {
|
|
|
93
95
|
hooks: [loadHooks],
|
|
94
96
|
skills: [loadSkills],
|
|
95
97
|
rules: [loadRules],
|
|
98
|
+
memory: [loadMemory],
|
|
96
99
|
permissions: [loadPermissions],
|
|
97
100
|
mcp: [loadMcp],
|
|
98
101
|
cli: [loadCli],
|
|
@@ -154,6 +157,7 @@ export const COMMAND_LOADERS = {
|
|
|
154
157
|
teams: [loadTeams],
|
|
155
158
|
cloud: [loadCloud],
|
|
156
159
|
message: [loadMessage],
|
|
160
|
+
feed: [loadFeed],
|
|
157
161
|
serve: [loadServe],
|
|
158
162
|
audit: [loadAudit],
|
|
159
163
|
};
|
package/dist/lib/state.d.ts
CHANGED
|
@@ -122,6 +122,8 @@ export declare function getProjectRoutinesDir(cwd?: string): string | null;
|
|
|
122
122
|
export declare function getRunsDir(): string;
|
|
123
123
|
/** Root for per-agent mailboxes (~/.agents/.history/mailbox/). */
|
|
124
124
|
export declare function getMailboxRootDir(): string;
|
|
125
|
+
/** Root for open-block feed records (~/.agents/.history/feed/). */
|
|
126
|
+
export declare function getFeedDir(): string;
|
|
125
127
|
/** Path to installed agent CLI binaries (~/.agents/.history/versions/). */
|
|
126
128
|
export declare function getVersionsDir(): string;
|
|
127
129
|
/** Path to version-switching shim scripts (~/.agents/.cache/shims/). */
|