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