@golba98/codexa 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (226) hide show
  1. package/README.md +320 -0
  2. package/bin/codexa.js +445 -0
  3. package/package.json +45 -0
  4. package/scripts/audit-codexa-capabilities.mjs +466 -0
  5. package/scripts/smoke-terminal-bench.mjs +35 -0
  6. package/src/app.tsx +4561 -0
  7. package/src/appRenderStability.test.ts +131 -0
  8. package/src/commands/handler.test.ts +643 -0
  9. package/src/commands/handler.ts +875 -0
  10. package/src/config/launchArgs.test.ts +158 -0
  11. package/src/config/launchArgs.ts +186 -0
  12. package/src/config/layeredConfig.test.ts +143 -0
  13. package/src/config/layeredConfig.ts +836 -0
  14. package/src/config/persistence.test.ts +110 -0
  15. package/src/config/persistence.ts +311 -0
  16. package/src/config/runtimeConfig.test.ts +218 -0
  17. package/src/config/runtimeConfig.ts +554 -0
  18. package/src/config/settings.test.ts +155 -0
  19. package/src/config/settings.ts +401 -0
  20. package/src/config/toml-serialize.ts +98 -0
  21. package/src/config/trustStore.test.ts +29 -0
  22. package/src/config/trustStore.ts +68 -0
  23. package/src/core/attachments.test.ts +155 -0
  24. package/src/core/attachments.ts +71 -0
  25. package/src/core/auth/codexAuth.test.ts +68 -0
  26. package/src/core/auth/codexAuth.ts +359 -0
  27. package/src/core/cleanupFastFail.test.ts +76 -0
  28. package/src/core/cleanupFastFail.ts +67 -0
  29. package/src/core/clipboard.ts +24 -0
  30. package/src/core/codex.ts +124 -0
  31. package/src/core/codexExecArgs.test.ts +195 -0
  32. package/src/core/codexExecArgs.ts +152 -0
  33. package/src/core/codexLaunch.test.ts +205 -0
  34. package/src/core/codexLaunch.ts +162 -0
  35. package/src/core/codexPrompt.test.ts +252 -0
  36. package/src/core/codexPrompt.ts +428 -0
  37. package/src/core/executables/claudeExecutable.ts +63 -0
  38. package/src/core/executables/codexExecutable.test.ts +212 -0
  39. package/src/core/executables/codexExecutable.ts +159 -0
  40. package/src/core/executables/executableResolver.test.ts +129 -0
  41. package/src/core/executables/executableResolver.ts +138 -0
  42. package/src/core/executables/geminiExecutable.test.ts +116 -0
  43. package/src/core/executables/geminiExecutable.ts +78 -0
  44. package/src/core/executables/pathSanityScan.test.ts +47 -0
  45. package/src/core/githubDiagnostics.test.ts +92 -0
  46. package/src/core/githubDiagnostics.ts +222 -0
  47. package/src/core/hollowResponseFormat.test.ts +58 -0
  48. package/src/core/hollowResponseFormat.ts +39 -0
  49. package/src/core/inputDebug.ts +51 -0
  50. package/src/core/launchContext.test.ts +157 -0
  51. package/src/core/launchContext.ts +266 -0
  52. package/src/core/models/codexCapabilities.test.ts +45 -0
  53. package/src/core/models/codexCapabilities.ts +95 -0
  54. package/src/core/models/codexModelCapabilities.test.ts +246 -0
  55. package/src/core/models/codexModelCapabilities.ts +571 -0
  56. package/src/core/models/modelSpecs.test.ts +283 -0
  57. package/src/core/models/modelSpecs.ts +300 -0
  58. package/src/core/perf/profiler.ts +125 -0
  59. package/src/core/perf/renderDebug.test.ts +230 -0
  60. package/src/core/perf/renderDebug.ts +373 -0
  61. package/src/core/planStorage.test.ts +143 -0
  62. package/src/core/planStorage.ts +141 -0
  63. package/src/core/process/CommandRunner.test.ts +105 -0
  64. package/src/core/process/CommandRunner.ts +269 -0
  65. package/src/core/process/processValidation.ts +101 -0
  66. package/src/core/projectInstructions.test.ts +50 -0
  67. package/src/core/projectInstructions.ts +54 -0
  68. package/src/core/providerLauncher/launcher.test.ts +238 -0
  69. package/src/core/providerLauncher/launcher.ts +203 -0
  70. package/src/core/providerLauncher/registry.test.ts +324 -0
  71. package/src/core/providerLauncher/registry.ts +253 -0
  72. package/src/core/providerLauncher/types.ts +84 -0
  73. package/src/core/providerLauncher/workspaceConfig.test.ts +638 -0
  74. package/src/core/providerLauncher/workspaceConfig.ts +407 -0
  75. package/src/core/providerRuntime/anthropic.test.ts +1120 -0
  76. package/src/core/providerRuntime/anthropic.ts +576 -0
  77. package/src/core/providerRuntime/capabilityProfile.test.ts +311 -0
  78. package/src/core/providerRuntime/capabilityProfile.ts +288 -0
  79. package/src/core/providerRuntime/claudeCodeDiscovery.ts +446 -0
  80. package/src/core/providerRuntime/contextMetadata.test.ts +468 -0
  81. package/src/core/providerRuntime/contextMetadata.ts +409 -0
  82. package/src/core/providerRuntime/gemini.test.ts +437 -0
  83. package/src/core/providerRuntime/gemini.ts +784 -0
  84. package/src/core/providerRuntime/lmstudio.test.ts +168 -0
  85. package/src/core/providerRuntime/lmstudio.ts +118 -0
  86. package/src/core/providerRuntime/local.test.ts +787 -0
  87. package/src/core/providerRuntime/local.ts +754 -0
  88. package/src/core/providerRuntime/models.ts +150 -0
  89. package/src/core/providerRuntime/reasoning.ts +17 -0
  90. package/src/core/providerRuntime/registry.test.ts +233 -0
  91. package/src/core/providerRuntime/registry.ts +203 -0
  92. package/src/core/providerRuntime/types.ts +103 -0
  93. package/src/core/providers/codexJsonStream.test.ts +148 -0
  94. package/src/core/providers/codexJsonStream.ts +305 -0
  95. package/src/core/providers/codexSubprocess.test.ts +68 -0
  96. package/src/core/providers/codexSubprocess.ts +372 -0
  97. package/src/core/providers/codexTranscript.test.ts +284 -0
  98. package/src/core/providers/codexTranscript.ts +695 -0
  99. package/src/core/providers/openaiNative.ts +13 -0
  100. package/src/core/providers/registry.ts +21 -0
  101. package/src/core/providers/types.ts +59 -0
  102. package/src/core/terminal/terminalCapabilities.test.ts +93 -0
  103. package/src/core/terminal/terminalCapabilities.ts +100 -0
  104. package/src/core/terminal/terminalControl.test.ts +75 -0
  105. package/src/core/terminal/terminalControl.ts +147 -0
  106. package/src/core/terminal/terminalSanitize.test.ts +22 -0
  107. package/src/core/terminal/terminalSanitize.ts +147 -0
  108. package/src/core/terminal/terminalSelection.test.ts +42 -0
  109. package/src/core/terminal/terminalSelection.ts +66 -0
  110. package/src/core/terminal/terminalTitle.test.ts +328 -0
  111. package/src/core/terminal/terminalTitle.ts +483 -0
  112. package/src/core/workspaceActivity.test.ts +163 -0
  113. package/src/core/workspaceActivity.ts +380 -0
  114. package/src/core/workspaceGuard.test.ts +151 -0
  115. package/src/core/workspaceGuard.ts +288 -0
  116. package/src/core/workspaceRoot.test.ts +23 -0
  117. package/src/core/workspaceRoot.ts +47 -0
  118. package/src/exec.test.ts +13 -0
  119. package/src/exec.ts +72 -0
  120. package/src/headless/execArgs.test.ts +147 -0
  121. package/src/headless/execArgs.ts +294 -0
  122. package/src/headless/execRunner.test.ts +434 -0
  123. package/src/headless/execRunner.ts +304 -0
  124. package/src/index.test.tsx +618 -0
  125. package/src/index.tsx +296 -0
  126. package/src/session/appSession.test.ts +897 -0
  127. package/src/session/appSession.ts +761 -0
  128. package/src/session/chatLifecycle.test.ts +64 -0
  129. package/src/session/chatLifecycle.ts +951 -0
  130. package/src/session/liveRenderScheduler.test.ts +201 -0
  131. package/src/session/liveRenderScheduler.ts +214 -0
  132. package/src/session/planFlow.test.ts +103 -0
  133. package/src/session/planFlow.ts +149 -0
  134. package/src/session/planTranscript.test.ts +65 -0
  135. package/src/session/planTranscript.ts +15 -0
  136. package/src/session/promptRunSchedule.test.ts +36 -0
  137. package/src/session/promptRunSchedule.ts +26 -0
  138. package/src/session/types.ts +228 -0
  139. package/src/test/runtimeTestUtils.ts +14 -0
  140. package/src/types/react-dom.d.ts +3 -0
  141. package/src/ui/ActionRequiredBlock.tsx +38 -0
  142. package/src/ui/ActivityBars.tsx +68 -0
  143. package/src/ui/ActivityIndicator.test.tsx +58 -0
  144. package/src/ui/ActivityIndicator.tsx +58 -0
  145. package/src/ui/AgentBlock.test.ts +6 -0
  146. package/src/ui/AgentBlock.tsx +130 -0
  147. package/src/ui/AnimatedStatusText.test.ts +16 -0
  148. package/src/ui/AnimatedStatusText.tsx +69 -0
  149. package/src/ui/AppShell.test.tsx +1739 -0
  150. package/src/ui/AppShell.tsx +698 -0
  151. package/src/ui/AttachmentImportPanel.test.tsx +204 -0
  152. package/src/ui/AttachmentImportPanel.tsx +98 -0
  153. package/src/ui/AuthPanel.tsx +113 -0
  154. package/src/ui/BackendPicker.tsx +28 -0
  155. package/src/ui/BottomComposer.test.ts +674 -0
  156. package/src/ui/BottomComposer.tsx +1028 -0
  157. package/src/ui/CodexLogo.tsx +55 -0
  158. package/src/ui/DashCard.tsx +82 -0
  159. package/src/ui/Markdown.test.ts +157 -0
  160. package/src/ui/Markdown.tsx +310 -0
  161. package/src/ui/ModePicker.tsx +27 -0
  162. package/src/ui/ModelPicker.tsx +31 -0
  163. package/src/ui/ModelPickerProviderScope.test.tsx +411 -0
  164. package/src/ui/ModelPickerScreen.test.tsx +99 -0
  165. package/src/ui/ModelPickerScreen.tsx +416 -0
  166. package/src/ui/ModelPickerState.test.tsx +151 -0
  167. package/src/ui/ModelReasoningPicker.test.tsx +447 -0
  168. package/src/ui/ModelReasoningPicker.tsx +458 -0
  169. package/src/ui/Panel.tsx +51 -0
  170. package/src/ui/PermissionsPanel.tsx +78 -0
  171. package/src/ui/PlanActionPicker.tsx +119 -0
  172. package/src/ui/PlanReviewPanel.test.tsx +267 -0
  173. package/src/ui/PlanReviewPanel.tsx +212 -0
  174. package/src/ui/PromptCardBorder.test.tsx +161 -0
  175. package/src/ui/ProviderPicker.test.tsx +289 -0
  176. package/src/ui/ProviderPicker.tsx +321 -0
  177. package/src/ui/ProviderShortcut.test.tsx +143 -0
  178. package/src/ui/ReasoningPicker.tsx +46 -0
  179. package/src/ui/RunFooter.tsx +65 -0
  180. package/src/ui/SelectionPanel.tsx +67 -0
  181. package/src/ui/SettingsPanel.test.tsx +233 -0
  182. package/src/ui/SettingsPanel.tsx +156 -0
  183. package/src/ui/Spinner.tsx +25 -0
  184. package/src/ui/StaticIntroItem.tsx +54 -0
  185. package/src/ui/StaticTranscriptItem.tsx +56 -0
  186. package/src/ui/TextEntryPanel.tsx +139 -0
  187. package/src/ui/ThemePicker.tsx +31 -0
  188. package/src/ui/ThinkingBlock.tsx +100 -0
  189. package/src/ui/Timeline.test.ts +2067 -0
  190. package/src/ui/Timeline.tsx +1472 -0
  191. package/src/ui/TimelineNavigation.test.tsx +201 -0
  192. package/src/ui/TopHeader.test.tsx +239 -0
  193. package/src/ui/TopHeader.tsx +257 -0
  194. package/src/ui/TurnGroup.test.tsx +365 -0
  195. package/src/ui/TurnGroup.tsx +657 -0
  196. package/src/ui/busyStatusAnimation.test.ts +30 -0
  197. package/src/ui/busyStatusAnimation.ts +11 -0
  198. package/src/ui/commandNormalize.test.ts +142 -0
  199. package/src/ui/commandNormalize.ts +66 -0
  200. package/src/ui/diffRenderer.test.ts +102 -0
  201. package/src/ui/diffRenderer.ts +116 -0
  202. package/src/ui/focus.ts +61 -0
  203. package/src/ui/focusFlow.test.tsx +1098 -0
  204. package/src/ui/inputBuffer.test.ts +151 -0
  205. package/src/ui/inputBuffer.ts +203 -0
  206. package/src/ui/layout.test.ts +145 -0
  207. package/src/ui/layout.ts +287 -0
  208. package/src/ui/modeDisplay.test.ts +42 -0
  209. package/src/ui/modeDisplay.ts +52 -0
  210. package/src/ui/outputPipeline.ts +64 -0
  211. package/src/ui/progressEntries.ts +156 -0
  212. package/src/ui/runActivityView.test.ts +89 -0
  213. package/src/ui/runActivityView.ts +37 -0
  214. package/src/ui/runLifecycleView.test.tsx +237 -0
  215. package/src/ui/slashCommands.ts +41 -0
  216. package/src/ui/statusRenderIsolation.test.tsx +654 -0
  217. package/src/ui/terminalAnswerFormat.test.ts +19 -0
  218. package/src/ui/terminalAnswerFormat.ts +128 -0
  219. package/src/ui/textLayout.test.ts +18 -0
  220. package/src/ui/textLayout.ts +338 -0
  221. package/src/ui/theme.tsx +395 -0
  222. package/src/ui/themeFlow.test.ts +53 -0
  223. package/src/ui/themeFlow.ts +41 -0
  224. package/src/ui/timelineMeasure.ts +3088 -0
  225. package/src/ui/timelineMeasureCache.test.ts +986 -0
  226. package/src/ui/useThrottledValue.ts +31 -0
