@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.
Files changed (142) hide show
  1. package/CHANGELOG.md +71 -0
  2. package/README.md +12 -0
  3. package/dist/bin/agents +0 -0
  4. package/dist/commands/browser.js +215 -7
  5. package/dist/commands/cloud.d.ts +3 -0
  6. package/dist/commands/cloud.js +8 -1
  7. package/dist/commands/events.d.ts +1 -1
  8. package/dist/commands/events.js +2 -3
  9. package/dist/commands/exec.js +82 -3
  10. package/dist/commands/factory.js +8 -0
  11. package/dist/commands/feed.d.ts +38 -0
  12. package/dist/commands/feed.js +274 -0
  13. package/dist/commands/logs.d.ts +5 -1
  14. package/dist/commands/logs.js +248 -3
  15. package/dist/commands/mcp.js +7 -0
  16. package/dist/commands/memory.d.ts +9 -0
  17. package/dist/commands/memory.js +164 -0
  18. package/dist/commands/message.d.ts +11 -6
  19. package/dist/commands/message.js +140 -5
  20. package/dist/commands/routines.js +12 -0
  21. package/dist/commands/secrets-migrate.d.ts +2 -1
  22. package/dist/commands/secrets-migrate.js +88 -13
  23. package/dist/commands/secrets.d.ts +22 -0
  24. package/dist/commands/secrets.js +176 -42
  25. package/dist/commands/sessions.js +10 -1
  26. package/dist/commands/teams.js +4 -0
  27. package/dist/commands/worktree.js +4 -2
  28. package/dist/index.js +21 -22
  29. package/dist/lib/agents.js +249 -17
  30. package/dist/lib/answer-router.d.ts +75 -0
  31. package/dist/lib/answer-router.js +149 -0
  32. package/dist/lib/ask-classifier.d.ts +71 -0
  33. package/dist/lib/ask-classifier.js +197 -0
  34. package/dist/lib/browser/login-detection.d.ts +94 -0
  35. package/dist/lib/browser/login-detection.js +274 -0
  36. package/dist/lib/browser/profiles.d.ts +17 -8
  37. package/dist/lib/browser/profiles.js +27 -8
  38. package/dist/lib/browser/secret-ref.d.ts +10 -0
  39. package/dist/lib/browser/secret-ref.js +14 -0
  40. package/dist/lib/browser/service.js +14 -12
  41. package/dist/lib/cloud/antigravity.d.ts +0 -2
  42. package/dist/lib/cloud/antigravity.js +2 -17
  43. package/dist/lib/cloud/codex.js +3 -18
  44. package/dist/lib/cloud/rush.d.ts +15 -0
  45. package/dist/lib/cloud/rush.js +10 -16
  46. package/dist/lib/cloud/stream.js +2 -0
  47. package/dist/lib/cloud/types.d.ts +21 -0
  48. package/dist/lib/cloud/types.js +81 -0
  49. package/dist/lib/crabbox/cli.d.ts +1 -1
  50. package/dist/lib/crabbox/cli.js +12 -2
  51. package/dist/lib/crabbox/lease.d.ts +19 -0
  52. package/dist/lib/crabbox/lease.js +22 -11
  53. package/dist/lib/crabbox/progress.d.ts +62 -0
  54. package/dist/lib/crabbox/progress.js +129 -0
  55. package/dist/lib/crabbox/runtimes.d.ts +38 -1
  56. package/dist/lib/crabbox/runtimes.js +98 -5
  57. package/dist/lib/daemon.d.ts +12 -9
  58. package/dist/lib/daemon.js +32 -17
  59. package/dist/lib/events.d.ts +31 -5
  60. package/dist/lib/events.js +291 -101
  61. package/dist/lib/exec.js +20 -1
  62. package/dist/lib/feed-outcome.d.ts +101 -0
  63. package/dist/lib/feed-outcome.js +244 -0
  64. package/dist/lib/feed-policy.d.ts +30 -0
  65. package/dist/lib/feed-policy.js +133 -0
  66. package/dist/lib/feed.d.ts +180 -0
  67. package/dist/lib/feed.js +627 -0
  68. package/dist/lib/git.d.ts +17 -1
  69. package/dist/lib/git.js +20 -1
  70. package/dist/lib/hooks.js +529 -14
  71. package/dist/lib/hosts/passthrough.d.ts +3 -3
  72. package/dist/lib/hosts/passthrough.js +3 -3
  73. package/dist/lib/mailbox-gc.d.ts +22 -0
  74. package/dist/lib/mailbox-gc.js +161 -0
  75. package/dist/lib/mailbox.d.ts +26 -2
  76. package/dist/lib/mailbox.js +80 -5
  77. package/dist/lib/mcp.js +82 -0
  78. package/dist/lib/memory.d.ts +55 -0
  79. package/dist/lib/memory.js +274 -0
  80. package/dist/lib/notify.d.ts +16 -0
  81. package/dist/lib/notify.js +61 -0
  82. package/dist/lib/operator.d.ts +26 -0
  83. package/dist/lib/operator.js +107 -0
  84. package/dist/lib/plugins.d.ts +35 -0
  85. package/dist/lib/plugins.js +217 -0
  86. package/dist/lib/remote-agents-json.d.ts +14 -0
  87. package/dist/lib/remote-agents-json.js +94 -0
  88. package/dist/lib/resources/mcp.js +44 -0
  89. package/dist/lib/resources/memory.d.ts +15 -0
  90. package/dist/lib/resources/memory.js +46 -0
  91. package/dist/lib/resources/types.d.ts +2 -2
  92. package/dist/lib/rotate.js +2 -0
  93. package/dist/lib/runner.d.ts +43 -0
  94. package/dist/lib/runner.js +323 -74
  95. package/dist/lib/sandbox.js +6 -0
  96. package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
  97. package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
  98. package/dist/lib/secrets/agent.d.ts +21 -0
  99. package/dist/lib/secrets/agent.js +63 -1
  100. package/dist/lib/secrets/bundles.d.ts +33 -1
  101. package/dist/lib/secrets/bundles.js +76 -21
  102. package/dist/lib/secrets/icloud-import.d.ts +79 -0
  103. package/dist/lib/secrets/icloud-import.js +203 -0
  104. package/dist/lib/secrets/index.d.ts +142 -2
  105. package/dist/lib/secrets/index.js +695 -30
  106. package/dist/lib/secrets/remote.js +1 -1
  107. package/dist/lib/secrets/sync.js +1 -1
  108. package/dist/lib/session/active.d.ts +18 -0
  109. package/dist/lib/session/active.js +47 -17
  110. package/dist/lib/session/db.d.ts +9 -1
  111. package/dist/lib/session/db.js +18 -3
  112. package/dist/lib/session/discover.d.ts +13 -0
  113. package/dist/lib/session/discover.js +32 -2
  114. package/dist/lib/session/parse.d.ts +8 -0
  115. package/dist/lib/session/parse.js +42 -21
  116. package/dist/lib/session/remote-active.js +8 -89
  117. package/dist/lib/session/state.d.ts +11 -0
  118. package/dist/lib/session/state.js +50 -1
  119. package/dist/lib/session/tail.d.ts +23 -4
  120. package/dist/lib/session/tail.js +34 -16
  121. package/dist/lib/session/throughput.d.ts +30 -0
  122. package/dist/lib/session/throughput.js +86 -0
  123. package/dist/lib/shim-heal.d.ts +12 -3
  124. package/dist/lib/shim-heal.js +12 -6
  125. package/dist/lib/staleness/detectors/subagents.js +57 -3
  126. package/dist/lib/staleness/writers/hooks.js +7 -3
  127. package/dist/lib/staleness/writers/subagents.js +37 -6
  128. package/dist/lib/startup/command-registry.d.ts +2 -0
  129. package/dist/lib/startup/command-registry.js +4 -0
  130. package/dist/lib/state.d.ts +2 -0
  131. package/dist/lib/state.js +25 -8
  132. package/dist/lib/subagents.d.ts +52 -0
  133. package/dist/lib/subagents.js +315 -12
  134. package/dist/lib/teams/agents.js +6 -3
  135. package/dist/lib/teams/worktree.d.ts +8 -0
  136. package/dist/lib/teams/worktree.js +8 -0
  137. package/dist/lib/types.d.ts +20 -2
  138. package/dist/lib/versions.js +38 -48
  139. package/dist/lib/whats-new.d.ts +5 -3
  140. package/dist/lib/whats-new.js +25 -5
  141. package/package.json +4 -3
  142. 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
- if (looksLikeQuestion(last.content ?? '')) {
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. 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;
@@ -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
  };
@@ -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/). */