codehere 1.0.1 → 1.0.3
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.
- package/README.md +35 -193
- package/dist/application/use-cases/check-claimed-tests.js +81 -0
- package/dist/application/use-cases/detect-test-weakening.js +237 -0
- package/dist/application/use-cases/rerun-tests.js +110 -7
- package/dist/index.js +38 -1212
- package/dist/infrastructure/orchestration/artifacts-extractor.js +2 -323
- package/dist/presentation/cli/commands/check-command.js +178 -23
- package/dist/presentation/cli/commands/help-command.js +21 -285
- package/dist/ui.js +5 -236
- package/dist/utils/version.js +0 -6
- package/package.json +9 -38
- package/dist/application/services/verdict-service.js +0 -154
- package/dist/application/use-cases/assess-cross-vendor.js +0 -126
- package/dist/application/use-cases/build-cross-vendor-verdict.js +0 -118
- package/dist/application/use-cases/domain-trust.js +0 -110
- package/dist/application/use-cases/export-aat.js +0 -107
- package/dist/application/use-cases/generate-acceptance-criteria.js +0 -223
- package/dist/application/use-cases/generate-final-report.js +0 -259
- package/dist/application/use-cases/generate-narration.js +0 -130
- package/dist/application/use-cases/generate-thread-title.js +0 -181
- package/dist/application/use-cases/narrator-prompt-template.js +0 -158
- package/dist/application/use-cases/plan-auto-review.js +0 -57
- package/dist/application/use-cases/reconcile-audit.js +0 -100
- package/dist/application/use-cases/search-sessions.js +0 -70
- package/dist/application/use-cases/should-auto-review.js +0 -71
- package/dist/application/use-cases/thread-export.js +0 -245
- package/dist/application/use-cases/verify-claim.js +0 -456
- package/dist/application/use-cases/write-pass.js +0 -277
- package/dist/domain/audit.js +0 -11
- package/dist/domain/services/codebase-detector.js +0 -85
- package/dist/domain/task.js +0 -91
- package/dist/error-handler.js +0 -139
- package/dist/infrastructure/completion/bash-completion.js +0 -29
- package/dist/infrastructure/completion/fish-completion.js +0 -17
- package/dist/infrastructure/completion/zsh-completion.js +0 -25
- package/dist/infrastructure/config/config-manager.js +0 -174
- package/dist/infrastructure/config/product-context.js +0 -49
- package/dist/infrastructure/context/adapters/drawio-source.js +0 -157
- package/dist/infrastructure/context/adapters/figma-source.js +0 -198
- package/dist/infrastructure/context/adapters/github-issue-source.js +0 -160
- package/dist/infrastructure/context/adapters/linear-source.js +0 -190
- package/dist/infrastructure/context/context-resolver.js +0 -144
- package/dist/infrastructure/context/context-source.js +0 -114
- package/dist/infrastructure/context/github-api.js +0 -110
- package/dist/infrastructure/context/github-device-flow.js +0 -65
- package/dist/infrastructure/context/oauth-flow.js +0 -207
- package/dist/infrastructure/context/oauth-store.js +0 -122
- package/dist/infrastructure/context/registry-bootstrap.js +0 -95
- package/dist/infrastructure/context/write-pass-resolver.js +0 -106
- package/dist/infrastructure/cost/budget-config.js +0 -114
- package/dist/infrastructure/cost/cost-router.js +0 -97
- package/dist/infrastructure/cost/spend-aggregator.js +0 -222
- package/dist/infrastructure/cost/team-ledger.js +0 -355
- package/dist/infrastructure/discovery/discovery-log.js +0 -85
- package/dist/infrastructure/hooks/audit-tip-signature.js +0 -336
- package/dist/infrastructure/hooks/claude-sdk-hook.js +0 -1018
- package/dist/infrastructure/hooks/hook-mode.js +0 -87
- package/dist/infrastructure/hooks/mirror-tool-call-to-audit.js +0 -49
- package/dist/infrastructure/hooks/pre-prompt-scan.js +0 -122
- package/dist/infrastructure/hooks/session-analyzer.js +0 -177
- package/dist/infrastructure/observability/execution-tracer.js +0 -227
- package/dist/infrastructure/orchestration/agent-adapter.js +0 -13
- package/dist/infrastructure/orchestration/agent-installer.js +0 -101
- package/dist/infrastructure/orchestration/agents-config-seed.js +0 -68
- package/dist/infrastructure/orchestration/aider-adapter.js +0 -611
- package/dist/infrastructure/orchestration/cassette-recorder.js +0 -120
- package/dist/infrastructure/orchestration/claude-code-adapter.js +0 -592
- package/dist/infrastructure/orchestration/codex-adapter.js +0 -574
- package/dist/infrastructure/orchestration/conductor-adapter.js +0 -416
- package/dist/infrastructure/orchestration/context-handoff.js +0 -162
- package/dist/infrastructure/orchestration/control/codex-control-surface.js +0 -99
- package/dist/infrastructure/orchestration/control/control-surface.js +0 -15
- package/dist/infrastructure/orchestration/detect-ollama.js +0 -75
- package/dist/infrastructure/orchestration/failover-classifier.js +0 -151
- package/dist/infrastructure/orchestration/failover-config.js +0 -67
- package/dist/infrastructure/orchestration/failover-policy.js +0 -106
- package/dist/infrastructure/orchestration/gemini-adapter.js +0 -663
- package/dist/infrastructure/orchestration/generic-cli-adapter.js +0 -689
- package/dist/infrastructure/orchestration/graphify-context.js +0 -209
- package/dist/infrastructure/orchestration/memory-injector.js +0 -380
- package/dist/infrastructure/orchestration/mocks/mock-agent-repl.js +0 -388
- package/dist/infrastructure/orchestration/mocks/mock-codex-app-server.js +0 -73
- package/dist/infrastructure/orchestration/mocks/mock-synthesis.js +0 -83
- package/dist/infrastructure/orchestration/mocks/resolve-agent-spawn.js +0 -37
- package/dist/infrastructure/orchestration/opencode-adapter.js +0 -580
- package/dist/infrastructure/orchestration/progress-aggregator.js +0 -906
- package/dist/infrastructure/orchestration/readiness.js +0 -103
- package/dist/infrastructure/orchestration/session-manager.js +0 -1590
- package/dist/infrastructure/orchestration/session-persistence.js +0 -394
- package/dist/infrastructure/orchestration/thread-key-store.js +0 -127
- package/dist/infrastructure/orchestration/write-pass-store.js +0 -38
- package/dist/infrastructure/protocols/cloud-sync.js +0 -207
- package/dist/infrastructure/protocols/mcp-helpers.js +0 -25
- package/dist/infrastructure/protocols/mcp-server-impl.js +0 -3935
- package/dist/infrastructure/protocols/mcp-tools.js +0 -401
- package/dist/infrastructure/protocols/probe-port.js +0 -36
- package/dist/infrastructure/protocols/thread-phase.js +0 -105
- package/dist/infrastructure/protocols/ui/app-script-artifacts.js +0 -121
- package/dist/infrastructure/protocols/ui/app-script-audit-footer.js +0 -44
- package/dist/infrastructure/protocols/ui/app-script-audit-verify.js +0 -56
- package/dist/infrastructure/protocols/ui/app-script-composer-cost.js +0 -588
- package/dist/infrastructure/protocols/ui/app-script-context-sources.js +0 -168
- package/dist/infrastructure/protocols/ui/app-script-conversation.js +0 -67
- package/dist/infrastructure/protocols/ui/app-script-drawer.js +0 -115
- package/dist/infrastructure/protocols/ui/app-script-failover.js +0 -133
- package/dist/infrastructure/protocols/ui/app-script-github.js +0 -113
- package/dist/infrastructure/protocols/ui/app-script-observer.js +0 -104
- package/dist/infrastructure/protocols/ui/app-script-onboarding.js +0 -80
- package/dist/infrastructure/protocols/ui/app-script-overlay.js +0 -203
- package/dist/infrastructure/protocols/ui/app-script-recovery.js +0 -136
- package/dist/infrastructure/protocols/ui/app-script-render.js +0 -166
- package/dist/infrastructure/protocols/ui/app-script-share.js +0 -78
- package/dist/infrastructure/protocols/ui/app-script-sidebar.js +0 -194
- package/dist/infrastructure/protocols/ui/app-script-tour.js +0 -78
- package/dist/infrastructure/protocols/ui/app-script-wpass.js +0 -312
- package/dist/infrastructure/protocols/ui/app-script.js +0 -2998
- package/dist/infrastructure/protocols/ui/font-faces.js +0 -7
- package/dist/infrastructure/protocols/ui/styles.js +0 -2147
- package/dist/infrastructure/protocols/ui/tokens.js +0 -96
- package/dist/infrastructure/protocols/ui-page-v2.js +0 -320
- package/dist/infrastructure/protocols/verify-page.js +0 -1677
- package/dist/infrastructure/security/file-collection.js +0 -118
- package/dist/infrastructure/security/fix-code.js +0 -144
- package/dist/infrastructure/security/fix-secret.js +0 -89
- package/dist/infrastructure/security/permissions-config.js +0 -235
- package/dist/infrastructure/security/scan-types.js +0 -10
- package/dist/infrastructure/security/ship-scan.js +0 -195
- package/dist/infrastructure/security/trust-scorer.js +0 -724
- package/dist/infrastructure/security/vibe-checks.js +0 -99
- package/dist/infrastructure/storage/atomic-write.js +0 -86
- package/dist/infrastructure/storage/cross-tool-agents.js +0 -268
- package/dist/infrastructure/storage/project-thread-migration.js +0 -237
- package/dist/infrastructure/storage/project-thread-mirror.js +0 -671
- package/dist/infrastructure/storage/project-thread-reader.js +0 -409
- package/dist/infrastructure/storage/project-thread-title-backfill.js +0 -214
- package/dist/monitoring.js +0 -212
- package/dist/presentation/cli/commands/audit-command.js +0 -201
- package/dist/presentation/cli/commands/budget-command.js +0 -79
- package/dist/presentation/cli/commands/config-command.js +0 -125
- package/dist/presentation/cli/commands/context-source-command.js +0 -217
- package/dist/presentation/cli/commands/hook-command.js +0 -265
- package/dist/presentation/cli/commands/init-command.js +0 -590
- package/dist/presentation/cli/commands/memory-command.js +0 -1031
- package/dist/presentation/cli/commands/narrator-command.js +0 -101
- package/dist/presentation/cli/commands/permission-command.js +0 -105
- package/dist/presentation/cli/commands/protocols-builder.js +0 -254
- package/dist/presentation/cli/commands/protocols-command.js +0 -84
- package/dist/presentation/cli/commands/repo-map-builder.js +0 -247
- package/dist/presentation/cli/commands/report-command.js +0 -403
- package/dist/presentation/cli/commands/scan-command.js +0 -52
- package/dist/presentation/cli/commands/scan-tool-command.js +0 -110
- package/dist/presentation/cli/commands/search-command.js +0 -51
- package/dist/presentation/cli/commands/setup-command.js +0 -330
- package/dist/presentation/cli/commands/status-command.js +0 -614
- package/dist/presentation/cli/commands/team-command.js +0 -256
- package/dist/presentation/cli/commands/thread-command.js +0 -370
- package/dist/presentation/cli/commands/trust-command.js +0 -328
- package/dist/presentation/cli/commands/verify-command.js +0 -298
- package/dist/presentation/cli/error-display.js +0 -328
- package/dist/presentation/cli/repl.js +0 -805
- package/dist/presentation/cli/task-deliverable.js +0 -153
- package/dist/testing/cassette.js +0 -125
- 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
|