codehere 0.8.0 → 1.0.2

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 (119) hide show
  1. package/README.md +37 -184
  2. package/dist/application/use-cases/check-claimed-tests.js +81 -0
  3. package/dist/application/use-cases/detect-test-weakening.js +237 -0
  4. package/dist/application/use-cases/rerun-tests.js +329 -0
  5. package/dist/index.js +74 -1004
  6. package/dist/infrastructure/orchestration/artifacts-extractor.js +2 -265
  7. package/dist/presentation/cli/commands/check-command.js +251 -0
  8. package/dist/presentation/cli/commands/help-command.js +22 -286
  9. package/dist/ui.js +19 -438
  10. package/dist/utils/version.js +0 -12
  11. package/package.json +18 -27
  12. package/dist/application/use-cases/domain-trust.js +0 -110
  13. package/dist/application/use-cases/generate-final-report.js +0 -134
  14. package/dist/application/use-cases/generate-narration.js +0 -128
  15. package/dist/application/use-cases/generate-thread-title.js +0 -178
  16. package/dist/application/use-cases/narrator-prompt-template.js +0 -158
  17. package/dist/application/use-cases/thread-export.js +0 -155
  18. package/dist/application/use-cases/verify-claim.js +0 -274
  19. package/dist/application/use-cases/write-pass.js +0 -277
  20. package/dist/domain/entities/task.js +0 -61
  21. package/dist/domain/services/codebase-detector.js +0 -91
  22. package/dist/error-handler.js +0 -251
  23. package/dist/infrastructure/completion/bash-completion.js +0 -25
  24. package/dist/infrastructure/completion/fish-completion.js +0 -22
  25. package/dist/infrastructure/completion/zsh-completion.js +0 -30
  26. package/dist/infrastructure/config/config-manager.js +0 -173
  27. package/dist/infrastructure/config/product-context.js +0 -67
  28. package/dist/infrastructure/context/adapters/drawio-source.js +0 -157
  29. package/dist/infrastructure/context/adapters/figma-source.js +0 -163
  30. package/dist/infrastructure/context/adapters/github-issue-source.js +0 -159
  31. package/dist/infrastructure/context/adapters/linear-source.js +0 -190
  32. package/dist/infrastructure/context/context-resolver.js +0 -144
  33. package/dist/infrastructure/context/context-source.js +0 -114
  34. package/dist/infrastructure/context/oauth-flow.js +0 -207
  35. package/dist/infrastructure/context/oauth-store.js +0 -119
  36. package/dist/infrastructure/context/registry-bootstrap.js +0 -99
  37. package/dist/infrastructure/context/write-pass-resolver.js +0 -106
  38. package/dist/infrastructure/cost/budget-config.js +0 -113
  39. package/dist/infrastructure/cost/cost-router.js +0 -104
  40. package/dist/infrastructure/cost/spend-aggregator.js +0 -147
  41. package/dist/infrastructure/hooks/claude-sdk-hook.js +0 -888
  42. package/dist/infrastructure/hooks/hook-mode.js +0 -71
  43. package/dist/infrastructure/hooks/mirror-tool-call-to-audit.js +0 -43
  44. package/dist/infrastructure/hooks/pre-prompt-scan.js +0 -122
  45. package/dist/infrastructure/hooks/session-analyzer.js +0 -177
  46. package/dist/infrastructure/observability/execution-tracer.js +0 -227
  47. package/dist/infrastructure/orchestration/agent-adapter.js +0 -13
  48. package/dist/infrastructure/orchestration/agent-installer.js +0 -101
  49. package/dist/infrastructure/orchestration/agents-config-seed.js +0 -56
  50. package/dist/infrastructure/orchestration/aider-adapter.js +0 -447
  51. package/dist/infrastructure/orchestration/cassette-recorder.js +0 -120
  52. package/dist/infrastructure/orchestration/claude-code-adapter.js +0 -478
  53. package/dist/infrastructure/orchestration/codex-adapter.js +0 -538
  54. package/dist/infrastructure/orchestration/conductor-adapter.js +0 -377
  55. package/dist/infrastructure/orchestration/context-handoff.js +0 -144
  56. package/dist/infrastructure/orchestration/control/codex-control-surface.js +0 -99
  57. package/dist/infrastructure/orchestration/control/control-surface.js +0 -15
  58. package/dist/infrastructure/orchestration/failover-classifier.js +0 -151
  59. package/dist/infrastructure/orchestration/failover-config.js +0 -77
  60. package/dist/infrastructure/orchestration/failover-policy.js +0 -106
  61. package/dist/infrastructure/orchestration/gemini-adapter.js +0 -583
  62. package/dist/infrastructure/orchestration/generic-cli-adapter.js +0 -558
  63. package/dist/infrastructure/orchestration/graphify-context.js +0 -254
  64. package/dist/infrastructure/orchestration/memory-injector.js +0 -380
  65. package/dist/infrastructure/orchestration/mocks/mock-agent-repl.js +0 -233
  66. package/dist/infrastructure/orchestration/mocks/mock-codex-app-server.js +0 -73
  67. package/dist/infrastructure/orchestration/mocks/mock-synthesis.js +0 -83
  68. package/dist/infrastructure/orchestration/mocks/resolve-agent-spawn.js +0 -37
  69. package/dist/infrastructure/orchestration/opencode-adapter.js +0 -510
  70. package/dist/infrastructure/orchestration/progress-aggregator.js +0 -689
  71. package/dist/infrastructure/orchestration/readiness.js +0 -103
  72. package/dist/infrastructure/orchestration/session-manager.js +0 -1091
  73. package/dist/infrastructure/orchestration/session-persistence.js +0 -258
  74. package/dist/infrastructure/orchestration/thread-key-store.js +0 -127
  75. package/dist/infrastructure/orchestration/write-pass-store.js +0 -46
  76. package/dist/infrastructure/protocols/cloud-sync.js +0 -207
  77. package/dist/infrastructure/protocols/mcp-helpers.js +0 -25
  78. package/dist/infrastructure/protocols/mcp-server-impl.js +0 -2853
  79. package/dist/infrastructure/protocols/probe-port.js +0 -36
  80. package/dist/infrastructure/protocols/thread-phase.js +0 -105
  81. package/dist/infrastructure/protocols/ui-page-v2.js +0 -4418
  82. package/dist/infrastructure/protocols/ui-page.js +0 -2377
  83. package/dist/infrastructure/protocols/verify-page.js +0 -983
  84. package/dist/infrastructure/security/file-collection.js +0 -118
  85. package/dist/infrastructure/security/input-validator.js +0 -152
  86. package/dist/infrastructure/security/permissions-config.js +0 -234
  87. package/dist/infrastructure/security/trust-scorer.js +0 -657
  88. package/dist/infrastructure/storage/cross-tool-agents.js +0 -268
  89. package/dist/infrastructure/storage/embeddings-path.js +0 -37
  90. package/dist/infrastructure/storage/project-thread-migration.js +0 -237
  91. package/dist/infrastructure/storage/project-thread-mirror.js +0 -524
  92. package/dist/infrastructure/storage/project-thread-reader.js +0 -172
  93. package/dist/infrastructure/storage/project-thread-title-backfill.js +0 -214
  94. package/dist/infrastructure/storage/task-repository.js +0 -206
  95. package/dist/monitoring.js +0 -212
  96. package/dist/presentation/cli/commands/audit-command.js +0 -126
  97. package/dist/presentation/cli/commands/budget-command.js +0 -79
  98. package/dist/presentation/cli/commands/config-command.js +0 -125
  99. package/dist/presentation/cli/commands/context-source-command.js +0 -198
  100. package/dist/presentation/cli/commands/hook-command.js +0 -265
  101. package/dist/presentation/cli/commands/init-command.js +0 -578
  102. package/dist/presentation/cli/commands/memory-command.js +0 -1031
  103. package/dist/presentation/cli/commands/narrator-command.js +0 -101
  104. package/dist/presentation/cli/commands/permission-command.js +0 -116
  105. package/dist/presentation/cli/commands/protocols-builder.js +0 -254
  106. package/dist/presentation/cli/commands/protocols-command.js +0 -84
  107. package/dist/presentation/cli/commands/repo-map-builder.js +0 -247
  108. package/dist/presentation/cli/commands/report-command.js +0 -387
  109. package/dist/presentation/cli/commands/scan-tool-command.js +0 -94
  110. package/dist/presentation/cli/commands/setup-command.js +0 -307
  111. package/dist/presentation/cli/commands/status-command.js +0 -661
  112. package/dist/presentation/cli/commands/thread-command.js +0 -288
  113. package/dist/presentation/cli/commands/trust-command.js +0 -306
  114. package/dist/presentation/cli/commands/verify-command.js +0 -233
  115. package/dist/presentation/cli/error-display.js +0 -336
  116. package/dist/presentation/cli/repl.js +0 -797
  117. package/dist/presentation/cli/task-deliverable.js +0 -127
  118. package/dist/testing/cassette.js +0 -125
  119. package/dist/testing/replay-adapter.js +0 -166