@@ -0,0 +1,304 @@
1
+ import {
2
+ resolveLayeredConfig,
3
+ type LayeredConfigResult,
4
+ } from "../config/layeredConfig.js";
5
+ import type { LaunchArgs } from "../config/launchArgs.js";
6
+ import {
7
+ mergeRuntimeConfig,
8
+ resolveRuntimeConfig,
9
+ type ResolvedRuntimeConfig,
10
+ } from "../config/runtimeConfig.js";
11
+ import { loadProjectInstructions, type ProjectInstructionsLoadResult } from "../core/projectInstructions.js";
12
+ import { getBackendProvider } from "../core/providers/registry.js";
13
+ import type { BackendProvider } from "../core/providers/types.js";
14
+ import { isNoiseLine } from "../core/providers/codexTranscript.js";
15
+ import { sanitizeTerminalOutput } from "../core/terminal/terminalSanitize.js";
16
+ import { resolveWorkspaceRoot } from "../core/workspaceRoot.js";
17
+ import type { RunToolActivity } from "../session/types.js";
18
+
19
+ // ─── Types & constants ────────────────────────────────────────────────────────
20
+
21
+ export const HEADLESS_EXEC_PARSE_ERROR = 2;
22
+ export const HEADLESS_EXEC_PROVIDER_UNAVAILABLE = 3;
23
+ export const HEADLESS_EXEC_RUN_FAILED = 1;
24
+
25
+ export interface HeadlessExecIo {
26
+ stdout: Pick<NodeJS.WriteStream, "write">;
27
+ stderr: Pick<NodeJS.WriteStream, "write">;
28
+ }
29
+
30
+ export interface HeadlessExecOptions {
31
+ prompt: string;
32
+ launchArgs: LaunchArgs;
33
+ workspaceRoot?: string;
34
+ benchmarkDiagnostics?: HeadlessExecTiming;
35
+ promptPolicy?: "raw" | "wrapped";
36
+ }
37
+
38
+ export interface HeadlessExecResult {
39
+ exitCode: number;
40
+ }
41
+
42
+ export interface HeadlessExecDependencies {
43
+ resolveWorkspaceRoot: () => string;
44
+ resolveLayeredConfig: (options: { workspaceRoot: string; launchArgs: LaunchArgs }) => LayeredConfigResult;
45
+ resolveRuntimeConfig: typeof resolveRuntimeConfig;
46
+ getBackendProvider: (id: string) => BackendProvider;
47
+ loadProjectInstructions: (workspaceRoot: string) => ProjectInstructionsLoadResult;
48
+ }
49
+
50
+ export type HeadlessExecTimingValue = string | number | boolean | null | readonly string[];
51
+
52
+ export interface HeadlessExecTiming {
53
+ enabled: boolean;
54
+ mark: (phase: string, fields?: Record<string, HeadlessExecTimingValue>) => void;
55
+ }
56
+
57
+ export function createHeadlessExecTiming(options: {
58
+ enabled: boolean;
59
+ stderr?: Pick<NodeJS.WriteStream, "write">;
60
+ startTimeMs?: number;
61
+ }): HeadlessExecTiming {
62
+ const startTimeMs = options.startTimeMs ?? Date.now();
63
+ const stderr = options.stderr ?? process.stderr;
64
+ let previousElapsedMs = 0;
65
+
66
+ return {
67
+ enabled: options.enabled,
68
+ mark: (phase, fields = {}) => {
69
+ if (!options.enabled) return;
70
+ const elapsedMs = Date.now() - startTimeMs;
71
+ const deltaMs = elapsedMs - previousElapsedMs;
72
+ previousElapsedMs = elapsedMs;
73
+ const formattedFields = Object.entries(fields)
74
+ .map(([key, value]) => {
75
+ const serialized = Array.isArray(value) || typeof value === "string"
76
+ ? JSON.stringify(value)
77
+ : String(value);
78
+ return `${key}=${serialized}`;
79
+ })
80
+ .join(" ");
81
+ writeLine(stderr, `[codexa exec timing] phase=${phase} elapsed_ms=${elapsedMs} delta_ms=${deltaMs}${formattedFields ? ` ${formattedFields}` : ""}`);
82
+ },
83
+ };
84
+ }
85
+
86
+ export const createHeadlessBenchmarkDiagnostics = createHeadlessExecTiming;
87
+
88
+ const DEFAULT_DEPENDENCIES: HeadlessExecDependencies = {
89
+ resolveWorkspaceRoot,
90
+ resolveLayeredConfig,
91
+ resolveRuntimeConfig,
92
+ getBackendProvider,
93
+ loadProjectInstructions,
94
+ };
95
+
96
+ // ─── Helpers ─────────────────────────────────────────────────────────────────
97
+
98
+ function writeLine(stream: Pick<NodeJS.WriteStream, "write">, line: string): void {
99
+ stream.write(`${line}\n`);
100
+ }
101
+
102
+ function formatDiagnosticText(value: string): string {
103
+ return sanitizeTerminalOutput(value)
104
+ .replace(/\r\n/g, "\n")
105
+ .replace(/\r/g, "\n")
106
+ .trim();
107
+ }
108
+
109
+ function writeDiagnostic(stderr: Pick<NodeJS.WriteStream, "write">, kind: string, message: string): void {
110
+ const safeMessage = formatDiagnosticText(message);
111
+ if (!safeMessage) return;
112
+ writeLine(stderr, `[codexa exec] ${kind}: ${safeMessage.replace(/\n/g, "\n ")}`);
113
+ }
114
+
115
+ function formatToolActivity(activity: RunToolActivity): string {
116
+ const summary = activity.summary?.trim();
117
+ const safeSummary = summary && isProcessTerminationNoise(summary) ? "" : summary;
118
+ return summary
119
+ ? `${activity.status}: ${activity.command}${safeSummary ? `\n${safeSummary}` : ""}`
120
+ : `${activity.status}: ${activity.command}`;
121
+ }
122
+
123
+ function isStructuredCodexEventLine(line: string): boolean {
124
+ const trimmed = line.trim();
125
+ if (!trimmed.startsWith("{") || !trimmed.endsWith("}")) {
126
+ return false;
127
+ }
128
+
129
+ try {
130
+ const parsed = JSON.parse(trimmed) as { type?: unknown };
131
+ return typeof parsed.type === "string"
132
+ && /^(?:thread|turn|item)\./.test(parsed.type);
133
+ } catch {
134
+ return false;
135
+ }
136
+ }
137
+
138
+ function isProcessTerminationNoise(line: string): boolean {
139
+ return /^SUCCESS: The process with PID \d+ .* has been terminated\.$/.test(line.trim());
140
+ }
141
+
142
+ function shouldSuppressAssistantChunk(chunk: string): boolean {
143
+ const lines = chunk
144
+ .replace(/\r\n/g, "\n")
145
+ .replace(/\r/g, "\n")
146
+ .split("\n")
147
+ .map((line) => line.trim())
148
+ .filter(Boolean);
149
+
150
+ return lines.length > 0
151
+ && lines.every((line) => isStructuredCodexEventLine(line) || isProcessTerminationNoise(line));
152
+ }
153
+
154
+ function formatRuntimeStartup(runtime: ResolvedRuntimeConfig, workspaceRoot: string, provider: BackendProvider): string {
155
+ return [
156
+ `workspace ${workspaceRoot}`,
157
+ `provider ${provider.label} (${provider.id})`,
158
+ `model ${runtime.model}`,
159
+ `mode ${runtime.mode}`,
160
+ `planMode ${runtime.planMode ? "enabled" : "disabled"}`,
161
+ `sandbox ${runtime.policy.sandboxMode}`,
162
+ `approval ${runtime.policy.approvalPolicy}`,
163
+ `network ${runtime.policy.networkAccess ? "enabled" : "disabled"}`,
164
+ ].join("; ");
165
+ }
166
+
167
+ // ─── Runner ───────────────────────────────────────────────────────────────────
168
+
169
+ export async function runHeadlessExec(
170
+ options: HeadlessExecOptions,
171
+ io: HeadlessExecIo = { stdout: process.stdout, stderr: process.stderr },
172
+ dependencies: Partial<HeadlessExecDependencies> = {},
173
+ ): Promise<HeadlessExecResult> {
174
+ const deps = { ...DEFAULT_DEPENDENCIES, ...dependencies };
175
+ const diagnostics = options.benchmarkDiagnostics;
176
+ const promptPolicy = options.promptPolicy ?? "raw";
177
+ diagnostics?.mark("run_headless_start");
178
+ const workspaceRoot = options.workspaceRoot ?? deps.resolveWorkspaceRoot();
179
+ diagnostics?.mark("workspace_resolved", { workspace_root: workspaceRoot });
180
+ const layeredConfig = deps.resolveLayeredConfig({
181
+ workspaceRoot,
182
+ launchArgs: options.launchArgs,
183
+ });
184
+ diagnostics?.mark("layered_config_loaded");
185
+ const runtimeConfig = mergeRuntimeConfig(layeredConfig.runtime, { planMode: false });
186
+ const runtime = deps.resolveRuntimeConfig(runtimeConfig);
187
+ diagnostics?.mark("runtime_config_resolved", {
188
+ effective_model: runtime.model,
189
+ effective_reasoning_effort: runtime.reasoningLevel,
190
+ prompt_policy: promptPolicy,
191
+ });
192
+
193
+ const projectInstructionsLoad = promptPolicy === "wrapped"
194
+ ? deps.loadProjectInstructions(workspaceRoot)
195
+ : ({ status: "missing" } as ProjectInstructionsLoadResult);
196
+ const projectInstructions = projectInstructionsLoad.status === "loaded"
197
+ ? projectInstructionsLoad.instructions
198
+ : null;
199
+ diagnostics?.mark("project_instructions_resolved", {
200
+ whether_project_instructions_loaded: projectInstructionsLoad.status === "loaded",
201
+ project_instructions_path: projectInstructions?.path ?? ("path" in projectInstructionsLoad ? projectInstructionsLoad.path : null),
202
+ project_instructions_character_count: projectInstructions?.content.length ?? 0,
203
+ });
204
+
205
+ const provider = deps.getBackendProvider(runtime.provider);
206
+ diagnostics?.mark("provider_created", {
207
+ provider_id: provider.id,
208
+ provider_label: provider.label,
209
+ });
210
+
211
+ writeDiagnostic(io.stderr, "startup", formatRuntimeStartup(runtime, workspaceRoot, provider));
212
+
213
+ if (layeredConfig.diagnostics.ignoredEntries.length > 0) {
214
+ writeDiagnostic(io.stderr, "config", `ignored ${layeredConfig.diagnostics.ignoredEntries.join("; ")}`);
215
+ }
216
+
217
+ if (projectInstructionsLoad.status === "error") {
218
+ writeDiagnostic(io.stderr, "config", `could not load project instructions at ${projectInstructionsLoad.path}: ${projectInstructionsLoad.message}`);
219
+ }
220
+
221
+ if (!provider.run) {
222
+ writeDiagnostic(io.stderr, "error", `${provider.label} is unavailable for headless execution.`);
223
+ return { exitCode: HEADLESS_EXEC_PROVIDER_UNAVAILABLE };
224
+ }
225
+
226
+ return await new Promise<HeadlessExecResult>((resolve) => {
227
+ let settled = false;
228
+ const settle = (exitCode: number) => {
229
+ if (settled) return;
230
+ settled = true;
231
+ resolve({ exitCode });
232
+ };
233
+
234
+ try {
235
+ let streamedAssistantChars = 0;
236
+ const toolActivityIds = new Set<string>();
237
+
238
+ provider.run!(
239
+ options.prompt,
240
+ { runtime, workspaceRoot, projectInstructions, promptPolicy },
241
+ {
242
+ onAssistantDelta: (chunk) => {
243
+ const safeChunk = sanitizeTerminalOutput(chunk, { preserveTabs: false, tabSize: 2 });
244
+ if (shouldSuppressAssistantChunk(safeChunk)) return;
245
+ if (!safeChunk) return;
246
+ streamedAssistantChars += safeChunk.length;
247
+ io.stdout.write(safeChunk);
248
+ },
249
+ onProgress: (update) => {
250
+ const safeText = formatDiagnosticText(update.text);
251
+ if (!safeText || isNoiseLine(safeText) || isProcessTerminationNoise(safeText)) return;
252
+ if (update.source === "tool" && toolActivityIds.has(update.id)) return;
253
+ writeDiagnostic(io.stderr, update.source, safeText);
254
+ },
255
+ onToolActivity: (activity) => {
256
+ toolActivityIds.add(activity.id);
257
+ writeDiagnostic(io.stderr, "tool", formatToolActivity(activity));
258
+ },
259
+ onFinalAnswerObserved: (response) => {
260
+ diagnostics?.mark("final_answer_observed", {
261
+ final_answer_character_count: response.length,
262
+ });
263
+ },
264
+ onResponse: (response) => {
265
+ const safeResponse = sanitizeTerminalOutput(response, { preserveTabs: false, tabSize: 2 });
266
+ if (streamedAssistantChars === 0 && safeResponse) {
267
+ io.stdout.write(safeResponse);
268
+ }
269
+ settle(0);
270
+ },
271
+ onError: (message, rawOutput) => {
272
+ const details = [message, rawOutput].filter((value) => value?.trim()).join("\n");
273
+ writeDiagnostic(io.stderr, "error", details || "Provider run failed.");
274
+ settle(HEADLESS_EXEC_RUN_FAILED);
275
+ },
276
+ benchmarkHooks: diagnostics?.enabled
277
+ ? {
278
+ onProviderPrepStart: () => diagnostics.mark("provider_prep_start"),
279
+ onProviderPrepComplete: () => diagnostics.mark("provider_prep_complete"),
280
+ onProviderPromptPrepared: ({ policy, characterCount }) => diagnostics.mark("provider_prompt_prepared", {
281
+ prompt_policy: policy,
282
+ prompt_character_count_before_wrapping: options.prompt.length,
283
+ prompt_character_count_after_wrapping: characterCount,
284
+ }),
285
+ onCodexProcessSpawned: ({ executable, argv }) => diagnostics.mark("codex_process_spawned", {
286
+ codex_argv_preview: [executable, ...argv].join(" "),
287
+ }),
288
+ onFirstStdout: (observed = true) => diagnostics.mark("first_stdout", { observed }),
289
+ onFirstStderr: (observed = true) => diagnostics.mark("first_stderr", { observed }),
290
+ onCodexProcessExit: (exitCode) => diagnostics.mark("codex_process_exit", {
291
+ exit_code: exitCode,
292
+ }),
293
+ onCleanupStart: () => diagnostics.mark("cleanup_start"),
294
+ onCleanupComplete: ({ skipped }) => diagnostics.mark("cleanup_complete", { skipped }),
295
+ }
296
+ : undefined,
297
+ },
298
+ );
299
+ } catch (error) {
300
+ writeDiagnostic(io.stderr, "error", error instanceof Error ? error.message : String(error));
301
+ settle(HEADLESS_EXEC_RUN_FAILED);
302
+ }
303
+ });
304
+ }