@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,205 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+ import { resolveRuntimeConfig, normalizeRuntimeConfig } from "../config/runtimeConfig.js";
4
+ import { prepareCodexExecLaunch } from "./codexLaunch.js";
5
+ import type { CodexCliCapabilities } from "./models/codexCapabilities.js";
6
+
7
+ test("prepares a shared launch plan with resolved executable strategy and source metadata", async () => {
8
+ const capabilities: CodexCliCapabilities = {
9
+ askForApproval: false,
10
+ sandbox: true,
11
+ config: true,
12
+ fullAuto: true,
13
+ };
14
+
15
+ const result = await prepareCodexExecLaunch(
16
+ {
17
+ runtime: resolveRuntimeConfig(normalizeRuntimeConfig({
18
+ model: "gpt-5.4",
19
+ reasoningLevel: "medium",
20
+ policy: {
21
+ approvalPolicy: "on-request",
22
+ sandboxMode: "workspace-write",
23
+ },
24
+ })),
25
+ cwd: "C:/repo",
26
+ structuredOutput: false,
27
+ probeCapabilities: true,
28
+ },
29
+ "file:///C:/project/src/core/providers/codexSubprocess.ts",
30
+ {
31
+ resolveExecutable: async () => "C:/tools/codex.cmd",
32
+ getCapabilities: async () => capabilities,
33
+ },
34
+ );
35
+
36
+ assert.deepEqual(result, {
37
+ ok: true,
38
+ strategy: "config-overrides",
39
+ executable: "C:/tools/codex.cmd",
40
+ capabilities,
41
+ responsibleModulePath: "C:\\project\\src\\core\\providers\\codexSubprocess.ts",
42
+ responsibleModuleKind: "src",
43
+ launchContext: {
44
+ launchKind: process.env.CODEXA_LAUNCH_KIND,
45
+ packageRoot: process.env.CODEXA_PACKAGE_ROOT,
46
+ launcherScript: process.env.CODEXA_LAUNCHER_SCRIPT,
47
+ },
48
+ args: [
49
+ "exec",
50
+ "--skip-git-repo-check",
51
+ "--cd",
52
+ "C:/repo",
53
+ "--model",
54
+ "gpt-5.4",
55
+ "--config",
56
+ "model_reasoning_effort=medium",
57
+ "-c",
58
+ "approval_policy=on-request",
59
+ "--sandbox",
60
+ "workspace-write",
61
+ "-",
62
+ ],
63
+ });
64
+ });
65
+
66
+ test("launch diagnostics are injectable and silent by default", async () => {
67
+ const previousDebug = process.env.CODEXA_DEBUG_CODEX_LAUNCH;
68
+ process.env.CODEXA_DEBUG_CODEX_LAUNCH = "1";
69
+ const capabilities: CodexCliCapabilities = {
70
+ askForApproval: false,
71
+ sandbox: true,
72
+ config: true,
73
+ fullAuto: true,
74
+ };
75
+
76
+ try {
77
+ const silent = await prepareCodexExecLaunch(
78
+ {
79
+ runtime: resolveRuntimeConfig(normalizeRuntimeConfig({
80
+ model: "gpt-5.4",
81
+ reasoningLevel: "medium",
82
+ policy: {
83
+ approvalPolicy: "on-request",
84
+ sandboxMode: "workspace-write",
85
+ },
86
+ })),
87
+ cwd: "C:/repo",
88
+ structuredOutput: true,
89
+ },
90
+ "file:///C:/repo/src/core/providers/codexSubprocess.ts",
91
+ {
92
+ resolveExecutable: async () => "C:/tools/codex.cmd",
93
+ getCapabilities: async () => capabilities,
94
+ },
95
+ );
96
+ assert.equal(silent.ok, true);
97
+
98
+ const diagnostics: string[] = [];
99
+ await prepareCodexExecLaunch(
100
+ {
101
+ runtime: resolveRuntimeConfig(normalizeRuntimeConfig({
102
+ model: "gpt-5.4",
103
+ reasoningLevel: "medium",
104
+ policy: {
105
+ approvalPolicy: "on-request",
106
+ sandboxMode: "workspace-write",
107
+ },
108
+ })),
109
+ cwd: "C:/repo",
110
+ structuredOutput: true,
111
+ },
112
+ "file:///C:/repo/src/core/providers/codexSubprocess.ts",
113
+ {
114
+ resolveExecutable: async () => "C:/tools/codex.cmd",
115
+ getCapabilities: async () => capabilities,
116
+ diagnosticsLogger: (message) => diagnostics.push(message),
117
+ },
118
+ );
119
+
120
+ assert.equal(diagnostics.length, 1);
121
+ assert.match(diagnostics[0]!, /\[codexa\] codex launch debug/);
122
+ } finally {
123
+ if (previousDebug == null) {
124
+ delete process.env.CODEXA_DEBUG_CODEX_LAUNCH;
125
+ } else {
126
+ process.env.CODEXA_DEBUG_CODEX_LAUNCH = previousDebug;
127
+ }
128
+ }
129
+ });
130
+
131
+ test("uses modern Codex capabilities by default without probing help output", async () => {
132
+ let capabilityProbeCount = 0;
133
+
134
+ const result = await prepareCodexExecLaunch(
135
+ {
136
+ runtime: resolveRuntimeConfig(normalizeRuntimeConfig({
137
+ model: "gpt-5.4-mini",
138
+ reasoningLevel: "medium",
139
+ policy: {
140
+ approvalPolicy: "never",
141
+ sandboxMode: "danger-full-access",
142
+ },
143
+ })),
144
+ cwd: "C:/repo",
145
+ structuredOutput: true,
146
+ },
147
+ "file:///C:/repo/src/core/providers/codexSubprocess.ts",
148
+ {
149
+ resolveExecutable: async () => "codex.cmd",
150
+ getCapabilities: async () => {
151
+ capabilityProbeCount += 1;
152
+ throw new Error("capability probe should not run");
153
+ },
154
+ },
155
+ );
156
+
157
+ assert.equal(result.ok, true);
158
+ assert.equal(capabilityProbeCount, 0);
159
+ });
160
+
161
+ test("can explicitly probe capabilities for compatibility fallback", async () => {
162
+ let capabilityProbeCount = 0;
163
+ const capabilities: CodexCliCapabilities = {
164
+ askForApproval: false,
165
+ sandbox: false,
166
+ config: true,
167
+ fullAuto: false,
168
+ };
169
+
170
+ const result = await prepareCodexExecLaunch(
171
+ {
172
+ runtime: resolveRuntimeConfig(normalizeRuntimeConfig({
173
+ model: "gpt-5.4-mini",
174
+ reasoningLevel: "medium",
175
+ policy: {
176
+ approvalPolicy: "on-request",
177
+ sandboxMode: "workspace-write",
178
+ },
179
+ })),
180
+ cwd: "C:/repo",
181
+ structuredOutput: false,
182
+ probeCapabilities: true,
183
+ },
184
+ "file:///C:/repo/src/core/providers/codexSubprocess.ts",
185
+ {
186
+ resolveExecutable: async () => "codex.cmd",
187
+ getCapabilities: async () => {
188
+ capabilityProbeCount += 1;
189
+ return capabilities;
190
+ },
191
+ },
192
+ );
193
+
194
+ assert.equal(result.ok, true);
195
+ assert.equal(capabilityProbeCount, 1);
196
+ if (result.ok) {
197
+ assert.deepEqual(result.args.slice(-5), [
198
+ "-c",
199
+ "approval_policy=on-request",
200
+ "-c",
201
+ "sandbox_mode=workspace-write",
202
+ "-",
203
+ ]);
204
+ }
205
+ });
@@ -0,0 +1,162 @@
1
+ import { fileURLToPath } from "url";
2
+ import { buildCodexExecArgs, type BuildCodexExecArgsOptions, type BuildCodexExecArgsResult } from "./codexExecArgs.js";
3
+ import { getCodexCliCapabilities, type CodexCliCapabilities } from "./models/codexCapabilities.js";
4
+ import { resolveCodexExecutable } from "./executables/codexExecutable.js";
5
+ import * as perf from "./perf/profiler.js";
6
+
7
+ // Assumed capability set when probeCapabilities is false — avoids a slow help-output probe on every run.
8
+ const MODERN_CODEX_CLI_CAPABILITIES: CodexCliCapabilities = {
9
+ askForApproval: false,
10
+ sandbox: false,
11
+ config: true,
12
+ fullAuto: false,
13
+ };
14
+
15
+ export interface PreparedCodexExecLaunch {
16
+ executable: string;
17
+ capabilities: CodexCliCapabilities;
18
+ args: string[];
19
+ strategy: BuildCodexExecArgsResult["strategy"];
20
+ responsibleModulePath: string;
21
+ responsibleModuleKind: "src" | "built-artifact" | "other";
22
+ launchContext: {
23
+ launchKind?: string;
24
+ packageRoot?: string;
25
+ launcherScript?: string;
26
+ };
27
+ }
28
+
29
+ interface PrepareCodexExecLaunchDependencies {
30
+ resolveExecutable?: typeof resolveCodexExecutable;
31
+ getCapabilities?: typeof getCodexCliCapabilities;
32
+ diagnosticsLogger?: (message: string) => void;
33
+ }
34
+
35
+ export interface PrepareCodexExecLaunchOptions extends BuildCodexExecArgsOptions {
36
+ probeCapabilities?: boolean;
37
+ codexCommandPath?: string | null;
38
+ }
39
+
40
+ function resolveResponsibleModulePath(moduleUrl: string): string {
41
+ if (moduleUrl.startsWith("file:")) {
42
+ const windowsMatch = moduleUrl.match(/^file:\/\/\/([A-Za-z]:\/.*)/);
43
+ if (windowsMatch) {
44
+ return windowsMatch[1].replace(/\//g, "\\");
45
+ }
46
+ return fileURLToPath(moduleUrl);
47
+ }
48
+
49
+ return moduleUrl;
50
+ }
51
+
52
+ function classifyResponsibleModule(modulePath: string): "src" | "built-artifact" | "other" {
53
+ if (/[\\/]src[\\/]/i.test(modulePath)) {
54
+ return "src";
55
+ }
56
+
57
+ if (/[\\/](dist|build|out|lib)[\\/]/i.test(modulePath)) {
58
+ return "built-artifact";
59
+ }
60
+
61
+ return "other";
62
+ }
63
+
64
+ function shouldLogCodexLaunchDiagnostics(): boolean {
65
+ return process.env.CODEXA_DEBUG_CODEX_LAUNCH === "1";
66
+ }
67
+
68
+ function logCodexLaunchDiagnostics(
69
+ options: BuildCodexExecArgsOptions,
70
+ prepared: PreparedCodexExecLaunch,
71
+ logger: ((message: string) => void) | undefined,
72
+ ): void {
73
+ if (!logger || !shouldLogCodexLaunchDiagnostics()) {
74
+ return;
75
+ }
76
+
77
+ const { capabilities, launchContext } = prepared;
78
+ const debugLines = [
79
+ "[codexa] codex launch debug",
80
+ ` responsible module: ${prepared.responsibleModulePath} (${prepared.responsibleModuleKind})`,
81
+ ` launch kind: ${launchContext.launchKind ?? "unknown"}`,
82
+ ` package root: ${launchContext.packageRoot ?? "unknown"}`,
83
+ ` launcher script: ${launchContext.launcherScript ?? "unknown"}`,
84
+ ` resolved executable: ${prepared.executable}`,
85
+ " capabilities:",
86
+ ` askForApproval=${capabilities.askForApproval}`,
87
+ ` sandbox=${capabilities.sandbox}`,
88
+ ` config=${capabilities.config}`,
89
+ ` fullAuto=${capabilities.fullAuto}`,
90
+ ` chosen strategy: ${prepared.strategy}`,
91
+ ` structured output: ${options.structuredOutput ?? true}`,
92
+ ` runtime model: ${options.runtime.model}`,
93
+ ` runtime mode: ${options.runtime.mode}`,
94
+ ` final argv: ${JSON.stringify(prepared.args)}`,
95
+ ];
96
+
97
+ logger(debugLines.join("\n"));
98
+ }
99
+
100
+ export async function prepareCodexExecLaunch(
101
+ options: PrepareCodexExecLaunchOptions,
102
+ responsibleModuleUrl: string,
103
+ dependencies: PrepareCodexExecLaunchDependencies = {},
104
+ ): Promise<BuildCodexExecArgsResult & {
105
+ executable?: string;
106
+ capabilities?: CodexCliCapabilities;
107
+ responsibleModulePath?: string;
108
+ responsibleModuleKind?: PreparedCodexExecLaunch["responsibleModuleKind"];
109
+ launchContext?: PreparedCodexExecLaunch["launchContext"];
110
+ }> {
111
+ const executableResolver = dependencies.resolveExecutable ?? resolveCodexExecutable;
112
+ const capabilityResolver = dependencies.getCapabilities ?? getCodexCliCapabilities;
113
+ const diagnosticsLogger = dependencies.diagnosticsLogger;
114
+ perf.mark("exec_resolve_start");
115
+ const executable = await executableResolver({ configuredPath: options.codexCommandPath });
116
+ perf.mark("exec_resolve_end");
117
+ perf.mark("caps_probe_start");
118
+ const capabilities = options.probeCapabilities
119
+ ? await capabilityResolver(executable)
120
+ : MODERN_CODEX_CLI_CAPABILITIES;
121
+ perf.mark("caps_probe_end");
122
+ const argsResult = buildCodexExecArgs(options, capabilities);
123
+ const responsibleModulePath = resolveResponsibleModulePath(responsibleModuleUrl);
124
+ const responsibleModuleKind = classifyResponsibleModule(responsibleModulePath);
125
+ const launchContext = {
126
+ launchKind: process.env.CODEXA_LAUNCH_KIND,
127
+ packageRoot: process.env.CODEXA_PACKAGE_ROOT,
128
+ launcherScript: process.env.CODEXA_LAUNCHER_SCRIPT,
129
+ };
130
+
131
+ if (!argsResult.ok) {
132
+ return {
133
+ ...argsResult,
134
+ executable,
135
+ capabilities,
136
+ responsibleModulePath,
137
+ responsibleModuleKind,
138
+ launchContext,
139
+ };
140
+ }
141
+
142
+ const prepared: PreparedCodexExecLaunch = {
143
+ executable,
144
+ capabilities,
145
+ args: argsResult.args,
146
+ strategy: argsResult.strategy,
147
+ responsibleModulePath,
148
+ responsibleModuleKind,
149
+ launchContext,
150
+ };
151
+
152
+ logCodexLaunchDiagnostics(options, prepared, diagnosticsLogger);
153
+
154
+ return {
155
+ ...argsResult,
156
+ executable: prepared.executable,
157
+ capabilities: prepared.capabilities,
158
+ responsibleModulePath: prepared.responsibleModulePath,
159
+ responsibleModuleKind: prepared.responsibleModuleKind,
160
+ launchContext: prepared.launchContext,
161
+ };
162
+ }
@@ -0,0 +1,252 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+ import {
4
+ buildCodexPrompt,
5
+ buildPlanExecutionPrompt,
6
+ buildPlanningPrompt,
7
+ detectHollowResponse,
8
+ isClearlySafeGeneratedCleanupRequest,
9
+ promptHasWriteIntent,
10
+ resolveExecutionMode,
11
+ } from "./codexPrompt.js";
12
+
13
+ const readOnlyPolicy = {
14
+ approvalPolicy: "untrusted",
15
+ sandboxMode: "read-only",
16
+ } as const;
17
+
18
+ const writePolicy = {
19
+ approvalPolicy: "on-request",
20
+ sandboxMode: "workspace-write",
21
+ } as const;
22
+
23
+ test("detects write intent for build requests", () => {
24
+ assert.equal(promptHasWriteIntent("Create a weather app script with tests"), true);
25
+ });
26
+
27
+ test("does not force write mode for general questions", () => {
28
+ assert.equal(promptHasWriteIntent("Explain how array sorting works"), false);
29
+ });
30
+
31
+ test("detects write intent for generated cleanup requests", () => {
32
+ assert.equal(promptHasWriteIntent("Delete only clearly safe generated files and folders"), true);
33
+ });
34
+
35
+ test("detects narrow generated cleanup requests", () => {
36
+ assert.equal(isClearlySafeGeneratedCleanupRequest("Delete only clearly safe generated files and folders"), true);
37
+ assert.equal(isClearlySafeGeneratedCleanupRequest("Prune build artifacts and caches"), true);
38
+ });
39
+
40
+ test("does not fast-path broad destructive cleanup requests", () => {
41
+ assert.equal(isClearlySafeGeneratedCleanupRequest("delete everything"), false);
42
+ assert.equal(isClearlySafeGeneratedCleanupRequest("remove all files"), false);
43
+ assert.equal(isClearlySafeGeneratedCleanupRequest("wipe the workspace"), false);
44
+ });
45
+
46
+ test("auto-upgrades suggest mode for editing prompts", () => {
47
+ assert.deepEqual(resolveExecutionMode("suggest", "Create a new CLI script and tests"), {
48
+ mode: "auto-edit",
49
+ autoUpgraded: true,
50
+ });
51
+ });
52
+
53
+ test("auto-upgrades suggest mode for generated cleanup prompts", () => {
54
+ assert.deepEqual(resolveExecutionMode("suggest", "Delete only clearly safe generated files and folders"), {
55
+ mode: "auto-edit",
56
+ autoUpgraded: true,
57
+ });
58
+ });
59
+
60
+ test("keeps explicit full-auto mode unchanged", () => {
61
+ assert.deepEqual(resolveExecutionMode("full-auto", "Create a new CLI script and tests"), {
62
+ mode: "full-auto",
63
+ autoUpgraded: false,
64
+ });
65
+ });
66
+
67
+ test("read-only runtime policy overrides write-capable modes", () => {
68
+ const prompt = buildCodexPrompt("Create a weather app script with tests", "auto-edit", readOnlyPolicy);
69
+ assert.match(prompt, /runtime permissions are read-only/i);
70
+ assert.doesNotMatch(prompt, /write access/i);
71
+ assert.doesNotMatch(prompt, /create or update files directly/i);
72
+ });
73
+
74
+ test("suggest mode stays advisory even with write-capable permissions", () => {
75
+ const prompt = buildCodexPrompt("Explain this code", "suggest", writePolicy);
76
+ assert.match(prompt, /permissions allow workspace edits/i);
77
+ assert.match(prompt, /still in suggest mode/i);
78
+ assert.match(prompt, /without making file changes/i);
79
+ });
80
+
81
+ test("plan mode injects plan-first instructions without changing suggest semantics", () => {
82
+ const prompt = buildCodexPrompt("Explain this code", "suggest", {
83
+ ...writePolicy,
84
+ planMode: true,
85
+ });
86
+ assert.match(prompt, /Planning mode is enabled for this session/i);
87
+ assert.match(prompt, /Start by giving a concise, repo-aware plan/i);
88
+ assert.match(prompt, /continue the task normally under the current mode and runtime permissions/i);
89
+ assert.match(prompt, /still in suggest mode/i);
90
+ assert.match(prompt, /without making file changes/i);
91
+ });
92
+
93
+ test("builds a write-enabled codex prompt for auto-edit when permissions allow it", () => {
94
+ const prompt = buildCodexPrompt("Create a weather app script with tests", "auto-edit", writePolicy);
95
+ assert.match(prompt, /write access/i);
96
+ assert.match(prompt, /create or update files directly/i);
97
+ assert.match(prompt, /best-effort continuation/i);
98
+ assert.match(prompt, /make the most reasonable assumption/i);
99
+ assert.match(prompt, /\[QUESTION\]:/i);
100
+ assert.match(prompt, /do not reply with generic readiness/i);
101
+ assert.match(prompt, /Task:/i);
102
+ });
103
+
104
+ test("codex prompts include terminal-native final answer guidance", () => {
105
+ const prompt = buildCodexPrompt("Explain this repo", "suggest", writePolicy);
106
+
107
+ assert.match(prompt, /Final answer style for this terminal UI/i);
108
+ assert.match(prompt, /short relative paths such as src\/App\.tsx/i);
109
+ assert.match(prompt, /Do not include absolute local paths like C:\/Users/i);
110
+ assert.match(prompt, /avoid web-style local Markdown links/i);
111
+ assert.match(prompt, /Answer simple questions simply/i);
112
+ });
113
+
114
+ test("injects project instructions before the task", () => {
115
+ const prompt = buildCodexPrompt("Explain this repo", "suggest", writePolicy, {
116
+ projectInstructions: {
117
+ path: "/workspace/AGENTS.md",
118
+ content: "Prefer small, focused changes.",
119
+ },
120
+ });
121
+
122
+ assert.match(prompt, /Project instructions:/i);
123
+ assert.match(prompt, /Loaded from: \/workspace\/AGENTS\.md/i);
124
+ assert.match(prompt, /Prefer small, focused changes\./i);
125
+ assert.ok(prompt.indexOf("Project instructions:") < prompt.indexOf("Task:"));
126
+ });
127
+
128
+ test("adds fast generated cleanup safety instructions for write-enabled cleanup prompts", () => {
129
+ const prompt = buildCodexPrompt("Delete only clearly safe generated files and folders", "auto-edit", writePolicy);
130
+ assert.match(prompt, /Fast generated-file cleanup guidance/i);
131
+ assert.match(prompt, /shallow workspace inspection/i);
132
+ assert.match(prompt, /generated artifacts/i);
133
+ assert.match(prompt, /Attempt each safe cleanup target once/i);
134
+ assert.match(prompt, /stop immediately and report the blocked path and cause/i);
135
+ assert.match(prompt, /Do not retry, force-delete/i);
136
+ assert.match(prompt, /Do not do branch, bootstrap, package install, or repo setup/i);
137
+ });
138
+
139
+ test("plan mode keeps write-enabled prompts actionable", () => {
140
+ const prompt = buildCodexPrompt("Create a weather app script with tests", "auto-edit", {
141
+ ...writePolicy,
142
+ planMode: true,
143
+ });
144
+ assert.match(prompt, /Planning mode is enabled for this session/i);
145
+ assert.match(prompt, /continue the task normally under the current mode and runtime permissions/i);
146
+ assert.match(prompt, /write access/i);
147
+ assert.match(prompt, /create or update files directly/i);
148
+ });
149
+
150
+ test("plan mode does not override read-only runtime guidance", () => {
151
+ const prompt = buildCodexPrompt("Create a weather app script with tests", "full-auto", {
152
+ ...readOnlyPolicy,
153
+ planMode: true,
154
+ });
155
+ assert.match(prompt, /Planning mode is enabled for this session/i);
156
+ assert.match(prompt, /runtime permissions are read-only/i);
157
+ assert.doesNotMatch(prompt, /write access/i);
158
+ });
159
+
160
+ test("builds a planning prompt that stays plan-only and includes constraints", () => {
161
+ const prompt = buildPlanningPrompt({
162
+ task: "Delete everything in hello.py and replace it with a starter hello_world.py script.",
163
+ constraints: ["Keep the change scoped to the current workspace."],
164
+ });
165
+
166
+ assert.match(prompt, /plan-only turn/i);
167
+ assert.match(prompt, /Do not implement the task/i);
168
+ assert.match(prompt, /Files, Steps, Assumptions, Risks/i);
169
+ assert.match(prompt, /Active constraints:/i);
170
+ assert.match(prompt, /Task:/i);
171
+ });
172
+
173
+ test("builds an approved-plan execution prompt that tells codexa to implement now", () => {
174
+ const prompt = buildPlanExecutionPrompt({
175
+ task: "Delete everything in hello.py and replace it with a starter hello_world.py script.",
176
+ approvedPlan: "## Files\n- hello.py\n- hello_world.py",
177
+ constraints: ["Keep it minimal."],
178
+ });
179
+
180
+ assert.match(prompt, /approved the following plan/i);
181
+ assert.match(prompt, /implement it now/i);
182
+ assert.match(prompt, /Do the work in the workspace instead of re-planning/i);
183
+ assert.match(prompt, /Approved plan:/i);
184
+ assert.match(prompt, /Additional constraints:/i);
185
+ });
186
+
187
+ // --- detectHollowResponse tests ---
188
+
189
+ test("detects greetings as hollow with kind=greeting", () => {
190
+ for (const greeting of ["Hello.", "Hi!", "Hey", "Sure.", "Okay", "Sounds good"]) {
191
+ const result = detectHollowResponse("create a file", greeting);
192
+ assert.equal(result.isHollow, true, `Expected "${greeting}" to be hollow`);
193
+ assert.equal(result.kind, "greeting");
194
+ }
195
+ });
196
+
197
+ test("detects filler acknowledgments as hollow with kind=filler", () => {
198
+ for (const filler of ["Thanks.", "Thank you!", "No problem", "Will do", "Noted"]) {
199
+ const result = detectHollowResponse("create a file", filler);
200
+ assert.equal(result.isHollow, true, `Expected "${filler}" to be hollow`);
201
+ assert.equal(result.kind, "filler");
202
+ }
203
+ });
204
+
205
+ test("detects clarification questions as hollow with kind=clarification", () => {
206
+ for (const question of ["Can you clarify?", "Could you specify the path?", "What do you mean?"]) {
207
+ const result = detectHollowResponse("create a file", question);
208
+ assert.equal(result.isHollow, true, `Expected "${question}" to be hollow`);
209
+ assert.equal(result.kind, "clarification");
210
+ }
211
+ });
212
+
213
+ test("detects empty/whitespace as hollow with kind=filler", () => {
214
+ assert.equal(detectHollowResponse("create a file", "").isHollow, true);
215
+ assert.equal(detectHollowResponse("create a file", " ").isHollow, true);
216
+ assert.equal(detectHollowResponse("create a file", "").kind, "filler");
217
+ });
218
+
219
+ test("detects short no-action responses for write-intent prompts", () => {
220
+ const result = detectHollowResponse("create a new script file", "Let me think about that.");
221
+ assert.equal(result.isHollow, true);
222
+ assert.equal(result.kind, "short-no-action");
223
+ });
224
+
225
+ test("does not flag valid short responses with action confirmation", () => {
226
+ const result = detectHollowResponse("create a file", "Done, created the file.");
227
+ assert.equal(result.isHollow, false);
228
+ assert.equal(result.kind, "none");
229
+ });
230
+
231
+ test("does not flag longer task completions", () => {
232
+ const longResponse = "I've created the file at src/utils/helper.ts with the requested utility functions. The module exports three helpers for string manipulation.";
233
+ const result = detectHollowResponse("create a utility file", longResponse);
234
+ assert.equal(result.isHollow, false);
235
+ });
236
+
237
+ test("does not flag responses containing code blocks", () => {
238
+ const result = detectHollowResponse("create a script", "Here:\n```js\nconsole.log('hi');\n```");
239
+ assert.equal(result.isHollow, false);
240
+ });
241
+
242
+ test("does not flag greetings when prompt has no write intent", () => {
243
+ for (const prompt of ["Hello", "Hi there", "Hey", "What's up"]) {
244
+ const result = detectHollowResponse(prompt, "Hello!");
245
+ assert.equal(result.isHollow, false, `Prompt "${prompt}" should not trigger hollow detection`);
246
+ }
247
+ });
248
+
249
+ test("does not flag filler when prompt is conversational", () => {
250
+ const result = detectHollowResponse("thanks for that", "You're welcome!");
251
+ assert.equal(result.isHollow, false);
252
+ });