@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
|
@@ -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;
|
|
@@ -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;
|
|
@@ -94,6 +95,7 @@ export const COMMAND_LOADERS = {
|
|
|
94
95
|
hooks: [loadHooks],
|
|
95
96
|
skills: [loadSkills],
|
|
96
97
|
rules: [loadRules],
|
|
98
|
+
memory: [loadMemory],
|
|
97
99
|
permissions: [loadPermissions],
|
|
98
100
|
mcp: [loadMcp],
|
|
99
101
|
cli: [loadCli],
|
package/dist/lib/subagents.d.ts
CHANGED
|
@@ -56,6 +56,57 @@ export declare function transformSubagentForClaude(subagentDir: string): string;
|
|
|
56
56
|
* See https://docs.factory.ai/cli/configuration/custom-droids.
|
|
57
57
|
*/
|
|
58
58
|
export declare function transformSubagentForDroid(subagentDir: string): string;
|
|
59
|
+
/**
|
|
60
|
+
* Transform a subagent into an OpenCode agent markdown file.
|
|
61
|
+
*
|
|
62
|
+
* OpenCode loads agents from ~/.config/opencode/agents/*.md (global) with
|
|
63
|
+
* YAML frontmatter (description required; mode: subagent for invocable
|
|
64
|
+
* subagents) and a prompt body. File stem becomes the agent name.
|
|
65
|
+
* https://opencode.ai/docs/agents/
|
|
66
|
+
*/
|
|
67
|
+
export declare function transformSubagentForOpenCode(subagentDir: string): string;
|
|
68
|
+
/** Managed parent agent file for Kimi (underscore prefix avoids clobbering a user subagent named agents-cli). */
|
|
69
|
+
export declare const KIMI_SUBAGENTS_PARENT_FILE = "_agents-cli.yaml";
|
|
70
|
+
export interface KimiSubagentFiles {
|
|
71
|
+
/** Agent YAML content (uses system_prompt_path, not inline system_prompt). */
|
|
72
|
+
yaml: string;
|
|
73
|
+
/** Markdown body written next to the YAML and referenced by system_prompt_path. */
|
|
74
|
+
systemPrompt: string;
|
|
75
|
+
/** Sibling prompt filename, e.g. `reviewer.system.md`. */
|
|
76
|
+
systemPromptFileName: string;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Transform a subagent into Kimi Code agent files.
|
|
80
|
+
*
|
|
81
|
+
* Kimi's schema (agentspec) only accepts `system_prompt_path` (path relative to
|
|
82
|
+
* the agent YAML) — there is no inline `system_prompt` key. We write:
|
|
83
|
+
* ~/.kimi-code/agents/<name>.yaml — agent YAML
|
|
84
|
+
* ~/.kimi-code/agents/<name>.system.md — system prompt body
|
|
85
|
+
* plus a managed parent `_agents-cli.yaml` that lists them under agent.subagents
|
|
86
|
+
* for `kimi --agent-file …/_agents-cli.yaml`.
|
|
87
|
+
* https://moonshotai.github.io/kimi-cli/en/customization/agents.html
|
|
88
|
+
*/
|
|
89
|
+
export declare function transformSubagentForKimi(subagentDir: string, fileBaseName: string): KimiSubagentFiles;
|
|
90
|
+
/**
|
|
91
|
+
* Build the managed parent agent YAML that declares all installed subagents
|
|
92
|
+
* so `kimi --agent-file ~/.kimi-code/agents/_agents-cli.yaml` can launch them.
|
|
93
|
+
*/
|
|
94
|
+
export declare function buildKimiSubagentsParentYaml(entries: Array<{
|
|
95
|
+
name: string;
|
|
96
|
+
description: string;
|
|
97
|
+
relativePath: string;
|
|
98
|
+
}>): string;
|
|
99
|
+
/** Write a Kimi subagent YAML + sibling system-prompt md into agentsDir. */
|
|
100
|
+
export declare function writeKimiSubagentFiles(agentsDir: string, subagentDir: string, name: string): void;
|
|
101
|
+
/**
|
|
102
|
+
* Transform a subagent into a Codex custom-agent TOML file.
|
|
103
|
+
*
|
|
104
|
+
* Codex loads standalone TOML under ~/.codex/agents/*.toml. Required fields:
|
|
105
|
+
* name, description, developer_instructions. Optional model maps from our
|
|
106
|
+
* frontmatter when present.
|
|
107
|
+
* https://developers.openai.com/codex/subagents (custom agents section)
|
|
108
|
+
*/
|
|
109
|
+
export declare function transformSubagentForCodex(subagentDir: string): string;
|
|
59
110
|
/**
|
|
60
111
|
* Sync a subagent to an OpenClaw workspace
|
|
61
112
|
* Copies full directory, renames AGENT.md to AGENTS.md
|
|
@@ -85,6 +136,7 @@ export declare function subagentContentMatches(installedDir: string, sourceDir:
|
|
|
85
136
|
/**
|
|
86
137
|
* List subagents installed to a specific agent's home
|
|
87
138
|
* Claude: scans ~/.claude/agents/{name}.md
|
|
139
|
+
* Kimi: scans ~/.kimi-code/agents/{name}.yaml (+ sibling .system.md)
|
|
88
140
|
* OpenClaw: scans ~/.openclaw/{name}/AGENTS.md
|
|
89
141
|
*/
|
|
90
142
|
export declare function listSubagentsForAgent(agentId: AgentId, home: string): InstalledSubagent[];
|