@@ -1,538 +0,0 @@
1
- /**
2
- * Codex adapter — F2 second concrete AgentAdapter (v0.4 → shipping in v0.3.0-alpha.4).
3
- *
4
- * Wraps `codex exec --json "<prompt>"` as a subprocess. Codex CLI's --json
5
- * flag emits JSONL on stdout; we tokenize, parse, forward as TaskEvents.
6
- *
7
- * Event shapes are not fully documented — renderer handles unknowns by falling
8
- * through (same pattern as ClaudeCodeAdapter: skip tokens we can't shape, fire
9
- * raw stdout for anything else). First dogfood session will show what's real.
10
- *
11
- * Same structural pattern as ClaudeCodeAdapter: event buffer, late-joiner replay,
12
- * SIGTERM→SIGKILL escalation, known-error fingerprints. Intentionally duplicates
13
- * some structure — premature shared-base-class abstraction would couple adapters
14
- * that may diverge substantially (each agent CLI has its own quirks).
15
- */
16
- import { spawn, execFileSync } from 'child_process';
17
- import { EventEmitter } from 'events';
18
- import { randomUUID } from 'crypto';
19
- import { startRecording } from './cassette-recorder.js';
20
- import { ProgressAggregator } from './progress-aggregator.js';
21
- import { snapshotCwd, extractArtifacts } from './artifacts-extractor.js';
22
- import { applyMemoryPrefix } from './memory-injector.js';
23
- import { mirrorToolCallToAudit } from '../hooks/mirror-tool-call-to-audit.js';
24
- import { resolveAgentSpawn } from './mocks/resolve-agent-spawn.js';
25
- const HEADLESS_FLAGS = [
26
- 'exec',
27
- '--json',
28
- // codehere is the external safety layer — every tool call goes through
29
- // our PreToolUse hook (F0) which is where destructive-command scanning
30
- // happens. codex's own gates in -p mode are redundant AND block tasks
31
- // from actually running, so we bypass both:
32
- //
33
- // --skip-git-repo-check: run outside a git repo (like /tmp, home, etc)
34
- // --dangerously-bypass-approvals-and-sandbox: skip codex's read-only
35
- // sandbox (blocks writes) and its interactive approval prompts
36
- // (impossible to answer in -p mode anyway).
37
- //
38
- // codex's own help text: "EXTREMELY DANGEROUS. Intended solely for
39
- // running in environments that are externally sandboxed." — that's us.
40
- '--skip-git-repo-check',
41
- '--dangerously-bypass-approvals-and-sandbox',
42
- ];
43
- const ERROR_PATTERNS = [
44
- {
45
- code: 'auth_invalid',
46
- regex: /not\s+logged\s+in|authentication\s+failed|401 unauth/i,
47
- message: 'Codex auth missing or rejected.',
48
- recovery: 'Run `codex login` in a terminal, then retry the task.',
49
- },
50
- {
51
- code: 'rate_limit',
52
- regex: /rate.?limit(ed)?|429|too many requests/i,
53
- message: 'Codex rate-limited the request.',
54
- recovery: 'Wait a minute and retry, or check your OpenAI usage dashboard.',
55
- },
56
- {
57
- code: 'quota_exceeded',
58
- regex: /quota|insufficient_quota|you exceeded your current quota/i,
59
- message: 'OpenAI quota exhausted.',
60
- recovery: 'Top up at platform.openai.com or switch to a different agent.',
61
- },
62
- ];
63
- function scanForKnownError(text) {
64
- for (const p of ERROR_PATTERNS) {
65
- if (p.regex.test(text))
66
- return { code: p.code, message: p.message, recovery: p.recovery };
67
- }
68
- return null;
69
- }
70
- export function spawnCodex(prompt, options = {}) {
71
- const sessionId = randomUUID();
72
- const startedAt = Date.now();
73
- const emitter = new EventEmitter();
74
- const buffer = [];
75
- let statusValue = 'running';
76
- let killRequested = false;
77
- // PM UX Layer 2+3 state — same shape as claude-code-adapter. Kept in-file (not
78
- // extracted to a base class) because the two CLIs diverge in more than they share.
79
- const cwd = options.cwd ?? process.cwd();
80
- // F3 Task 8 — memory injection (same shape as every other adapter).
81
- const injected = applyMemoryPrefix(prompt, cwd, options);
82
- const finalPrompt = injected.prompt;
83
- const snapshot = snapshotCwd(cwd);
84
- const aggregator = new ProgressAggregator('codex');
85
- let stepsSnapshot = [];
86
- let artifactsSnapshot;
87
- let textAccum = '';
88
- // Codex emits one turn.completed per turn; sum across turns for session total.
89
- let usageSnapshot;
90
- let errorEmitted = false;
91
- const emit = (event) => {
92
- buffer.push(event);
93
- emitter.emit('event', event);
94
- // Layer 2: aggregate + emit synthetic snapshot on change.
95
- if (aggregator.ingest(event)) {
96
- stepsSnapshot = aggregator.steps.map((s) => ({ ...s }));
97
- const snapEvt = { type: 'codehere:steps', timestamp: Date.now(), data: { steps: stepsSnapshot } };
98
- buffer.push(snapEvt);
99
- emitter.emit('event', snapEvt);
100
- }
101
- // Collect text for test-summary parsing (Layer 3). Codex surfaces command
102
- // output via item.completed.aggregated_output and agent messages via item.text.
103
- //
104
- // v0.7 CRIT-1 fix — extended item subtypes parsed:
105
- // - command_execution (Bash) — existing, mirrored to audit
106
- // - agent_message (assistant text) — existing
107
- // - file_change (Edit/Write batch) — NEW, mirrored as Edit per file
108
- // - mcp_tool_call (MCP servers) — NEW, mirrored as Tool with server prefix
109
- // - collab_tool_call (SUBAGENT FAN-OUT, multi-agent v2 GA 2026-05-18) — NEW
110
- // · spawn_agent / send_input / wait / close_agent / resume_agent
111
- // · mirrored to audit as "Subagent" toolName so /history shows the
112
- // child-spawn activity. Without this, multi-agent Codex tasks fall
113
- // silently through (audit + cost claim breach per CRIT-1).
114
- // · Token usage of children remains UNDER-REPORTED in this fix —
115
- // child usage lives on a separate app-server stream we don't yet
116
- // subscribe to. Disclosed honestly in docs/contracts/00-FINDINGS.md.
117
- // - todo_list — NEW, fed through as a synthetic 'todo' tool call (lets
118
- // ProgressAggregator surface plan changes for PM Layer 2 UX parity)
119
- // - web_search — NEW, mirrored as WebSearch tool
120
- // - reasoning — NOT mirrored (private CoT trace; not an action)
121
- // - error (item) — NEW, surfaced as codehere:error for non-fatal errors
122
- if (event.type === 'item.completed') {
123
- const item = event.data?.item;
124
- if (!item?.type) {
125
- // unknown shape — let it pass for textAccum below
126
- }
127
- else if (item.type === 'command_execution') {
128
- if (item.aggregated_output)
129
- textAccum += item.aggregated_output + '\n';
130
- if (typeof item.command === 'string' && item.command) {
131
- mirrorToolCallToAudit({
132
- sessionId,
133
- agent: 'codex',
134
- toolName: 'Bash',
135
- toolInput: item.command,
136
- cwd,
137
- });
138
- }
139
- }
140
- else if (item.type === 'agent_message') {
141
- if (item.text)
142
- textAccum += item.text + '\n';
143
- }
144
- else if (item.type === 'file_change') {
145
- // One audit entry per changed file. Codehere-cased toolName per
146
- // /history filter convention (matches Claude Edit/Write rows).
147
- for (const change of item.changes ?? []) {
148
- const path = String(change.path ?? '');
149
- const kind = String(change.kind ?? 'update');
150
- if (!path)
151
- continue;
152
- const toolName = kind === 'add' ? 'Write' : kind === 'delete' ? 'Delete' : 'Edit';
153
- mirrorToolCallToAudit({
154
- sessionId,
155
- agent: 'codex',
156
- toolName,
157
- toolInput: path,
158
- cwd,
159
- });
160
- }
161
- }
162
- else if (item.type === 'mcp_tool_call') {
163
- const server = String(item.server ?? '');
164
- const tool = String(item.tool ?? 'mcp');
165
- mirrorToolCallToAudit({
166
- sessionId,
167
- agent: 'codex',
168
- toolName: `MCP.${server}.${tool}`,
169
- toolInput: typeof item.arguments === 'string' ? item.arguments : JSON.stringify(item.arguments ?? {}).slice(0, 500),
170
- cwd,
171
- });
172
- }
173
- else if (item.type === 'collab_tool_call') {
174
- // SUBAGENT FAN-OUT — the v0.7 fix that closes CRIT-1's audit gap.
175
- // Mirror every collab call to the audit log so /history shows the
176
- // subagent activity. Tool naming convention: "Subagent.<tool>".
177
- const collabTool = String(item.tool ?? 'collab');
178
- const receivers = (item.receiver_thread_ids ?? []).join(',');
179
- const promptExcerpt = item.prompt ? String(item.prompt).slice(0, 200) : '';
180
- const auditPayload = JSON.stringify({
181
- tool: collabTool,
182
- sender: item.sender_thread_id,
183
- receivers,
184
- prompt: promptExcerpt,
185
- agents_states: item.agents_states ?? {},
186
- }).slice(0, 1000);
187
- mirrorToolCallToAudit({
188
- sessionId,
189
- agent: 'codex',
190
- toolName: `Subagent.${collabTool}`,
191
- toolInput: auditPayload,
192
- cwd,
193
- });
194
- }
195
- else if (item.type === 'todo_list') {
196
- // Feed into textAccum for Final Report visibility. Future v0.8: wire
197
- // into ProgressAggregator for PM Layer 2 parity with Claude TodoWrite.
198
- const items = (item.items ?? []).map((t) => `- [${t.completed ? 'x' : ' '}] ${t.text ?? ''}`).join('\n');
199
- if (items)
200
- textAccum += `Todo list:\n${items}\n`;
201
- }
202
- else if (item.type === 'web_search') {
203
- mirrorToolCallToAudit({
204
- sessionId,
205
- agent: 'codex',
206
- toolName: 'WebSearch',
207
- toolInput: String(item.query ?? ''),
208
- cwd,
209
- });
210
- }
211
- else if (item.type === 'error') {
212
- // Non-fatal item-level error (distinct from top-level `error` + `turn.failed`).
213
- // Don't flip errorEmitted — caller may still complete successfully.
214
- if (item.message)
215
- textAccum += `[error] ${item.message}\n`;
216
- }
217
- // reasoning: intentionally not mirrored (private trace, not an action)
218
- }
219
- // Accumulate token usage across all turns (one turn.completed per turn).
220
- // v0.7 MED-3 parity — also sum reasoning_output_tokens into outputTokens.
221
- if (event.type === 'turn.completed') {
222
- const u = event.data?.usage;
223
- if (u) {
224
- const acc = usageSnapshot ?? { inputTokens: 0, outputTokens: 0 };
225
- acc.inputTokens += Number(u.input_tokens ?? 0);
226
- acc.outputTokens += Number(u.output_tokens ?? 0);
227
- // Reasoning tokens billed at output rate per OpenAI pricing — fold in.
228
- if (u.reasoning_output_tokens)
229
- acc.outputTokens += Number(u.reasoning_output_tokens);
230
- if (u.cached_input_tokens)
231
- acc.cacheReadTokens = (acc.cacheReadTokens ?? 0) + Number(u.cached_input_tokens);
232
- usageSnapshot = acc;
233
- }
234
- }
235
- // v0.7 MED-1 — explicit handler for turn.failed. Previously caught only by
236
- // the catch-all stringify scan below (load-bearing accident). Now the
237
- // canonical wire surface for API errors is parsed directly.
238
- if (event.type === 'turn.failed' && !errorEmitted) {
239
- const errObj = event.data?.error;
240
- const message = String(errObj?.message ?? '');
241
- if (message) {
242
- const hit = scanForKnownError(message);
243
- if (hit) {
244
- errorEmitted = true;
245
- const errEvent = {
246
- type: 'codehere:error',
247
- timestamp: Date.now(),
248
- data: { code: hit.code, message: hit.message, recovery: hit.recovery },
249
- };
250
- buffer.push(errEvent);
251
- emitter.emit('event', errEvent);
252
- }
253
- }
254
- }
255
- if (!errorEmitted) {
256
- const data = event.data;
257
- let text = '';
258
- if (event.type === 'stderr' && data && typeof data.line === 'string') {
259
- text = data.line;
260
- }
261
- else if (event.type === 'stdout' && data && typeof data.line === 'string') {
262
- text = data.line;
263
- }
264
- else if (data && typeof data === 'object') {
265
- // Catch-all (kept for stream-level `error` events + any future shape we
266
- // don't have an explicit handler for). MED-1's explicit turn.failed
267
- // handler above runs first; this is the safety net.
268
- text = JSON.stringify(data);
269
- }
270
- if (text) {
271
- const hit = scanForKnownError(text);
272
- if (hit) {
273
- errorEmitted = true;
274
- const errEvent = {
275
- type: 'codehere:error',
276
- timestamp: Date.now(),
277
- data: { code: hit.code, message: hit.message, recovery: hit.recovery },
278
- };
279
- buffer.push(errEvent);
280
- emitter.emit('event', errEvent);
281
- }
282
- }
283
- }
284
- };
285
- const spawnEnv = { ...process.env, ...(options.env ?? {}) };
286
- let child;
287
- try {
288
- // Codex expects the prompt via stdin when stdin is piped; passing as argv
289
- // while stdin is 'ignore' causes codex to hang waiting for EOF → exit 0
290
- // with no task run. Pipe stdin, write prompt, close.
291
- const sp = resolveAgentSpawn('codex', 'codex', [...HEADLESS_FLAGS, ...(options.extraArgs ?? [])]);
292
- child = spawn(sp.command, sp.args, {
293
- cwd,
294
- env: spawnEnv,
295
- stdio: ['pipe', 'pipe', 'pipe'],
296
- });
297
- child.stdin?.write(finalPrompt);
298
- child.stdin?.end();
299
- }
300
- catch (err) {
301
- statusValue = 'error';
302
- const msg = err instanceof Error ? err.message : String(err);
303
- queueMicrotask(() => emit({ type: 'error', timestamp: Date.now(), data: { message: msg } }));
304
- return {
305
- sessionId,
306
- startedAt,
307
- prompt,
308
- agent: 'codex',
309
- get status() {
310
- return statusValue;
311
- },
312
- get steps() {
313
- return stepsSnapshot;
314
- },
315
- get artifacts() {
316
- return artifactsSnapshot;
317
- },
318
- subscribe(handler) {
319
- for (const e of buffer)
320
- handler(e);
321
- emitter.on('event', handler);
322
- return () => emitter.off('event', handler);
323
- },
324
- kill() {
325
- /* already not running */
326
- },
327
- };
328
- }
329
- // v0.6.0 — capture codex's thread_id from the `thread.started` event so
330
- // appendMessage() can use `codex exec resume <thread_id> <prompt>` for
331
- // multi-turn continuity. Same pattern as claude-code-adapter capturing
332
- // session_id from the init event for `claude --resume`.
333
- let codexThreadId;
334
- // Organic cassette recording (S2b · capture-first plan) · no-op unless
335
- // CODEHERE_RECORD_CASSETTES=1, so zero overhead by default.
336
- const recorder = startRecording({ adapter: 'codex', sessionId, prompt: finalPrompt, cwd });
337
- let stdoutBuf = '';
338
- child.stdout?.on('data', (chunk) => {
339
- const text = chunk.toString('utf-8');
340
- recorder.onStdout(text);
341
- stdoutBuf += text;
342
- let idx;
343
- while ((idx = stdoutBuf.indexOf('\n')) >= 0) {
344
- const line = stdoutBuf.slice(0, idx).trim();
345
- stdoutBuf = stdoutBuf.slice(idx + 1);
346
- if (!line)
347
- continue;
348
- try {
349
- const data = JSON.parse(line);
350
- if (codexThreadId === undefined && data.type === 'thread.started' && typeof data.thread_id === 'string') {
351
- codexThreadId = data.thread_id;
352
- }
353
- emit({ type: data.type ?? 'codex_event', timestamp: Date.now(), data });
354
- }
355
- catch {
356
- emit({ type: 'stdout', timestamp: Date.now(), data: { line } });
357
- }
358
- }
359
- });
360
- child.stderr?.on('data', (chunk) => {
361
- const text = chunk.toString('utf-8');
362
- recorder.onStderr(text);
363
- for (const line of text.split('\n')) {
364
- if (line.trim())
365
- emit({ type: 'stderr', timestamp: Date.now(), data: { line } });
366
- }
367
- });
368
- child.on('exit', (code, signal) => {
369
- recorder.onExit(code ?? -1, signal);
370
- statusValue =
371
- killRequested || signal !== null ? 'killed' : errorEmitted ? 'error' : code === 0 ? 'completed' : 'error';
372
- // PM events BEFORE 'exit' — see claude-code-adapter for rationale
373
- // (client closes SSE on exit; close() cancels pending messages, so order matters).
374
- if (aggregator.finalize(statusValue === 'completed' ? 'completed' : statusValue === 'killed' ? 'killed' : 'error')) {
375
- stepsSnapshot = aggregator.steps.map((s) => ({ ...s }));
376
- emit({ type: 'codehere:steps', timestamp: Date.now(), data: { steps: stepsSnapshot } });
377
- }
378
- if (statusValue === 'completed') {
379
- try {
380
- artifactsSnapshot = { ...extractArtifacts(snapshot, textAccum), usage: usageSnapshot };
381
- emit({ type: 'codehere:artifacts', timestamp: Date.now(), data: artifactsSnapshot });
382
- }
383
- catch {
384
- /* best-effort */
385
- }
386
- }
387
- emit({
388
- type: 'exit',
389
- timestamp: Date.now(),
390
- data: { code, signal, status: statusValue, durationMs: Date.now() - startedAt },
391
- });
392
- });
393
- child.on('error', (err) => {
394
- statusValue = 'error';
395
- emit({ type: 'error', timestamp: Date.now(), data: { message: err.message } });
396
- });
397
- return {
398
- sessionId,
399
- startedAt,
400
- prompt,
401
- agent: 'codex',
402
- memoryInjection: injected.record,
403
- get status() {
404
- return statusValue;
405
- },
406
- get steps() {
407
- return stepsSnapshot;
408
- },
409
- get artifacts() {
410
- return artifactsSnapshot;
411
- },
412
- subscribe(handler) {
413
- for (const e of buffer)
414
- handler(e);
415
- emitter.on('event', handler);
416
- return () => emitter.off('event', handler);
417
- },
418
- kill() {
419
- if (statusValue !== 'running')
420
- return;
421
- killRequested = true;
422
- child.kill('SIGTERM');
423
- setTimeout(() => {
424
- if (statusValue === 'running') {
425
- try {
426
- child.kill('SIGKILL');
427
- }
428
- catch {
429
- /* already dead */
430
- }
431
- }
432
- }, 3000);
433
- },
434
- canAppendMessage: true,
435
- /**
436
- * v0.6.0 — Slice 1 of REPL persistence. Spawns `codex exec resume <thread_id>`
437
- * with the follow-up prompt via stdin (codex exec resume accepts `-` to read
438
- * prompt from stdin per `codex exec resume --help`). Re-uses the original
439
- * thread context so this is a true continuation, not a fresh session.
440
- *
441
- * Same shape as claude-code's appendMessage:
442
- * - One-shot Q&A (no streaming reply yet)
443
- * - Returns assistant text + duration
444
- * - Rejects if thread_id wasn't captured (init event missed)
445
- *
446
- * Founder hit codex usage cap at smoke time; the real-error path is
447
- * exercised by the rate-limit fingerprint that lands in stderr/JSONL.
448
- */
449
- appendMessage(text) {
450
- const startedAt = Date.now();
451
- return new Promise((resolve, reject) => {
452
- if (!codexThreadId) {
453
- reject(new Error('Cannot reply: codex thread_id was never captured (thread.started event missed)'));
454
- return;
455
- }
456
- let stdout = '';
457
- let stderr = '';
458
- // codex exec resume <SESSION_ID> - → reads prompt from stdin
459
- // Route follow-ups through the mock seam too — under CODEHERE_MOCK=1 this
460
- // hits the mock REPL, not real codex. (Was a real-spend leak.)
461
- const sp2 = resolveAgentSpawn('codex', 'codex', ['exec', '--json', '--skip-git-repo-check', '--dangerously-bypass-approvals-and-sandbox', 'resume', codexThreadId, '-'], text);
462
- const followup = spawn(sp2.command, sp2.args, {
463
- cwd,
464
- env: spawnEnv,
465
- stdio: ['pipe', 'pipe', 'pipe'],
466
- });
467
- followup.stdin?.write(text);
468
- followup.stdin?.end();
469
- followup.on('error', (err) => reject(err));
470
- followup.stdout?.on('data', (chunk) => {
471
- stdout += chunk.toString('utf-8');
472
- });
473
- followup.stderr?.on('data', (chunk) => {
474
- stderr += chunk.toString('utf-8');
475
- });
476
- followup.on('exit', (code) => {
477
- if (code !== 0) {
478
- reject(new Error(`codex exec resume exited ${code}: ${stderr.slice(0, 300)}`));
479
- return;
480
- }
481
- // Parse JSONL — extract assistant message text from item.completed
482
- // events of type agent_message (same shape codex emits during the
483
- // initial exec; reuse the textAccum logic).
484
- const lines = stdout.split('\n').filter((l) => l.trim());
485
- const fragments = [];
486
- for (const line of lines) {
487
- try {
488
- const obj = JSON.parse(line);
489
- if (obj.type === 'item.completed' && obj.item?.type === 'agent_message' && obj.item.text) {
490
- fragments.push(obj.item.text);
491
- }
492
- }
493
- catch {
494
- /* non-JSON line — skip */
495
- }
496
- }
497
- const result = fragments.join('\n').trim() || '(codex returned no agent_message text)';
498
- resolve({ result, durationMs: Date.now() - startedAt });
499
- });
500
- });
501
- },
502
- };
503
- }
504
- export const CodexAdapter = {
505
- id: 'codex',
506
- label: 'Codex',
507
- description: "OpenAI's coding agent. Good when the task needs reading a lot of code first, then changing several files together.",
508
- processPattern: /(?:^|\/)codex(?:\s|$)/,
509
- isInstalled() {
510
- try {
511
- const out = execFileSync('which', ['codex'], { encoding: 'utf-8', timeout: 2000 });
512
- return out.trim().length > 0;
513
- }
514
- catch {
515
- return false;
516
- }
517
- },
518
- spawn(prompt, options) {
519
- return spawnCodex(prompt, options);
520
- },
521
- checkReadiness() {
522
- const installed = this.isInstalled();
523
- if (!installed) {
524
- return {
525
- id: this.id,
526
- label: this.label,
527
- state: 'needs_install',
528
- installed: false,
529
- installHint: { command: 'npm install -g @openai/codex', url: 'https://github.com/openai/codex-cli' },
530
- };
531
- }
532
- // Codex supports ChatGPT account auth via `codex login` (similar to Claude
533
- // Code's OAuth) OR OPENAI_API_KEY. Can't verify without calling out — let
534
- // runtime catch real failures.
535
- return { id: this.id, label: this.label, state: 'ready', installed: true };
536
- },
537
- };
538
- //# sourceMappingURL=codex-adapter.js.map