@golba98/codexa 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.
Files changed (234) hide show
  1. package/README.md +391 -94
  2. package/package.json +14 -8
  3. package/src/app.tsx +196 -63
  4. package/src/commands/handler.ts +9 -4
  5. package/src/config/buildInfo.ts +3 -0
  6. package/src/config/launchArgs.ts +10 -0
  7. package/src/config/layeredConfig.ts +0 -0
  8. package/src/config/persistence.ts +51 -0
  9. package/src/config/runtimeConfig.ts +4 -0
  10. package/src/config/settings.ts +6 -13
  11. package/src/config/toml-serialize.ts +0 -0
  12. package/src/config/trustStore.ts +0 -0
  13. package/src/config/updateCheckCache.ts +63 -0
  14. package/src/core/attachments.ts +0 -0
  15. package/src/core/auth/codexAuth.ts +0 -0
  16. package/src/core/channel.ts +23 -0
  17. package/src/core/cleanupFastFail.ts +0 -0
  18. package/src/core/clipboard.ts +0 -0
  19. package/src/core/codex.ts +0 -0
  20. package/src/core/codexExecArgs.ts +0 -0
  21. package/src/core/codexLaunch.ts +0 -0
  22. package/src/core/codexPrompt.ts +0 -0
  23. package/src/core/executables/claudeExecutable.ts +0 -0
  24. package/src/core/executables/codexExecutable.ts +1 -0
  25. package/src/core/executables/executableResolver.ts +61 -43
  26. package/src/core/executables/geminiExecutable.ts +0 -0
  27. package/src/core/githubDiagnostics.ts +0 -0
  28. package/src/core/hollowResponseFormat.ts +0 -0
  29. package/src/core/inputDebug.ts +0 -0
  30. package/src/core/launchContext.ts +33 -33
  31. package/src/core/models/codexCapabilities.ts +0 -0
  32. package/src/core/models/codexModelCapabilities.ts +0 -0
  33. package/src/core/models/modelSpecs.ts +0 -0
  34. package/src/core/perf/profiler.ts +0 -0
  35. package/src/core/perf/renderDebug.ts +0 -0
  36. package/src/core/planStorage.ts +0 -0
  37. package/src/core/process/CommandRunner.ts +0 -0
  38. package/src/core/process/processValidation.ts +9 -5
  39. package/src/core/projectInstructions.ts +0 -0
  40. package/src/core/providerLauncher/launcher.ts +59 -42
  41. package/src/core/providerLauncher/registry.ts +0 -0
  42. package/src/core/providerLauncher/types.ts +0 -0
  43. package/src/core/providerLauncher/workspaceConfig.ts +0 -0
  44. package/src/core/providerRuntime/anthropic.ts +0 -0
  45. package/src/core/providerRuntime/capabilityProfile.ts +0 -0
  46. package/src/core/providerRuntime/claudeCodeDiscovery.ts +0 -0
  47. package/src/core/providerRuntime/contextMetadata.ts +0 -0
  48. package/src/core/providerRuntime/gemini.ts +0 -0
  49. package/src/core/providerRuntime/lmstudio.ts +0 -0
  50. package/src/core/providerRuntime/local.ts +0 -0
  51. package/src/core/providerRuntime/models.ts +0 -0
  52. package/src/core/providerRuntime/reasoning.ts +0 -0
  53. package/src/core/providerRuntime/registry.ts +0 -0
  54. package/src/core/providerRuntime/types.ts +0 -0
  55. package/src/core/providers/codexJsonStream.ts +0 -0
  56. package/src/core/providers/codexSubprocess.ts +0 -0
  57. package/src/core/providers/codexTranscript.ts +0 -0
  58. package/src/core/providers/openaiNative.ts +0 -0
  59. package/src/core/providers/registry.ts +0 -0
  60. package/src/core/providers/types.ts +0 -0
  61. package/src/core/terminal/startupClear.ts +20 -0
  62. package/src/core/terminal/terminalCapabilities.ts +0 -0
  63. package/src/core/terminal/terminalControl.ts +0 -0
  64. package/src/core/terminal/terminalSanitize.ts +0 -0
  65. package/src/core/terminal/terminalSelection.ts +0 -0
  66. package/src/core/terminal/terminalTitle.ts +0 -0
  67. package/src/core/updateCheck.ts +188 -0
  68. package/src/core/workspaceActivity.ts +0 -0
  69. package/src/core/workspaceGuard.ts +0 -0
  70. package/src/core/workspaceRoot.ts +0 -0
  71. package/src/exec.ts +0 -0
  72. package/src/headless/execArgs.ts +1 -0
  73. package/src/headless/execRunner.ts +0 -0
  74. package/src/index.tsx +13 -9
  75. package/src/session/appSession.ts +0 -0
  76. package/src/session/chatLifecycle.ts +0 -0
  77. package/src/session/liveRenderScheduler.ts +0 -0
  78. package/src/session/planFlow.ts +0 -0
  79. package/src/session/planTranscript.ts +0 -0
  80. package/src/session/promptRunSchedule.ts +0 -0
  81. package/src/session/types.ts +2 -1
  82. package/src/test/runtimeTestUtils.ts +0 -0
  83. package/src/types/react-dom.d.ts +0 -0
  84. package/src/ui/ActionRequiredBlock.tsx +5 -5
  85. package/src/ui/ActivityBars.tsx +3 -3
  86. package/src/ui/ActivityIndicator.tsx +6 -6
  87. package/src/ui/AgentBlock.tsx +6 -6
  88. package/src/ui/AnimatedStatusText.tsx +1 -1
  89. package/src/ui/AppShell.tsx +13 -5
  90. package/src/ui/AttachmentImportPanel.tsx +8 -8
  91. package/src/ui/AuthPanel.tsx +20 -20
  92. package/src/ui/BackendPicker.tsx +0 -0
  93. package/src/ui/BottomComposer.tsx +117 -78
  94. package/src/ui/DashCard.tsx +3 -3
  95. package/src/ui/Markdown.tsx +17 -17
  96. package/src/ui/ModePicker.tsx +0 -0
  97. package/src/ui/ModelPicker.tsx +0 -0
  98. package/src/ui/ModelPickerScreen.tsx +10 -10
  99. package/src/ui/ModelReasoningPicker.tsx +14 -14
  100. package/src/ui/Panel.tsx +3 -3
  101. package/src/ui/PermissionsPanel.tsx +0 -0
  102. package/src/ui/PlanActionPicker.tsx +6 -6
  103. package/src/ui/PlanReviewPanel.tsx +8 -8
  104. package/src/ui/ProviderPicker.tsx +18 -18
  105. package/src/ui/ReasoningPicker.tsx +0 -0
  106. package/src/ui/RunFooter.tsx +3 -3
  107. package/src/ui/SelectionPanel.tsx +4 -4
  108. package/src/ui/SettingsPanel.tsx +8 -8
  109. package/src/ui/Spinner.tsx +1 -1
  110. package/src/ui/StaticIntroItem.tsx +0 -0
  111. package/src/ui/StaticTranscriptItem.tsx +0 -0
  112. package/src/ui/TextEntryPanel.tsx +10 -10
  113. package/src/ui/ThemePicker.tsx +0 -0
  114. package/src/ui/ThinkingBlock.tsx +8 -8
  115. package/src/ui/Timeline.tsx +21 -17
  116. package/src/ui/TopHeader.tsx +421 -257
  117. package/src/ui/TurnGroup.tsx +31 -31
  118. package/src/ui/UpdateAvailableCard.tsx +41 -0
  119. package/src/ui/UpdatePromptPanel.tsx +203 -0
  120. package/src/ui/busyStatusAnimation.ts +0 -0
  121. package/src/ui/commandNormalize.ts +0 -0
  122. package/src/ui/diffRenderer.ts +0 -0
  123. package/src/ui/focus.ts +3 -0
  124. package/src/ui/inputBuffer.ts +0 -0
  125. package/src/ui/layout.ts +1 -1
  126. package/src/ui/logoVariants.ts +107 -0
  127. package/src/ui/modeDisplay.ts +12 -12
  128. package/src/ui/outputPipeline.ts +0 -0
  129. package/src/ui/progressEntries.ts +0 -0
  130. package/src/ui/runActivityView.ts +0 -0
  131. package/src/ui/runtimeDisplay.ts +100 -0
  132. package/src/ui/slashCommands.ts +0 -0
  133. package/src/ui/terminalAnswerFormat.ts +0 -0
  134. package/src/ui/textLayout.ts +0 -0
  135. package/src/ui/theme.tsx +274 -395
  136. package/src/ui/themeFlow.ts +0 -0
  137. package/src/ui/timelineMeasure.ts +29 -19
  138. package/src/ui/useThrottledValue.ts +0 -0
  139. package/scripts/audit-codexa-capabilities.mjs +0 -466
  140. package/scripts/smoke-terminal-bench.mjs +0 -35
  141. package/src/appRenderStability.test.ts +0 -131
  142. package/src/commands/handler.test.ts +0 -643
  143. package/src/config/launchArgs.test.ts +0 -158
  144. package/src/config/layeredConfig.test.ts +0 -143
  145. package/src/config/persistence.test.ts +0 -110
  146. package/src/config/runtimeConfig.test.ts +0 -218
  147. package/src/config/settings.test.ts +0 -155
  148. package/src/config/trustStore.test.ts +0 -29
  149. package/src/core/attachments.test.ts +0 -155
  150. package/src/core/auth/codexAuth.test.ts +0 -68
  151. package/src/core/cleanupFastFail.test.ts +0 -76
  152. package/src/core/codexExecArgs.test.ts +0 -195
  153. package/src/core/codexLaunch.test.ts +0 -205
  154. package/src/core/codexPrompt.test.ts +0 -252
  155. package/src/core/executables/codexExecutable.test.ts +0 -212
  156. package/src/core/executables/executableResolver.test.ts +0 -129
  157. package/src/core/executables/geminiExecutable.test.ts +0 -116
  158. package/src/core/executables/pathSanityScan.test.ts +0 -47
  159. package/src/core/githubDiagnostics.test.ts +0 -92
  160. package/src/core/hollowResponseFormat.test.ts +0 -58
  161. package/src/core/launchContext.test.ts +0 -157
  162. package/src/core/models/codexCapabilities.test.ts +0 -45
  163. package/src/core/models/codexModelCapabilities.test.ts +0 -246
  164. package/src/core/models/modelSpecs.test.ts +0 -283
  165. package/src/core/perf/renderDebug.test.ts +0 -230
  166. package/src/core/planStorage.test.ts +0 -143
  167. package/src/core/process/CommandRunner.test.ts +0 -105
  168. package/src/core/projectInstructions.test.ts +0 -50
  169. package/src/core/providerLauncher/launcher.test.ts +0 -238
  170. package/src/core/providerLauncher/registry.test.ts +0 -324
  171. package/src/core/providerLauncher/workspaceConfig.test.ts +0 -638
  172. package/src/core/providerRuntime/anthropic.test.ts +0 -1120
  173. package/src/core/providerRuntime/capabilityProfile.test.ts +0 -311
  174. package/src/core/providerRuntime/contextMetadata.test.ts +0 -468
  175. package/src/core/providerRuntime/gemini.test.ts +0 -437
  176. package/src/core/providerRuntime/lmstudio.test.ts +0 -168
  177. package/src/core/providerRuntime/local.test.ts +0 -787
  178. package/src/core/providerRuntime/registry.test.ts +0 -233
  179. package/src/core/providers/codexJsonStream.test.ts +0 -148
  180. package/src/core/providers/codexSubprocess.test.ts +0 -68
  181. package/src/core/providers/codexTranscript.test.ts +0 -284
  182. package/src/core/terminal/terminalCapabilities.test.ts +0 -93
  183. package/src/core/terminal/terminalControl.test.ts +0 -75
  184. package/src/core/terminal/terminalSanitize.test.ts +0 -22
  185. package/src/core/terminal/terminalSelection.test.ts +0 -42
  186. package/src/core/terminal/terminalTitle.test.ts +0 -328
  187. package/src/core/workspaceActivity.test.ts +0 -163
  188. package/src/core/workspaceGuard.test.ts +0 -151
  189. package/src/core/workspaceRoot.test.ts +0 -23
  190. package/src/exec.test.ts +0 -13
  191. package/src/headless/execArgs.test.ts +0 -147
  192. package/src/headless/execRunner.test.ts +0 -434
  193. package/src/index.test.tsx +0 -618
  194. package/src/session/appSession.test.ts +0 -897
  195. package/src/session/chatLifecycle.test.ts +0 -64
  196. package/src/session/liveRenderScheduler.test.ts +0 -201
  197. package/src/session/planFlow.test.ts +0 -103
  198. package/src/session/planTranscript.test.ts +0 -65
  199. package/src/session/promptRunSchedule.test.ts +0 -36
  200. package/src/ui/ActivityIndicator.test.tsx +0 -58
  201. package/src/ui/AgentBlock.test.ts +0 -6
  202. package/src/ui/AnimatedStatusText.test.ts +0 -16
  203. package/src/ui/AppShell.test.tsx +0 -1739
  204. package/src/ui/AttachmentImportPanel.test.tsx +0 -204
  205. package/src/ui/BottomComposer.test.ts +0 -674
  206. package/src/ui/CodexLogo.tsx +0 -55
  207. package/src/ui/Markdown.test.ts +0 -157
  208. package/src/ui/ModelPickerProviderScope.test.tsx +0 -411
  209. package/src/ui/ModelPickerScreen.test.tsx +0 -99
  210. package/src/ui/ModelPickerState.test.tsx +0 -151
  211. package/src/ui/ModelReasoningPicker.test.tsx +0 -447
  212. package/src/ui/PlanReviewPanel.test.tsx +0 -267
  213. package/src/ui/PromptCardBorder.test.tsx +0 -161
  214. package/src/ui/ProviderPicker.test.tsx +0 -289
  215. package/src/ui/ProviderShortcut.test.tsx +0 -143
  216. package/src/ui/SettingsPanel.test.tsx +0 -233
  217. package/src/ui/Timeline.test.ts +0 -2067
  218. package/src/ui/TimelineNavigation.test.tsx +0 -201
  219. package/src/ui/TopHeader.test.tsx +0 -239
  220. package/src/ui/TurnGroup.test.tsx +0 -365
  221. package/src/ui/busyStatusAnimation.test.ts +0 -30
  222. package/src/ui/commandNormalize.test.ts +0 -142
  223. package/src/ui/diffRenderer.test.ts +0 -102
  224. package/src/ui/focusFlow.test.tsx +0 -1098
  225. package/src/ui/inputBuffer.test.ts +0 -151
  226. package/src/ui/layout.test.ts +0 -145
  227. package/src/ui/modeDisplay.test.ts +0 -42
  228. package/src/ui/runActivityView.test.ts +0 -89
  229. package/src/ui/runLifecycleView.test.tsx +0 -237
  230. package/src/ui/statusRenderIsolation.test.tsx +0 -654
  231. package/src/ui/terminalAnswerFormat.test.ts +0 -19
  232. package/src/ui/textLayout.test.ts +0 -18
  233. package/src/ui/themeFlow.test.ts +0 -53
  234. package/src/ui/timelineMeasureCache.test.ts +0 -986
