@link-assistant/hive-mind 1.77.1 → 1.78.1

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 (60) hide show
  1. package/CHANGELOG.md +67 -0
  2. package/package.json +1 -1
  3. package/src/agent.lib.mjs +2 -1
  4. package/src/claude.lib.mjs +2 -2
  5. package/src/claude.runtime-switch.lib.mjs +2 -1
  6. package/src/codex.lib.mjs +2 -1
  7. package/src/config.lib.mjs +2 -1
  8. package/src/contributing-guidelines.lib.mjs +2 -1
  9. package/src/gemini.lib.mjs +2 -1
  10. package/src/github-entity-validation.lib.mjs +2 -1
  11. package/src/github-error-reporter.lib.mjs +2 -1
  12. package/src/github.batch.lib.mjs +2 -1
  13. package/src/github.lib.mjs +2 -1
  14. package/src/handoff-skill.lib.mjs +2 -1
  15. package/src/hive.mjs +11 -10
  16. package/src/interactive-codex-events.lib.mjs +167 -0
  17. package/src/interactive-mode.lib.mjs +62 -152
  18. package/src/interactive-mode.shared.lib.mjs +1 -0
  19. package/src/interactive-system-events.lib.mjs +224 -0
  20. package/src/isolation-runner.lib.mjs +2 -1
  21. package/src/lenv-reader.lib.mjs +2 -1
  22. package/src/lib.mjs +2 -1
  23. package/src/lino.lib.mjs +2 -1
  24. package/src/local-ci-checks.lib.mjs +2 -1
  25. package/src/log-upload.lib.mjs +2 -1
  26. package/src/memory-check.mjs +2 -1
  27. package/src/models/index.mjs +2 -1
  28. package/src/npm-global-prefix.lib.mjs +160 -0
  29. package/src/opencode.lib.mjs +2 -1
  30. package/src/playwright-mcp.lib.mjs +2 -1
  31. package/src/protect-branch.mjs +2 -1
  32. package/src/queue-config.lib.mjs +2 -1
  33. package/src/qwen.lib.mjs +2 -1
  34. package/src/review.mjs +2 -1
  35. package/src/reviewers-hive.mjs +2 -1
  36. package/src/solve.auto-continue.lib.mjs +2 -1
  37. package/src/solve.auto-ensure.lib.mjs +2 -1
  38. package/src/solve.auto-merge-helpers.lib.mjs +2 -1
  39. package/src/solve.auto-merge.lib.mjs +2 -1
  40. package/src/solve.bootstrap.lib.mjs +2 -1
  41. package/src/solve.escalate.lib.mjs +2 -1
  42. package/src/solve.execution.lib.mjs +2 -1
  43. package/src/solve.fork-detection.lib.mjs +2 -1
  44. package/src/solve.fork-sync.lib.mjs +2 -1
  45. package/src/solve.keep-working.lib.mjs +2 -1
  46. package/src/solve.mjs +2 -2
  47. package/src/solve.repository.lib.mjs +2 -1
  48. package/src/solve.restart-shared.lib.mjs +2 -1
  49. package/src/solve.results.lib.mjs +2 -1
  50. package/src/solve.validation.lib.mjs +2 -1
  51. package/src/solve.watch.lib.mjs +2 -1
  52. package/src/telegram-bot.mjs +2 -1
  53. package/src/telegram-safe-reply.lib.mjs +112 -10
  54. package/src/telegram-solve-queue.helpers.lib.mjs +117 -14
  55. package/src/telegram-solve-queue.lib.mjs +38 -44
  56. package/src/token-sanitization.lib.mjs +2 -1
  57. package/src/tool-comments.lib.mjs +2 -1
  58. package/src/use-m-bootstrap.lib.mjs +23 -0
  59. package/src/useless-tools.lib.mjs +2 -1
  60. package/src/youtrack/youtrack.lib.mjs +2 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,72 @@
1
1
  # @link-assistant/hive-mind
2
2
 
