@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.
Files changed (107) hide show
  1. package/CHANGELOG.md +57 -2
  2. package/README.md +12 -0
  3. package/dist/bin/agents +0 -0
  4. package/dist/commands/cloud.d.ts +3 -0
  5. package/dist/commands/cloud.js +2 -1
  6. package/dist/commands/exec.js +65 -1
  7. package/dist/commands/feed.d.ts +29 -0
  8. package/dist/commands/feed.js +237 -32
  9. package/dist/commands/memory.d.ts +9 -0
  10. package/dist/commands/memory.js +164 -0
  11. package/dist/commands/message.d.ts +11 -6
  12. package/dist/commands/message.js +140 -5
  13. package/dist/commands/routines.js +12 -0
  14. package/dist/commands/secrets-migrate.d.ts +2 -1
  15. package/dist/commands/secrets-migrate.js +88 -13
  16. package/dist/commands/secrets.js +4 -1
  17. package/dist/commands/sessions.js +10 -1
  18. package/dist/commands/worktree.js +4 -2
  19. package/dist/index.js +16 -21
  20. package/dist/lib/agents.js +249 -17
  21. package/dist/lib/answer-router.d.ts +75 -0
  22. package/dist/lib/answer-router.js +149 -0
  23. package/dist/lib/ask-classifier.d.ts +71 -0
  24. package/dist/lib/ask-classifier.js +197 -0
  25. package/dist/lib/cloud/antigravity.d.ts +0 -2
  26. package/dist/lib/cloud/antigravity.js +2 -17
  27. package/dist/lib/cloud/codex.js +3 -18
  28. package/dist/lib/cloud/rush.js +3 -15
  29. package/dist/lib/cloud/stream.js +2 -0
  30. package/dist/lib/cloud/types.d.ts +21 -0
  31. package/dist/lib/cloud/types.js +81 -0
  32. package/dist/lib/crabbox/cli.d.ts +1 -1
  33. package/dist/lib/crabbox/cli.js +12 -2
  34. package/dist/lib/crabbox/lease.d.ts +13 -0
  35. package/dist/lib/crabbox/lease.js +11 -2
  36. package/dist/lib/crabbox/progress.d.ts +62 -0
  37. package/dist/lib/crabbox/progress.js +129 -0
  38. package/dist/lib/events.js +4 -1
  39. package/dist/lib/exec.js +19 -1
  40. package/dist/lib/feed-outcome.d.ts +101 -0
  41. package/dist/lib/feed-outcome.js +244 -0
  42. package/dist/lib/feed-policy.d.ts +30 -0
  43. package/dist/lib/feed-policy.js +133 -0
  44. package/dist/lib/feed.d.ts +127 -3
  45. package/dist/lib/feed.js +416 -40
  46. package/dist/lib/git.d.ts +17 -1
  47. package/dist/lib/git.js +20 -1
  48. package/dist/lib/hooks.js +522 -12
  49. package/dist/lib/hosts/passthrough.d.ts +3 -3
  50. package/dist/lib/hosts/passthrough.js +3 -4
  51. package/dist/lib/mailbox-gc.d.ts +22 -0
  52. package/dist/lib/mailbox-gc.js +161 -0
  53. package/dist/lib/mailbox.d.ts +26 -2
  54. package/dist/lib/mailbox.js +80 -5
  55. package/dist/lib/mcp.js +82 -0
  56. package/dist/lib/memory.d.ts +55 -0
  57. package/dist/lib/memory.js +274 -0
  58. package/dist/lib/notify.d.ts +16 -0
  59. package/dist/lib/notify.js +61 -0
  60. package/dist/lib/operator.d.ts +26 -0
  61. package/dist/lib/operator.js +107 -0
  62. package/dist/lib/plugins.d.ts +35 -0
  63. package/dist/lib/plugins.js +217 -0
  64. package/dist/lib/remote-agents-json.d.ts +14 -0
  65. package/dist/lib/remote-agents-json.js +94 -0
  66. package/dist/lib/resources/mcp.js +44 -0
  67. package/dist/lib/resources/memory.d.ts +15 -0
  68. package/dist/lib/resources/memory.js +46 -0
  69. package/dist/lib/resources/types.d.ts +2 -2
  70. package/dist/lib/runner.d.ts +43 -0
  71. package/dist/lib/runner.js +323 -74
  72. package/dist/lib/sandbox.js +6 -0
  73. package/dist/lib/secrets/bundles.js +38 -13
  74. package/dist/lib/secrets/icloud-import.d.ts +12 -3
  75. package/dist/lib/secrets/icloud-import.js +37 -7
  76. package/dist/lib/secrets/index.d.ts +106 -2
  77. package/dist/lib/secrets/index.js +603 -28
  78. package/dist/lib/session/active.d.ts +18 -0
  79. package/dist/lib/session/active.js +47 -17
  80. package/dist/lib/session/db.d.ts +9 -1
  81. package/dist/lib/session/db.js +18 -3
  82. package/dist/lib/session/discover.d.ts +13 -0
  83. package/dist/lib/session/discover.js +31 -0
  84. package/dist/lib/session/parse.d.ts +8 -0
  85. package/dist/lib/session/parse.js +42 -21
  86. package/dist/lib/session/remote-active.js +8 -89
  87. package/dist/lib/session/state.d.ts +11 -0
  88. package/dist/lib/session/state.js +37 -0
  89. package/dist/lib/session/tail.d.ts +23 -4
  90. package/dist/lib/session/tail.js +34 -16
  91. package/dist/lib/session/throughput.d.ts +30 -0
  92. package/dist/lib/session/throughput.js +86 -0
  93. package/dist/lib/shim-heal.d.ts +12 -3
  94. package/dist/lib/shim-heal.js +12 -6
  95. package/dist/lib/staleness/detectors/subagents.js +57 -3
  96. package/dist/lib/staleness/writers/hooks.js +7 -3
  97. package/dist/lib/staleness/writers/subagents.js +37 -6
  98. package/dist/lib/startup/command-registry.d.ts +1 -0
  99. package/dist/lib/startup/command-registry.js +2 -0
  100. package/dist/lib/subagents.d.ts +52 -0
  101. package/dist/lib/subagents.js +315 -12
  102. package/dist/lib/teams/worktree.d.ts +8 -0
  103. package/dist/lib/teams/worktree.js +8 -0
  104. package/dist/lib/types.d.ts +10 -2
  105. package/dist/lib/versions.js +38 -48
  106. package/package.json +4 -3
  107. 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. A tail that begins mid-line yields one