@@ -1,147 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import test from "node:test";
3
- import { parseHeadlessExecArgs } from "./execArgs.js";
4
-
5
- test("parses codexa exec positional prompt", () => {
6
- const parsed = parseHeadlessExecArgs(["Print", "the", "directory"]);
7
-
8
- assert.equal(parsed.ok, true);
9
- if (!parsed.ok) return;
10
- assert.equal(parsed.value.prompt, "Print the directory");
11
- assert.equal(parsed.value.promptPolicy, "raw");
12
- assert.equal(parsed.value.launchArgs.initialPrompt, "Print the directory");
13
- });
14
-
15
- test("parses codexa exec --prompt value", () => {
16
- const parsed = parseHeadlessExecArgs(["--prompt", "Print the directory"]);
17
-
18
- assert.equal(parsed.ok, true);
19
- if (!parsed.ok) return;
20
- assert.equal(parsed.value.prompt, "Print the directory");
21
- });
22
-
23
- test("parses codexa exec --prompt=value", () => {
24
- const parsed = parseHeadlessExecArgs(["--prompt=Print the directory"]);
25
-
26
- assert.equal(parsed.ok, true);
27
- if (!parsed.ok) return;
28
- assert.equal(parsed.value.prompt, "Print the directory");
29
- });
30
-
31
- test("rejects missing and empty prompts", () => {
32
- const missing = parseHeadlessExecArgs([]);
33
- assert.equal(missing.ok, false);
34
- if (!missing.ok) {
35
- assert.match(missing.error, /missing prompt/i);
36
- }
37
-
38
- const empty = parseHeadlessExecArgs(["--prompt", " "]);
39
- assert.equal(empty.ok, false);
40
- if (!empty.ok) {
41
- assert.match(empty.error, /--prompt/i);
42
- }
43
- });
44
-
45
- test("parses codexa exec --reasoning value", () => {
46
- const parsed = parseHeadlessExecArgs(["--reasoning", "medium", "Print files"]);
47
-
48
- assert.equal(parsed.ok, true);
49
- if (!parsed.ok) return;
50
- assert.equal(parsed.value.prompt, "Print files");
51
- assert.deepEqual(parsed.value.launchArgs.configOverrides, ["model_reasoning_effort=medium"]);
52
- assert.deepEqual(parsed.value.launchArgs.passthroughArgs, ["--reasoning", "medium"]);
53
- });
54
-
55
- test("parses codexa exec --reasoning=value", () => {
56
- const parsed = parseHeadlessExecArgs(["--reasoning=high", "Print files"]);
57
-
58
- assert.equal(parsed.ok, true);
59
- if (!parsed.ok) return;
60
- assert.equal(parsed.value.prompt, "Print files");
61
- assert.deepEqual(parsed.value.launchArgs.configOverrides, ["model_reasoning_effort=high"]);
62
- assert.deepEqual(parsed.value.launchArgs.passthroughArgs, ["--reasoning=high"]);
63
- });
64
-
65
- test("parses timing and prompt policy flags without forwarding them to Codex", () => {
66
- const parsed = parseHeadlessExecArgs([
67
- "--timing",
68
- "--codexa-prompt-policy",
69
- "wrapped",
70
- "Print files",
71
- ]);
72
-
73
- assert.equal(parsed.ok, true);
74
- if (!parsed.ok) return;
75
- assert.equal(parsed.value.timing, true);
76
- assert.equal(parsed.value.promptPolicy, "wrapped");
77
- assert.equal(parsed.value.prompt, "Print files");
78
- assert.deepEqual(parsed.value.launchArgs.passthroughArgs, []);
79
- });
80
-
81
- test("rejects invalid prompt policy values", () => {
82
- const parsed = parseHeadlessExecArgs(["--codexa-prompt-policy", "verbose", "Prompt"]);
83
-
84
- assert.equal(parsed.ok, false);
85
- if (!parsed.ok) {
86
- assert.match(parsed.error, /codexa-prompt-policy/i);
87
- }
88
- });
89
-
90
- test("parses codexa exec --model and --reasoning together", () => {
91
- const parsed = parseHeadlessExecArgs([
92
- "--model", "gpt-5.4-mini",
93
- "--reasoning", "medium",
94
- "Reply with exactly: CODEXA_READY",
95
- ]);
96
-
97
- assert.equal(parsed.ok, true);
98
- if (!parsed.ok) return;
99
- assert.equal(parsed.value.prompt, "Reply with exactly: CODEXA_READY");
100
- assert.deepEqual(parsed.value.launchArgs.configOverrides, [
101
- "model=\"gpt-5.4-mini\"",
102
- "model_reasoning_effort=medium",
103
- ]);
104
- });
105
-
106
- test("preserves profile and repeated config overrides", () => {
107
- const parsed = parseHeadlessExecArgs([
108
- "--benchmark-diagnostics",
109
- "--skip-git-repo-check",
110
- "--profile",
111
- "bench",
112
- "--model",
113
- "gpt-5.4-mini",
114
- "-c",
115
- "model_reasoning_effort=medium",
116
- "--config",
117
- "sandbox_mode=\"workspace-write\"",
118
- "--config=approval_policy=\"never\"",
119
- "Print",
120
- "files",
121
- ]);
122
-
123
- assert.equal(parsed.ok, true);
124
- if (!parsed.ok) return;
125
- assert.equal(parsed.value.benchmarkDiagnostics, true);
126
- assert.equal(parsed.value.timing, true);
127
- assert.equal(parsed.value.prompt, "Print files");
128
- assert.equal(parsed.value.launchArgs.profile, "bench");
129
- assert.deepEqual(parsed.value.launchArgs.configOverrides, [
130
- "model=\"gpt-5.4-mini\"",
131
- "model_reasoning_effort=medium",
132
- "sandbox_mode=\"workspace-write\"",
133
- "approval_policy=\"never\"",
134
- ]);
135
- assert.deepEqual(parsed.value.launchArgs.passthroughArgs, [
136
- "--skip-git-repo-check",
137
- "--profile",
138
- "bench",
139
- "--model",
140
- "gpt-5.4-mini",
141
- "-c",
142
- "model_reasoning_effort=medium",
143
- "--config",
144
- "sandbox_mode=\"workspace-write\"",
145
- "--config=approval_policy=\"never\"",
146
- ]);
147
- });
@@ -1,434 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import test from "node:test";
3
- import {
4
- HEADLESS_EXEC_PROVIDER_UNAVAILABLE,
5
- HEADLESS_EXEC_RUN_FAILED,
6
- createHeadlessExecTiming,
7
- runHeadlessExec,
8
- type HeadlessExecIo,
9
- } from "./execRunner.js";
10
- import { normalizeRuntimeConfig, resolveRuntimeConfig, type ResolvedRuntimeConfig } from "../config/runtimeConfig.js";
11
- import type { LayeredConfigResult } from "../config/layeredConfig.js";
12
- import type { LaunchArgs } from "../config/launchArgs.js";
13
- import type { BackendProvider } from "../core/providers/types.js";
14
-
15
- function createLaunchArgs(): LaunchArgs {
16
- return {
17
- help: false,
18
- version: false,
19
- initialPrompt: "Prompt",
20
- profile: null,
21
- configOverrides: [],
22
- passthroughArgs: [],
23
- modelOverride: null,
24
- };
25
- }
26
-
27
- function createIo(): HeadlessExecIo & { stdoutText: () => string; stderrText: () => string } {
28
- let stdout = "";
29
- let stderr = "";
30
- return {
31
- stdout: {
32
- write: (chunk: string) => {
33
- stdout += chunk;
34
- return true;
35
- },
36
- } as NodeJS.WriteStream,
37
- stderr: {
38
- write: (chunk: string) => {
39
- stderr += chunk;
40
- return true;
41
- },
42
- } as NodeJS.WriteStream,
43
- stdoutText: () => stdout,
44
- stderrText: () => stderr,
45
- };
46
- }
47
-
48
- function createLayeredConfig(): LayeredConfigResult {
49
- return {
50
- runtime: normalizeRuntimeConfig({
51
- provider: "codex-subprocess",
52
- model: "gpt-5.4-mini",
53
- reasoningLevel: "medium",
54
- mode: "full-auto",
55
- planMode: true,
56
- policy: {
57
- approvalPolicy: "never",
58
- sandboxMode: "danger-full-access",
59
- networkAccess: "enabled",
60
- writableRoots: ["C:/Repo/extra"],
61
- serviceTier: "fast",
62
- personality: "pragmatic",
63
- },
64
- }),
65
- diagnostics: {
66
- projectRoot: "C:\\Repo",
67
- projectTrusted: true,
68
- selectedProfile: null,
69
- selectedProfileSource: null,
70
- cliOverrides: [],
71
- layers: [{ label: "test", status: "loaded" }],
72
- ignoredEntries: [],
73
- fieldSources: {
74
- provider: "test",
75
- model: "test",
76
- reasoningLevel: "test",
77
- mode: "test",
78
- planMode: "test",
79
- geminiCommandPath: "test",
80
- "policy.approvalPolicy": "test",
81
- "policy.sandboxMode": "test",
82
- "policy.networkAccess": "test",
83
- "policy.writableRoots": "test",
84
- "policy.serviceTier": "test",
85
- "policy.personality": "test",
86
- },
87
- },
88
- };
89
- }
90
-
91
- function createProvider(run: BackendProvider["run"]): BackendProvider {
92
- return {
93
- id: "codex-subprocess",
94
- label: "Mock Codex",
95
- description: "Mock provider",
96
- authState: "delegated",
97
- authLabel: "Mock",
98
- statusMessage: "Mock",
99
- supportsModels: () => true,
100
- run,
101
- };
102
- }
103
-
104
- test("streams assistant deltas to stdout and progress/tool/error diagnostics to stderr", async () => {
105
- const io = createIo();
106
- const provider = createProvider((_prompt, _options, handlers) => {
107
- handlers.onProgress?.({ id: "p1", source: "reasoning", text: "thinking" });
108
- handlers.onToolActivity?.({
109
- id: "tool-1",
110
- command: "pwd",
111
- status: "running",
112
- startedAt: Date.now(),
113
- });
114
- handlers.onAssistantDelta?.("Hello ");
115
- handlers.onAssistantDelta?.("world");
116
- handlers.onResponse("Hello world");
117
- return () => {};
118
- });
119
-
120
- const result = await runHeadlessExec(
121
- { prompt: "Prompt", launchArgs: createLaunchArgs(), workspaceRoot: "C:\\Repo" },
122
- io,
123
- {
124
- resolveLayeredConfig: () => createLayeredConfig(),
125
- getBackendProvider: () => provider,
126
- loadProjectInstructions: () => ({ status: "missing" }),
127
- },
128
- );
129
-
130
- assert.equal(result.exitCode, 0);
131
- assert.equal(io.stdoutText(), "Hello world");
132
- assert.match(io.stderrText(), /startup:/);
133
- assert.match(io.stderrText(), /reasoning: thinking/);
134
- assert.match(io.stderrText(), /tool: running: pwd/);
135
- });
136
-
137
- test("emits final_answer_observed timing before provider response settles", async () => {
138
- const io = createIo();
139
- const provider = createProvider((_prompt, _options, handlers) => {
140
- handlers.onAssistantDelta?.("READY");
141
- handlers.onFinalAnswerObserved?.("READY");
142
- handlers.onResponse("READY");
143
- return () => {};
144
- });
145
-
146
- const result = await runHeadlessExec(
147
- {
148
- prompt: "Prompt",
149
- launchArgs: createLaunchArgs(),
150
- workspaceRoot: "C:\\Repo",
151
- benchmarkDiagnostics: createHeadlessExecTiming({
152
- enabled: true,
153
- stderr: io.stderr,
154
- startTimeMs: Date.now(),
155
- }),
156
- },
157
- io,
158
- {
159
- resolveLayeredConfig: () => createLayeredConfig(),
160
- getBackendProvider: () => provider,
161
- loadProjectInstructions: () => ({ status: "missing" }),
162
- },
163
- );
164
-
165
- assert.equal(result.exitCode, 0);
166
- assert.match(io.stderrText(), /final_answer_observed/);
167
- assert.match(io.stderrText(), /\[codexa exec timing\]/);
168
- assert.match(io.stderrText(), /final_answer_character_count=5/);
169
- });
170
-
171
- test("timing is silent when disabled", async () => {
172
- const io = createIo();
173
- const provider = createProvider((_prompt, _options, handlers) => {
174
- handlers.onResponse("ok");
175
- return () => {};
176
- });
177
-
178
- const result = await runHeadlessExec(
179
- { prompt: "Prompt", launchArgs: createLaunchArgs(), workspaceRoot: "C:\\Repo" },
180
- io,
181
- {
182
- resolveLayeredConfig: () => createLayeredConfig(),
183
- getBackendProvider: () => provider,
184
- loadProjectInstructions: () => ({ status: "missing" }),
185
- },
186
- );
187
-
188
- assert.equal(result.exitCode, 0);
189
- assert.doesNotMatch(io.stderrText(), /\[codexa exec timing\]/);
190
- });
191
-
192
- test("raw prompt policy sends the exact prompt and skips project instructions", async () => {
193
- const io = createIo();
194
- const captured: Array<{ prompt: string; promptPolicy: string | undefined; projectInstructions: unknown }> = [];
195
- let projectInstructionLoadCount = 0;
196
- const provider = createProvider((prompt, options, handlers) => {
197
- captured.push({
198
- prompt,
199
- promptPolicy: options.promptPolicy,
200
- projectInstructions: options.projectInstructions,
201
- });
202
- handlers.onResponse("ok");
203
- return () => {};
204
- });
205
-
206
- const result = await runHeadlessExec(
207
- {
208
- prompt: "Reply with exactly: CODEXA_READY",
209
- launchArgs: createLaunchArgs(),
210
- workspaceRoot: "C:\\Repo",
211
- },
212
- io,
213
- {
214
- resolveLayeredConfig: () => createLayeredConfig(),
215
- getBackendProvider: () => provider,
216
- loadProjectInstructions: () => {
217
- projectInstructionLoadCount += 1;
218
- return {
219
- status: "loaded",
220
- instructions: { path: "C:\\Repo\\AGENTS.md", content: "extra instructions" },
221
- };
222
- },
223
- },
224
- );
225
-
226
- assert.equal(result.exitCode, 0);
227
- assert.equal(projectInstructionLoadCount, 0);
228
- assert.equal(captured[0]?.prompt, "Reply with exactly: CODEXA_READY");
229
- assert.equal(captured[0]?.promptPolicy, "raw");
230
- assert.equal(captured[0]?.projectInstructions, null);
231
- });
232
-
233
- test("wrapped prompt policy loads project instructions for compatibility", async () => {
234
- const io = createIo();
235
- let capturedProjectInstructions: unknown = null;
236
- const provider = createProvider((_prompt, options, handlers) => {
237
- capturedProjectInstructions = options.projectInstructions;
238
- handlers.onResponse("ok");
239
- return () => {};
240
- });
241
-
242
- const result = await runHeadlessExec(
243
- {
244
- prompt: "Prompt",
245
- launchArgs: createLaunchArgs(),
246
- workspaceRoot: "C:\\Repo",
247
- promptPolicy: "wrapped",
248
- },
249
- io,
250
- {
251
- resolveLayeredConfig: () => createLayeredConfig(),
252
- getBackendProvider: () => provider,
253
- loadProjectInstructions: () => ({
254
- status: "loaded",
255
- instructions: { path: "C:\\Repo\\AGENTS.md", content: "extra instructions" },
256
- }),
257
- },
258
- );
259
-
260
- assert.equal(result.exitCode, 0);
261
- assert.deepEqual(capturedProjectInstructions, {
262
- path: "C:\\Repo\\AGENTS.md",
263
- content: "extra instructions",
264
- });
265
- });
266
-
267
- test("does not duplicate equivalent tool activity as progress", async () => {
268
- const io = createIo();
269
- const provider = createProvider((_prompt, _options, handlers) => {
270
- handlers.onToolActivity?.({
271
- id: "tool-1",
272
- command: "Get-ChildItem",
273
- status: "running",
274
- startedAt: Date.now(),
275
- });
276
- handlers.onProgress?.({ id: "tool-1", source: "tool", text: "Running Get-ChildItem" });
277
- handlers.onResponse("ok");
278
- return () => {};
279
- });
280
-
281
- const result = await runHeadlessExec(
282
- { prompt: "Prompt", launchArgs: createLaunchArgs(), workspaceRoot: "C:\\Repo" },
283
- io,
284
- {
285
- resolveLayeredConfig: () => createLayeredConfig(),
286
- getBackendProvider: () => provider,
287
- loadProjectInstructions: () => ({ status: "missing" }),
288
- },
289
- );
290
-
291
- assert.equal(result.exitCode, 0);
292
- assert.match(io.stderrText(), /tool: running: Get-ChildItem/);
293
- assert.doesNotMatch(io.stderrText(), /tool: Running Get-ChildItem/);
294
- });
295
-
296
- test("returns exit code 0 on provider success without deltas", async () => {
297
- const io = createIo();
298
- const provider = createProvider((_prompt, _options, handlers) => {
299
- handlers.onResponse("Final answer");
300
- return () => {};
301
- });
302
-
303
- const result = await runHeadlessExec(
304
- { prompt: "Prompt", launchArgs: createLaunchArgs(), workspaceRoot: "C:\\Repo" },
305
- io,
306
- {
307
- resolveLayeredConfig: () => createLayeredConfig(),
308
- getBackendProvider: () => provider,
309
- loadProjectInstructions: () => ({ status: "missing" }),
310
- },
311
- );
312
-
313
- assert.equal(result.exitCode, 0);
314
- assert.equal(io.stdoutText(), "Final answer");
315
- });
316
-
317
- test("keeps structured fallback events out of stdout", async () => {
318
- const io = createIo();
319
- const provider = createProvider((_prompt, _options, handlers) => {
320
- handlers.onAssistantDelta?.("{\"type\":\"item.completed\",\"item\":{\"type\":\"agent_message\",\"text\":\"raw\"}}\n");
321
- handlers.onAssistantDelta?.("Actual answer");
322
- handlers.onResponse("Actual answer");
323
- return () => {};
324
- });
325
-
326
- const result = await runHeadlessExec(
327
- { prompt: "Prompt", launchArgs: createLaunchArgs(), workspaceRoot: "C:\\Repo" },
328
- io,
329
- {
330
- resolveLayeredConfig: () => createLayeredConfig(),
331
- getBackendProvider: () => provider,
332
- loadProjectInstructions: () => ({ status: "missing" }),
333
- },
334
- );
335
-
336
- assert.equal(result.exitCode, 0);
337
- assert.equal(io.stdoutText(), "Actual answer");
338
- });
339
-
340
- test("returns non-zero when provider is unavailable", async () => {
341
- const io = createIo();
342
- const provider = createProvider(undefined);
343
-
344
- const result = await runHeadlessExec(
345
- { prompt: "Prompt", launchArgs: createLaunchArgs(), workspaceRoot: "C:\\Repo" },
346
- io,
347
- {
348
- resolveLayeredConfig: () => createLayeredConfig(),
349
- getBackendProvider: () => provider,
350
- loadProjectInstructions: () => ({ status: "missing" }),
351
- },
352
- );
353
-
354
- assert.equal(result.exitCode, HEADLESS_EXEC_PROVIDER_UNAVAILABLE);
355
- assert.match(io.stderrText(), /unavailable/i);
356
- });
357
-
358
- test("returns non-zero on provider error", async () => {
359
- const io = createIo();
360
- const provider = createProvider((_prompt, _options, handlers) => {
361
- handlers.onError("Provider exploded");
362
- return () => {};
363
- });
364
-
365
- const result = await runHeadlessExec(
366
- { prompt: "Prompt", launchArgs: createLaunchArgs(), workspaceRoot: "C:\\Repo" },
367
- io,
368
- {
369
- resolveLayeredConfig: () => createLayeredConfig(),
370
- getBackendProvider: () => provider,
371
- loadProjectInstructions: () => ({ status: "missing" }),
372
- },
373
- );
374
-
375
- assert.equal(result.exitCode, HEADLESS_EXEC_RUN_FAILED);
376
- assert.match(io.stderrText(), /Provider exploded/);
377
- });
378
-
379
- test("missing-file failure remains non-zero and visible", async () => {
380
- const io = createIo();
381
- const provider = createProvider((_prompt, _options, handlers) => {
382
- handlers.onError("Process exited with code 1", "Get-Content: Cannot find path 'missing.txt' because it does not exist.");
383
- return () => {};
384
- });
385
-
386
- const result = await runHeadlessExec(
387
- { prompt: "Read missing.txt", launchArgs: createLaunchArgs(), workspaceRoot: "C:\\Repo" },
388
- io,
389
- {
390
- resolveLayeredConfig: () => createLayeredConfig(),
391
- getBackendProvider: () => provider,
392
- loadProjectInstructions: () => ({ status: "missing" }),
393
- },
394
- );
395
-
396
- assert.equal(result.exitCode, HEADLESS_EXEC_RUN_FAILED);
397
- assert.match(io.stderrText(), /missing\.txt/);
398
- assert.match(io.stderrText(), /does not exist/);
399
- });
400
-
401
- test("forces planMode off while preserving other runtime settings", async () => {
402
- const io = createIo();
403
- const capturedRuntimes: ResolvedRuntimeConfig[] = [];
404
- const provider = createProvider((_prompt, options, handlers) => {
405
- capturedRuntimes.push(options.runtime);
406
- handlers.onResponse("ok");
407
- return () => {};
408
- });
409
-
410
- const result = await runHeadlessExec(
411
- { prompt: "Prompt", launchArgs: createLaunchArgs(), workspaceRoot: "C:\\Repo" },
412
- io,
413
- {
414
- resolveLayeredConfig: () => createLayeredConfig(),
415
- resolveRuntimeConfig,
416
- getBackendProvider: () => provider,
417
- loadProjectInstructions: () => ({ status: "missing" }),
418
- },
419
- );
420
-
421
- assert.equal(result.exitCode, 0);
422
- const capturedRuntime = capturedRuntimes[0];
423
- assert.ok(capturedRuntime);
424
- assert.equal(capturedRuntime.planMode, false);
425
- assert.equal(capturedRuntime.model, "gpt-5.4-mini");
426
- assert.equal(capturedRuntime.reasoningLevel, "medium");
427
- assert.equal(capturedRuntime.mode, "full-auto");
428
- assert.equal(capturedRuntime.policy.approvalPolicy, "never");
429
- assert.equal(capturedRuntime.policy.sandboxMode, "danger-full-access");
430
- assert.equal(capturedRuntime.policy.networkAccess, true);
431
- assert.deepEqual(capturedRuntime.policy.writableRoots, ["C:\\Repo\\extra"]);
432
- assert.equal(capturedRuntime.policy.serviceTier, "fast");
433
- assert.equal(capturedRuntime.policy.personality, "pragmatic");
434
- });