3
+ ## 1.78.1
4
+
5
+ ### Patch Changes
6
+
7
+ - dc8bb99: Group interactive `system.thinking_tokens` events into one editable PR comment and handle observed system lifecycle events without unrecognized-event noise.
8
+ - d704dfc: fix(install): redirect npm global prefix when root-owned to avoid EACCES at startup (#1897)
9
+
10
+ `use-m` loads runtime dependencies (command-stream, getenv, yargs, …) by shelling
11
+ out to `npm install -g <alias>@npm:<pkg>@latest`, which installs into the global
12
+ prefix reported by `npm root -g`. When the CLI was launched under a system-wide
13
+ Node.js whose global `node_modules` is owned by root (e.g.
14
+ `/opt/node-v24.16.0-linux-x64/lib/node_modules`), that install failed with
15
+ `npm error code EACCES … rename … command-stream-v-latest` and the whole process
16
+ crashed at the very first `use()` call (`Error: Failed to install
17
+ command-stream@latest globally.`). This commonly happens when hive-mind was
18
+ installed with `bun add -g` (user-owned `~/.bun/...`) but invoked under a system
19
+ Node whose global prefix needs root.
20
+
21
+ The new `src/npm-global-prefix.lib.mjs` preflight mirrors npm's own documented
22
+ EACCES remedy: before any real `use-m` bootstrap runs, it detects a non-writable
23
+ npm global prefix and redirects `npm_config_prefix` (honoured by both
24
+ `npm install -g` and `npm root -g`) to a user-writable `~/.npm-global`,
25
+ prepending its `bin` to `PATH`. The common case where the prefix is already
26
+ writable stays a no-op with no extra `npm` spawn.
27
+
28
+ Hive Mind now routes direct repository `use-m` bootstraps through
29
+ `src/use-m-bootstrap.lib.mjs`, including CLI entry points, shared source modules,
30
+ scripts, and executable tests. The workaround skips Windows' different global
31
+ layout, skips Bun/Deno runtimes, and respects explicitly preset
32
+ `npm_config_prefix` or `NPM_CONFIG_PREFIX` values.
33
+
34
+ ## 1.78.0
35
+
36
+ ### Minor Changes
37
+
38
+ - 9506f03: fix(telegram): de-duplicate `/queue` display and split long messages without breaking markdown (#1891)
39
+
40
+ The `/queue` (alias `/solve_queue`) detailed display repeated the same words on every
41
+ line — every executing row said `(processing, …)`, every waiting row said
42
+ `(waiting, …)`, and the (almost always identical) per-item waiting reason was printed
43
+ once per item. Empty queues were also still printed. This wasted vertical space and
44
+ pushed real data off screen.
45
+
46
+ Display changes (`formatDetailedStatus` + queue helpers):
47
+ - Executing rows now render compactly as `• owner/repo#number (▶️ <dur>)` and pending
48
+ rows as `• owner/repo#number (⏳ <dur>)` — the status word is replaced by the emoji
49
+ marker inside the duration parenthesis.
50
+ - Processing, pending, completed, and failed entries are split into distinct
51
+ compact lists per tool, with counts only on those list labels instead of a
52
+ duplicated `(pending: n, processing: n)` tool-header summary.
53
+ - The shared waiting reason is shown **once per tool** (only when all pending items
54
+ agree on it) instead of once per item.
55
+ - Empty queues are skipped entirely.
56
+ - All queued items are listed (no per-queue truncation on the active lists).
57
+
58
+ Message-splitting changes (`splitTelegramMessageText` in `telegram-safe-reply.lib.mjs`,
59
+ the single universal splitter every Telegram send path funnels through):
60
+ - Splitting now happens only on line boundaries, so inline Markdown entities
61
+ (bold/italic/links) are never cut in half.
62
+ - Fenced code blocks stay balanced per chunk: a split inside a code block closes the
63
+ fence at the end of one chunk and reopens it — repeating the language — at the start
64
+ of the next. The original fence marker (```vs`~~~`) and indentation are preserved.
65
+ - Pathologically long single lines are hard-split as a fallback.
66
+
67
+ Both behaviours are covered by extensive new tests
68
+ (`tests/test-telegram-message-split-1891.mjs`, `tests/test-queue-compact-display-1891.mjs`).
69
+
3
70
  ## 1.77.1
4
71
 
5
72
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@link-assistant/hive-mind",
3
- "version": "1.77.1",
3
+ "version": "1.78.1",
4
4
  "description": "AI-powered issue solver and hive mind for collaborative problem solving",
5
5
  "main": "src/hive.mjs",
6
6
  "type": "module",
package/src/agent.lib.mjs CHANGED
@@ -1,10 +1,11 @@
1
1
  #!/usr/bin/env node
2
+ import { ensureUseM } from './use-m-bootstrap.lib.mjs';
2
3
  // Agent-related utility functions
3
4
 
4
5
  // Check if use is already defined (when imported from solve.mjs)
5
6
  // If not, fetch it (when running standalone)
6
7
  if (typeof globalThis.use === 'undefined') {
7
- globalThis.use = (await eval(await (await fetch('https://unpkg.com/use-m/use.js')).text())).use;
8
+ await ensureUseM();
8
9
  }
9
10
 
10
11
  const { $ } = await use('command-stream');
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
- // Claude CLI-related utility functions. Fetch use-m if not available.
2
+ import { ensureUseM } from './use-m-bootstrap.lib.mjs';
3
3
  if (typeof globalThis.use === 'undefined') {
4
- globalThis.use = (await eval(await (await fetch('https://unpkg.com/use-m/use.js')).text())).use;
4
+ await ensureUseM();
5
5
  }
6
6
  const { $ } = await use('command-stream');
7
7
  const fs = (await use('fs')).promises;
@@ -1,11 +1,12 @@
1
1
  #!/usr/bin/env node
2
+ import { ensureUseM } from './use-m-bootstrap.lib.mjs';
2
3
  // Claude runtime switching module
3
4
  // Extracted from claude.lib.mjs to maintain file line limits
4
5
  // See: docs/case-studies/issue-1141
5
6
 
6
7
  // If not, fetch it (when running standalone)
7
8
  if (typeof globalThis.use === 'undefined') {
8
- globalThis.use = (await eval(await (await fetch('https://unpkg.com/use-m/use.js')).text())).use;
9
+ await ensureUseM();
9
10
  }
10
11
  const { $ } = await use('command-stream');
11
12
  const fs = (await use('fs')).promises;
package/src/codex.lib.mjs CHANGED
@@ -1,10 +1,11 @@
1
1
  #!/usr/bin/env node
2
+ import { ensureUseM } from './use-m-bootstrap.lib.mjs';
2
3
  // Codex CLI-related utility functions
3
4
 
4
5
  // Check if use is already defined (when imported from solve.mjs)
5
6
  // If not, fetch it (when running standalone)
6
7
  if (typeof globalThis.use === 'undefined') {
7
- globalThis.use = (await eval(await (await fetch('https://unpkg.com/use-m/use.js')).text())).use;
8
+ await ensureUseM();
8
9
  }
9
10
 
10
11
  const { $ } = await use('command-stream');
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
+ import { ensureUseM } from './use-m-bootstrap.lib.mjs';
2
3
 
3
4
  /**
4
5
  * Central configuration module for all configurable values
@@ -8,7 +9,7 @@
8
9
  // Use use-m to dynamically import modules
9
10
  if (typeof globalThis.use === 'undefined') {
10
11
  try {
11
- globalThis.use = (await eval(await (await fetch('https://unpkg.com/use-m/use.js')).text())).use;
12
+ await ensureUseM();
12
13
  } catch (error) {
13
14
  console.error('❌ Fatal error: Failed to load dependencies for configuration');
14
15
  console.error(` ${error.message}`);
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
+ import { ensureUseM } from './use-m-bootstrap.lib.mjs';
2
3
 
3
4
  /**
4
5
  * Contributing Guidelines Detection and Fetching
@@ -6,7 +7,7 @@
6
7
  */
7
8
 
8
9
  if (typeof globalThis.use === 'undefined') {
9
- globalThis.use = (await eval(await (await fetch('https://unpkg.com/use-m/use.js')).text())).use;
10
+ await ensureUseM();
10
11
  }
11
12
 
12
13
  const { $: __rawDollar$ } = await use('command-stream');
@@ -1,10 +1,11 @@
1
1
  #!/usr/bin/env node
2
+ import { ensureUseM } from './use-m-bootstrap.lib.mjs';
2
3
  // Google Gemini CLI-related utility functions
3
4
 
4
5
  // Check if use is already defined (when imported from solve.mjs)
5
6
  // If not, fetch it (when running standalone)
6
7
  if (typeof globalThis.use === 'undefined') {
7
- globalThis.use = (await eval(await (await fetch('https://unpkg.com/use-m/use.js')).text())).use;
8
+ await ensureUseM();
8
9
  }
9
10
 
10
11
  const { $ } = await use('command-stream');
@@ -1,9 +1,10 @@
1
+ import { ensureUseM } from './use-m-bootstrap.lib.mjs';
1
2
  /**
2
3
  * GitHub entity existence validation for /solve command.
3
4
  * Extracted from github.lib.mjs to keep files under 1500 line limit.
4
5
  * @see https://github.com/link-assistant/hive-mind/issues/1552
5
6
  */
6
- if (typeof globalThis.use === 'undefined') globalThis.use = (await eval(await (await fetch('https://unpkg.com/use-m/use.js')).text())).use;
7
+ if (typeof globalThis.use === 'undefined') await ensureUseM();
7
8
  const { $ } = await use('command-stream');
8
9
  import { ghCmdRetry } from './lib.mjs';
9
10
  import { ghPrView, ghIssueView } from './github.lib.mjs';
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
+ import { ensureUseM } from './use-m-bootstrap.lib.mjs';
2
3
 
3
4
  /**
4
5
  * GitHub error reporter - handles error reporting via GitHub issues and comments
@@ -9,7 +10,7 @@ import { log, cleanErrorMessage, getAbsoluteLogPath } from './lib.mjs';
9
10
  import { reportError, isSentryEnabled } from './sentry.lib.mjs';
10
11
 
11
12
  if (typeof globalThis.use === 'undefined') {
12
- globalThis.use = (await eval(await (await fetch('https://unpkg.com/use-m/use.js')).text())).use;
13
+ await ensureUseM();
13
14
  }
14
15
 
15
16
  const fs = (await use('fs')).promises;
@@ -1,10 +1,11 @@
1
1
  #!/usr/bin/env node
2
+ import { ensureUseM } from './use-m-bootstrap.lib.mjs';
2
3
  // GitHub batch operations using GraphQL
3
4
 
4
5
  // Check if use is already defined (when imported from solve.mjs)
5
6
  // If not, fetch it (when running standalone)
6
7
  if (typeof globalThis.use === 'undefined') {
7
- globalThis.use = (await eval(await (await fetch('https://unpkg.com/use-m/use.js')).text())).use;
8
+ await ensureUseM();
8
9
  }
9
10
 
10
11
  // Import dependencies
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
+ import { ensureUseM } from './use-m-bootstrap.lib.mjs';
2
3
  // GitHub-related utility functions. Check if use is already defined (when imported from solve.mjs), if not, fetch it (when running standalone)
3
- if (typeof globalThis.use === 'undefined') globalThis.use = (await eval(await (await fetch('https://unpkg.com/use-m/use.js')).text())).use;
4
+ if (typeof globalThis.use === 'undefined') await ensureUseM();
4
5
  const { $ } = await use('command-stream'); // Use command-stream for consistent $ behavior
5
6
  import { log, maskToken, cleanErrorMessage, isENOSPC, ghCmdRetry } from './lib.mjs';
6
7
  import { reportError } from './sentry.lib.mjs';
@@ -1,3 +1,4 @@
1
+ import { ensureUseM } from './use-m-bootstrap.lib.mjs';
1
2
  /**
2
3
  * HANDOFF.md Agent Skill deployment (issue #1877)
3
4
  *
@@ -29,7 +30,7 @@
29
30
 
30
31
  // Fetch use-m if not available (matches the rest of src/*.lib.mjs).
31
32
  if (typeof globalThis.use === 'undefined') {
32
- globalThis.use = (await eval(await (await fetch('https://unpkg.com/use-m/use.js')).text())).use;
33
+ await ensureUseM();
33
34
  }
34
35
  const fs = (await use('fs')).promises;
35
36
  const path = (await use('path')).default;
package/src/hive.mjs CHANGED
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  // Import Sentry instrumentation first (must be before other imports)
3
3
  import './instrument.mjs';
4
+ import { ensureUseM } from './use-m-bootstrap.lib.mjs';
4
5
  import { wrapDollarWithGhRetry as _wrapDollarWithGhRetry, execGhWithRetry } from './github-rate-limit.lib.mjs'; // rate-limit marker (#1726): gh API calls flow through $ wrapped by caller. execGhWithRetry adds transient-network retry (#1756).
5
6
  const earlyArgs = process.argv.slice(2);
6
7
  if (earlyArgs.includes('--version')) {
@@ -42,16 +43,17 @@ if (isRunningDirectly) {
42
43
  console.log(' Initializing...');
43
44
  try {
44
45
  console.log(' Loading dependencies (this may take a moment)...');
45
- // Use use-m to dynamically import modules for cross-runtime compatibility
46
- if (typeof use === 'undefined') {
46
+ if (typeof globalThis.use === 'undefined') {
47
47
  try {
48
- // Wrap fetch in timeout to prevent hanging
49
- const useMCode = await withTimeout(
50
- fetch('https://unpkg.com/use-m/use.js').then(r => r.text()),
51
- 10000,
52
- 'fetching use-m library'
53
- );
54
- globalThis.use = (await eval(useMCode)).use;
48
+ await ensureUseM({
49
+ fetchUseMCode: () =>
50
+ withTimeout(
51
+ fetch('https://unpkg.com/use-m/use.js').then(r => r.text()),
52
+ 10000,
53
+ 'fetching use-m library'
54
+ ),
55
+ log: message => console.log(message),
56
+ });
55
57
  } catch (error) {
56
58
  console.error('❌ Fatal error: Failed to load dependencies');
57
59
  console.error(` ${error.message}`);
@@ -60,7 +62,6 @@ if (isRunningDirectly) {
60
62
  process.exit(1);
61
63
  }
62
64
  }
63
- // Use command-stream for consistent $ behavior across runtimes
64
65
  const { $ } = await withTimeout(
65
66
  use('command-stream'),
66
67
  30000, // 30 second timeout
@@ -0,0 +1,167 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { createCollapsible, createRawJsonSection, createRedactedRawJsonSection, escapeMarkdown, redactImageData, safeJsonStringify, truncateMiddle } from './interactive-mode.shared.lib.mjs';
4
+ import { INTERACTIVE_SESSION_STARTED_MARKER } from './tool-comments.lib.mjs';
5
+
6
+ export const createCodexEventHandlers = ({ state, postComment, handleAssistantText, imageRenderer }) => {
7
+ const handleCodexThreadStarted = async data => {
8
+ if (state.sessionId) return;
9
+
10
+ state.sessionId = data.thread_id || data.session_id || null;
11
+ state.startTime = Date.now();
12
+
13
+ const comment = `## 🚀 ${INTERACTIVE_SESSION_STARTED_MARKER}
14
+
15
+ | Property | Value |
16
+ |----------|-------|
17
+ | **Session ID** | \`${state.sessionId || 'unknown'}\` |
18
+ | **Model** | \`${data.model || 'unknown'}\` |
19
+ | **Tool** | \`codex\` |
20
+
21
+ ---
22
+
23
+ ${createRawJsonSection(data)}`;
24
+
25
+ await postComment(comment);
26
+ };
27
+
28
+ const handleCodexAgentMessage = async data => {
29
+ const text = data.item?.text;
30
+ if (typeof text !== 'string' || !text.trim()) return;
31
+ await handleAssistantText(data, text);
32
+ };
33
+
34
+ const handleCodexTodoList = async data => {
35
+ const items = Array.isArray(data.item?.items) ? data.item.items : [];
36
+ const todosPreview = items.length > 0 ? items.map(todo => `- [${todo?.completed ? 'x' : ' '}] ${todo?.text || ''}`).join('\n') : '_No tasks_';
37
+ const completedCount = items.filter(todo => todo?.completed).length;
38
+
39
+ const comment = `## 📋 Codex todo list
40
+
41
+ ${createCollapsible(`📋 Todos (${completedCount}/${items.length} items)`, todosPreview, true)}
42
+
43
+ ---
44
+
45
+ ${createRawJsonSection(data)}`;
46
+
47
+ await postComment(comment);
48
+ };
49
+
50
+ const handleCodexCommandExecution = async data => {
51
+ const item = data.item || {};
52
+ const command = item.command || '';
53
+ const output = item.aggregated_output || '';
54
+ const status = item.status || (data.type === 'item.completed' ? 'completed' : data.type === 'item.updated' ? 'updated' : 'started');
55
+ const body = `## 💻 Codex command execution
56
+
57
+ **Status:** \`${status}\`
58
+ ${command ? '\n' + createCollapsible('📋 Executed command', '```bash\n' + escapeMarkdown(command) + '\n```', true) : ''}
59
+ ${output ? '\n\n' + createCollapsible('📤 Output', '```\n' + escapeMarkdown(truncateMiddle(output, { maxLines: 60, keepStart: 25, keepEnd: 25 })) + '\n```', true) : ''}
60
+
61
+ ---
62
+
63
+ ${createRawJsonSection(data)}`;
64
+ await postComment(body);
65
+ };
66
+
67
+ const handleCodexMcpToolCall = async data => {
68
+ const item = data.item || {};
69
+ const summary = [`**Server:** \`${item.server || 'unknown'}\``, `**Tool:** \`${item.tool || 'unknown'}\``, `**Status:** \`${item.status || 'unknown'}\``].join('\n');
70
+ const details = item.arguments != null ? createCollapsible('📥 Arguments', '```json\n' + safeJsonStringify(item.arguments, 2) + '\n```', true) : '';
71
+ // Issue #1843: render MCP-result images inline; base64 is redacted from JSON below.
72
+ const imagesSection = await imageRenderer.section([item.result], `${item.tool || 'mcp'} image`);
73
+ const imagesBlock = imagesSection ? '\n\n' + imagesSection : '';
74
+ const resultSection = item.result != null ? '\n\n' + createCollapsible('📤 Result', '```json\n' + safeJsonStringify(redactImageData(item.result), 2) + '\n```', false) : '';
75
+ const errorSection = item.error != null ? '\n\n' + createCollapsible('❌ Error', '```json\n' + safeJsonStringify(item.error, 2) + '\n```', true) : '';
76
+
77
+ await postComment(`## 🔌 Codex MCP tool call
78
+
79
+ ${summary}
80
+ ${details}${resultSection}${imagesBlock}${errorSection}
81
+
82
+ ---
83
+
84
+ ${createRedactedRawJsonSection(data)}`);
85
+ };
86
+
87
+ const handleCodexWebSearch = async data => {
88
+ const item = data.item || {};
89
+ await postComment(`## 🌐 Codex web search
90
+
91
+ **Query:** ${escapeMarkdown(item.query || 'unknown')}
92
+ ${item.action ? `\n**Action:** \`${item.action}\`` : ''}
93
+
94
+ ---
95
+
96
+ ${createRawJsonSection(data)}`);
97
+ };
98
+
99
+ const handleCodexFileChange = async data => {
100
+ const item = data.item || {};
101
+ const changes = Array.isArray(item.changes) ? item.changes.map(change => `- \`${change?.kind || 'change'}\` ${change?.path || ''}`).join('\n') : '_No changes listed_';
102
+ await postComment(`## 📝 Codex file changes
103
+
104
+ **Status:** \`${item.status || 'unknown'}\`
105
+ ${createCollapsible('📄 Files', changes, true)}
106
+
107
+ ---
108
+
109
+ ${createRawJsonSection(data)}`);
110
+ };
111
+
112
+ const handleCodexCollabToolCall = async data => {
113
+ const item = data.item || {};
114
+ const prompt = item.prompt || item.description || `${item.tool || 'collab_tool_call'} via codex`;
115
+ await postComment(`## 🤝 Codex collab/sub-agent call
116
+
117
+ **Tool:** \`${item.tool || 'unknown'}\`
118
+ **Status:** \`${item.status || 'unknown'}\`
119
+ ${createCollapsible('📝 Prompt', escapeMarkdown(truncateMiddle(prompt, { maxLines: 30, keepStart: 12, keepEnd: 12 })), true)}
120
+
121
+ ---
122
+
123
+ ${createRawJsonSection(data)}`);
124
+ };
125
+
126
+ const handleCodexTurnCompleted = async data => {
127
+ const usage = data.usage || {};
128
+ let usageSection = '| Type | Count |\n|------|-------|\n';
129
+ usageSection += `| Input | ${(usage.input_tokens || 0).toLocaleString()} |\n`;
130
+ usageSection += `| Cache Read | ${(usage.cached_input_tokens || 0).toLocaleString()} |\n`;
131
+ usageSection += `| Output | ${(usage.output_tokens || 0).toLocaleString()} |\n`;
132
+
133
+ await postComment(`## ✅ Codex turn completed
134
+
135
+ ### 📊 Token Usage
136
+
137
+ ${usageSection}
138
+
139
+ ---
140
+
141
+ ${createRawJsonSection(data)}`);
142
+ };
143
+
144
+ const handleCodexError = async data => {
145
+ const message = data.message || data.error?.message || 'Unknown Codex error';
146
+ await postComment(`## ❌ Codex error
147
+
148
+ ${createCollapsible('View error', escapeMarkdown(message), true)}
149
+
150
+ ---
151
+
152
+ ${createRawJsonSection(data)}`);
153
+ };
154
+
155
+ return {
156
+ handleCodexThreadStarted,
157
+ handleCodexAgentMessage,
158
+ handleCodexTodoList,
159
+ handleCodexCommandExecution,
160
+ handleCodexMcpToolCall,
161
+ handleCodexWebSearch,
162
+ handleCodexFileChange,
163
+ handleCodexCollabToolCall,
164
+ handleCodexTurnCompleted,
165
+ handleCodexError,
166
+ };
167
+ };