14
- * malformed leading line, which the per-line JSON try/catch in the content
15
- * parsers skips. Only Claude and Codex are supported (the prioritized harnesses
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[];
@@ -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 and return its last
16
- * `maxEvents` normalized events. A tail that begins mid-line yields one
17
- * malformed leading line, which the per-line JSON try/catch in the content
18
- * parsers skips. Only Claude and Codex are supported (the prioritized harnesses
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 readSessionTail(filePath, agent, maxBytes = DEFAULT_MAX_BYTES, maxEvents = DEFAULT_MAX_EVENTS) {
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
- if (!content.trim())
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
+ }
@@ -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 notice lines to
3
- * print ONCE for whatever is left (a real-binary shadow we won't move; a PATH entry
4
- * that was just added / needs a reload), or null when there's nothing new to say.
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';
@@ -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 notice lines to
16
- * print ONCE for whatever is left (a real-binary shadow we won't move; a PATH entry
17
- * that was just added / needs a reload), or null when there's nothing new to say.
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 healShimsInteractive() {
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 buildClaudeDetector() {
12
+ function buildFlatMdAgentsDetector(agent, agentsRoot) {
12
13
  return {
13
14
  kind: 'subagents',
14
- agent: 'claude',
15
+ agent,
15
16
  list({ versionHome }) {
16
- const agentsDir = path.join(versionHome, '.claude', 'agents');
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 auto-
34
- // discovers from ~/.grok/hooks/ so the file copy is sufficient.
35
- if (agent === 'claude' || agent === 'codex' || agent === 'gemini' || agent === 'antigravity' || agent === 'kimi' || agent === 'droid') {
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/`. Droid (Factory AI) flattens each into a custom
4
- * droid .md under `<versionHome>/.factory/droids/`. OpenClaw copies the full
5
- * subagent directory (with AGENT.md renamed to AGENTS.md) into
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],
@@ -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[];