@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,155 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import test from "node:test";
3
- import {
4
- DEFAULT_SHOW_BUSY_LOADER,
5
- DEFAULT_TERMINAL_TITLE_MODE,
6
- DEFAULT_WORKSPACE_DISPLAY_MODE,
7
- DEFAULT_MODE,
8
- USER_SETTING_DEFINITIONS,
9
- formatBusyLoaderSettingValue,
10
- formatModeLabel,
11
- formatTerminalTitleModeLabel,
12
- formatTerminalTitlePath,
13
- formatWorkspaceDisplayModeLabel,
14
- formatWorkspaceDisplayPath,
15
- getCodexConfigFile,
16
- getCodexHome,
17
- getCodexaTrustStoreFile,
18
- getNextMode,
19
- normalizeReasoningForModel,
20
- } from "./settings.js";
21
-
22
- test("keeps supported reasoning levels for gpt-5.4-mini", () => {
23
- assert.equal(normalizeReasoningForModel("gpt-5.4-mini", "high"), "high");
24
- });
25
-
26
- test("keeps reasoning unchanged for non-mini models", () => {
27
- assert.equal(normalizeReasoningForModel("gpt-5.4", "low"), "low");
28
- });
29
-
30
-
31
- test("formats codex-style mode labels", () => {
32
- assert.equal(formatModeLabel("suggest"), "Read-only");
33
- assert.equal(formatModeLabel("auto-edit"), "Auto");
34
- assert.equal(formatModeLabel("full-auto"), "Full Access");
35
- });
36
-
37
- test("cycles modes in the same order as Ctrl+Y", () => {
38
- assert.equal(getNextMode("suggest"), "auto-edit");
39
- assert.equal(getNextMode("auto-edit"), "full-auto");
40
- assert.equal(getNextMode("full-auto"), "suggest");
41
- });
42
-
43
- test("defaults to full-auto mode", () => {
44
- assert.equal(DEFAULT_MODE, "full-auto");
45
- });
46
-
47
- test("defaults workspace display mode and busy loader", () => {
48
- assert.equal(DEFAULT_WORKSPACE_DISPLAY_MODE, "dir");
49
- assert.equal(DEFAULT_TERMINAL_TITLE_MODE, "dir");
50
- assert.equal(DEFAULT_SHOW_BUSY_LOADER, true);
51
- assert.equal(formatWorkspaceDisplayModeLabel("dir"), "Dir");
52
- assert.equal(formatWorkspaceDisplayModeLabel("name"), "Name");
53
- assert.equal(formatWorkspaceDisplayModeLabel("simple"), "Simple");
54
- assert.equal(formatTerminalTitleModeLabel("dir"), "Dir");
55
- assert.equal(formatBusyLoaderSettingValue(true), "true");
56
- assert.equal(formatBusyLoaderSettingValue(false), "false");
57
- });
58
-
59
- test("defines user settings through reusable schemas", () => {
60
- assert.deepEqual(USER_SETTING_DEFINITIONS, [
61
- {
62
- key: "workspaceDisplayMode",
63
- label: "Workspace display",
64
- description: "Controls how the workspace label is displayed in the Codexa header.",
65
- options: [
66
- { value: "dir", label: "Dir" },
67
- { value: "name", label: "Name" },
68
- { value: "simple", label: "Simple" },
69
- ],
70
- },
71
- {
72
- key: "terminalTitleMode",
73
- label: "Terminal title",
74
- description: "Controls how the terminal tab/window title is displayed.",
75
- options: [
76
- { value: "dir", label: "Dir" },
77
- { value: "name", label: "Name" },
78
- { value: "simple", label: "Simple" },
79
- ],
80
- },
81
- {
82
- key: "showBusyLoader",
83
- label: "Busy loader",
84
- description: "Controls whether the footer shows a subtle loading animation while Codexa is busy.",
85
- options: [
86
- { value: "true", label: "True" },
87
- { value: "false", label: "False" },
88
- ],
89
- },
90
- {
91
- key: "terminalMouseMode",
92
- label: "Mouse mode",
93
- description:
94
- "Selection (default): no mouse tracking — native drag-select and native wheel scroll work unmodified. "
95
- + "Scroll history via native terminal scrollback. "
96
- + "Wheel: enables SGR mouse tracking so the Codexa timeline captures wheel events for in-app scroll. "
97
- + "Native drag-select then requires Shift (Windows Terminal) or equivalent modifier. "
98
- + "Run /mouse to toggle for the current session.",
99
- options: [
100
- { value: "selection", label: "Native selection" },
101
- { value: "wheel", label: "Wheel scroll" },
102
- ],
103
- },
104
- ]);
105
- });
106
-
107
- test("formats workspace display paths without changing root semantics", () => {
108
- assert.equal(formatWorkspaceDisplayPath("", "simple"), "");
109
- assert.equal(
110
- formatWorkspaceDisplayPath("C:\\Development\\1-JavaScript\\13-Custom CLI", "dir"),
111
- "13-Custom CLI",
112
- );
113
- assert.equal(
114
- formatWorkspaceDisplayPath("C:\\Development\\1-JavaScript\\13-Custom CLI", "name"),
115
- "Codexa",
116
- );
117
- assert.equal(
118
- formatWorkspaceDisplayPath("C:\\Development\\1-JavaScript\\13-Custom CLI", "simple"),
119
- "13-Custom CLI",
120
- );
121
- assert.equal(formatWorkspaceDisplayPath("C:\\", "simple"), "C:\\");
122
- assert.equal(formatWorkspaceDisplayPath("C:\\Workspace\\", "simple"), "Workspace");
123
- });
124
-
125
- test("formats terminal title labels with the same workspace semantics", () => {
126
- assert.equal(
127
- formatTerminalTitlePath("C:\\Development\\1-JavaScript\\13-Custom-CLI-Normal", "dir"),
128
- "13-Custom-CLI-Normal",
129
- );
130
- assert.equal(
131
- formatTerminalTitlePath("C:\\Development\\1-JavaScript\\13-Custom-CLI-Normal", "name"),
132
- "Codexa",
133
- );
134
- assert.equal(
135
- formatTerminalTitlePath("C:\\Development\\1-JavaScript\\13-Custom-CLI-Normal", "simple"),
136
- "Codexa",
137
- );
138
- });
139
-
140
- test("resolves CODEX_HOME-derived paths from the live environment", () => {
141
- const previousCodexHome = process.env.CODEX_HOME;
142
- process.env.CODEX_HOME = "C:\\Temp\\codex-home";
143
-
144
- try {
145
- assert.equal(getCodexHome(), "C:\\Temp\\codex-home");
146
- assert.equal(getCodexConfigFile(), "C:\\Temp\\codex-home\\config.toml");
147
- assert.equal(getCodexaTrustStoreFile(), "C:\\Temp\\codex-home\\codexa-trust.json");
148
- } finally {
149
- if (previousCodexHome === undefined) {
150
- delete process.env.CODEX_HOME;
151
- } else {
152
- process.env.CODEX_HOME = previousCodexHome;
153
- }
154
- }
155
- });
@@ -1,29 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import { mkdtempSync, rmSync } from "fs";
3
- import { tmpdir } from "os";
4
- import { join } from "path";
5
- import test from "node:test";
6
-
7
- test("persists trusted project roots", async () => {
8
- const tempHome = mkdtempSync(join(tmpdir(), "codexa-trust-store-"));
9
- const previousCodexHome = process.env.CODEX_HOME;
10
- process.env.CODEX_HOME = tempHome;
11
-
12
- try {
13
- const module = await import(`./trustStore.js?trust=${Date.now()}`);
14
- const projectRoot = "C:/Workspace/Repo";
15
-
16
- assert.equal(module.isProjectTrusted(projectRoot), false);
17
- module.setProjectTrust(projectRoot, true);
18
- assert.equal(module.isProjectTrusted(projectRoot), true);
19
- module.setProjectTrust(projectRoot, false);
20
- assert.equal(module.isProjectTrusted(projectRoot), false);
21
- } finally {
22
- if (previousCodexHome === undefined) {
23
- delete process.env.CODEX_HOME;
24
- } else {
25
- process.env.CODEX_HOME = previousCodexHome;
26
- }
27
- rmSync(tempHome, { recursive: true, force: true });
28
- }
29
- });
@@ -1,155 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import test from "node:test";
3
- import path from "node:path";
4
- import { mkdir, writeFile, rm } from "node:fs/promises";
5
- import { tmpdir } from "node:os";
6
- import {
7
- isImageFile,
8
- resolveAttachmentDestPath,
9
- importExternalFile,
10
- rewritePromptWithImportedPaths,
11
- } from "./attachments.js";
12
-
13
- // ─── isImageFile ─────────────────────────────────────────────────────────────
14
-
15
- test("isImageFile returns true for .png", () => {
16
- assert.equal(isImageFile("screenshot.png"), true);
17
- });
18
-
19
- test("isImageFile returns true for .jpg", () => {
20
- assert.equal(isImageFile("photo.jpg"), true);
21
- });
22
-
23
- test("isImageFile returns true for .jpeg", () => {
24
- assert.equal(isImageFile("image.jpeg"), true);
25
- });
26
-
27
- test("isImageFile returns true for uppercase extension", () => {
28
- assert.equal(isImageFile("PHOTO.JPG"), true);
29
- });
30
-
31
- test("isImageFile returns true for .webp", () => {
32
- assert.equal(isImageFile("img.webp"), true);
33
- });
34
-
35
- test("isImageFile returns false for .txt", () => {
36
- assert.equal(isImageFile("document.txt"), false);
37
- });
38
-
39
- test("isImageFile returns false for .ts", () => {
40
- assert.equal(isImageFile("component.ts"), false);
41
- });
42
-
43
- test("isImageFile returns false for no extension", () => {
44
- assert.equal(isImageFile("noext"), false);
45
- });
46
-
47
- // ─── resolveAttachmentDestPath ────────────────────────────────────────────────
48
-
49
- test("resolveAttachmentDestPath returns base filename when no collision", async () => {
50
- const dir = path.join(tmpdir(), `codexa-attach-test-${Date.now()}`);
51
- await mkdir(dir, { recursive: true });
52
- try {
53
- const dest = await resolveAttachmentDestPath("/some/path/file.png", dir);
54
- assert.equal(path.basename(dest), "file.png");
55
- assert.equal(path.dirname(dest), dir);
56
- } finally {
57
- await rm(dir, { recursive: true, force: true });
58
- }
59
- });
60
-
61
- test("resolveAttachmentDestPath appends -1 on collision", async () => {
62
- const dir = path.join(tmpdir(), `codexa-attach-test-${Date.now()}`);
63
- await mkdir(dir, { recursive: true });
64
- try {
65
- // Create a file that would collide
66
- await writeFile(path.join(dir, "file.png"), "existing");
67
- const dest = await resolveAttachmentDestPath("/some/path/file.png", dir);
68
- assert.equal(path.basename(dest), "file-1.png");
69
- } finally {
70
- await rm(dir, { recursive: true, force: true });
71
- }
72
- });
73
-
74
- test("resolveAttachmentDestPath appends -2 on double collision", async () => {
75
- const dir = path.join(tmpdir(), `codexa-attach-test-${Date.now()}`);
76
- await mkdir(dir, { recursive: true });
77
- try {
78
- await writeFile(path.join(dir, "file.png"), "existing");
79
- await writeFile(path.join(dir, "file-1.png"), "existing-1");
80
- const dest = await resolveAttachmentDestPath("/some/path/file.png", dir);
81
- assert.equal(path.basename(dest), "file-2.png");
82
- } finally {
83
- await rm(dir, { recursive: true, force: true });
84
- }
85
- });
86
-
87
- // ─── importExternalFile ───────────────────────────────────────────────────────
88
-
89
- test("importExternalFile creates attachments dir if missing and copies file", async () => {
90
- const baseDir = path.join(tmpdir(), `codexa-import-test-${Date.now()}`);
91
- const srcDir = path.join(baseDir, "src");
92
- const destDir = path.join(baseDir, "attachments");
93
- await mkdir(srcDir, { recursive: true });
94
- const srcPath = path.join(srcDir, "test.png");
95
- await writeFile(srcPath, "image-content");
96
- try {
97
- const destPath = await importExternalFile(srcPath, destDir);
98
- assert.equal(path.basename(destPath), "test.png");
99
- // Verify the file was copied
100
- const { readFile } = await import("node:fs/promises");
101
- const content = await readFile(destPath, "utf8");
102
- assert.equal(content, "image-content");
103
- } finally {
104
- await rm(baseDir, { recursive: true, force: true });
105
- }
106
- });
107
-
108
- // ─── rewritePromptWithImportedPaths ──────────────────────────────────────────
109
-
110
- test("rewritePromptWithImportedPaths rewrites quoted path with spaces", () => {
111
- const prompt = '"C:\\Users\\jorda\\OneDrive\\Screenshots\\Screenshot 2026.png" what is this?';
112
- const result = rewritePromptWithImportedPaths(prompt, [{
113
- rawPath: "C:\\Users\\jorda\\OneDrive\\Screenshots\\Screenshot 2026.png",
114
- workspaceRelativePath: ".codexa/attachments/Screenshot 2026.png",
115
- }]);
116
- assert.equal(result, '".codexa/attachments/Screenshot 2026.png" what is this?');
117
- });
118
-
119
- test("rewritePromptWithImportedPaths rewrites unquoted path without spaces", () => {
120
- const prompt = "Look at C:\\Users\\jorda\\file.png please";
121
- const result = rewritePromptWithImportedPaths(prompt, [{
122
- rawPath: "C:\\Users\\jorda\\file.png",
123
- workspaceRelativePath: ".codexa/attachments/file.png",
124
- }]);
125
- assert.equal(result, "Look at .codexa/attachments/file.png please");
126
- });
127
-
128
- test("rewritePromptWithImportedPaths leaves non-matched text unchanged", () => {
129
- const prompt = "hello world, no paths here";
130
- const result = rewritePromptWithImportedPaths(prompt, [{
131
- rawPath: "C:\\some\\other\\path.png",
132
- workspaceRelativePath: ".codexa/attachments/path.png",
133
- }]);
134
- assert.equal(result, "hello world, no paths here");
135
- });
136
-
137
- test("rewritePromptWithImportedPaths does not double-quote already-quoted replacement path with spaces", () => {
138
- const prompt = '"C:\\Users\\file with spaces.png" describe';
139
- const result = rewritePromptWithImportedPaths(prompt, [{
140
- rawPath: "C:\\Users\\file with spaces.png",
141
- workspaceRelativePath: ".codexa/attachments/file with spaces.png",
142
- }]);
143
- // workspaceRelativePath has spaces → re-quoted; original was quoted → double-quoted match replaced
144
- assert.equal(result, '".codexa/attachments/file with spaces.png" describe');
145
- });
146
-
147
- test("rewritePromptWithImportedPaths handles multiple replacements", () => {
148
- // Paths without spaces get no quotes; the enclosing quotes from the original are consumed
149
- const prompt = '"C:\\path\\a.png" and "C:\\path\\b.png" compare them';
150
- const result = rewritePromptWithImportedPaths(prompt, [
151
- { rawPath: "C:\\path\\a.png", workspaceRelativePath: ".codexa/attachments/a.png" },
152
- { rawPath: "C:\\path\\b.png", workspaceRelativePath: ".codexa/attachments/b.png" },
153
- ]);
154
- assert.equal(result, ".codexa/attachments/a.png and .codexa/attachments/b.png compare them");
155
- });
@@ -1,68 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import test from "node:test";
3
- import {
4
- getRunGateDecision,
5
- getAuthStateLabel,
6
- inferAuthStateFromProbe,
7
- isLikelyAuthFailure,
8
- } from "./codexAuth.js";
9
-
10
- test("infers authenticated from successful probe exit", () => {
11
- const state = inferAuthStateFromProbe(0, "", "");
12
- assert.equal(state, "authenticated");
13
- });
14
-
15
- test("infers unauthenticated from signed-out probe output", () => {
16
- const state = inferAuthStateFromProbe(
17
- 1,
18
- "No active session. Please login.",
19
- "",
20
- );
21
- assert.equal(state, "unauthenticated");
22
- });
23
-
24
- test("infers unknown from ambiguous probe output", () => {
25
- const state = inferAuthStateFromProbe(
26
- 2,
27
- "Usage: codex login status [OPTIONS]",
28
- "unexpected argument '--json'",
29
- );
30
- assert.equal(state, "unknown");
31
- });
32
-
33
- test("blocks runs when unauthenticated", () => {
34
- const decision = getRunGateDecision("unauthenticated");
35
- assert.equal(decision.allowRun, false);
36
- assert.match(decision.blockMessage ?? "", /codex login/i);
37
- });
38
-
39
- test("warns but allows runs when auth state is unknown", () => {
40
- const decision = getRunGateDecision("unknown");
41
- assert.equal(decision.allowRun, true);
42
- assert.match(decision.warningMessage ?? "", /unknown/i);
43
- });
44
-
45
- test("allows unchecked initial auth state without a warning", () => {
46
- const decision = getRunGateDecision("unknown", { warnOnUnknown: false });
47
- assert.equal(decision.allowRun, true);
48
- assert.equal(decision.warningMessage, undefined);
49
- });
50
-
51
- test("allows runs when authenticated", () => {
52
- const decision = getRunGateDecision("authenticated");
53
- assert.equal(decision.allowRun, true);
54
- assert.equal(decision.warningMessage, undefined);
55
- });
56
-
57
- test("detects runtime auth failure messages", () => {
58
- const detected = isLikelyAuthFailure("Unauthorized (401): token expired. Run codex login.");
59
- assert.equal(detected, true);
60
- });
61
-
62
- test("getAuthStateLabel returns Checking for checking state", () => {
63
- assert.equal(getAuthStateLabel("checking"), "Checking");
64
- });
65
-
66
- test("getAuthStateLabel returns Unknown for unknown state", () => {
67
- assert.equal(getAuthStateLabel("unknown"), "Unknown");
68
- });
@@ -1,76 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import test from "node:test";
3
- import type { RunToolActivity } from "../session/types.js";
4
- import { getBlockedCleanupFailure } from "./cleanupFastFail.js";
5
-
6
- function activity(overrides: Partial<RunToolActivity>): RunToolActivity {
7
- return {
8
- id: "tool-1",
9
- command: "Remove-Item -Recurse __pycache__",
10
- status: "failed",
11
- startedAt: 1,
12
- completedAt: 2,
13
- ...overrides,
14
- };
15
- }
16
-
17
- test("reports access-denied Remove-Item cleanup failures", () => {
18
- const result = getBlockedCleanupFailure(activity({
19
- summary: "Access to the path 'C:\\repo\\__pycache__\\mod.pyc' is denied.",
20
- }));
21
-
22
- assert.match(result ?? "", /access denied/i);
23
- assert.match(result ?? "", /C:\\repo\\__pycache__\\mod\.pyc/);
24
- assert.match(result ?? "", /stopped/i);
25
- });
26
-
27
- test("reports generic POSIX and node locked delete failures", () => {
28
- for (const summary of [
29
- "rm: cannot remove 'tests/__pycache__/case.pyc': Permission denied",
30
- "EPERM: operation not permitted, unlink 'tests/__pycache__/case.pyc'",
31
- "EACCES: permission denied, rmdir 'tests/__pycache__'",
32
- "EBUSY: resource busy or locked, unlink 'tests/__pycache__/case.pyc'",
33
- "The process cannot access the file because it is being used by another process: 'tests/__pycache__/case.pyc'",
34
- ]) {
35
- const result = getBlockedCleanupFailure(activity({
36
- command: "rm -rf tests/__pycache__",
37
- summary,
38
- }));
39
-
40
- assert.ok(result, `Expected blocked cleanup failure for: ${summary}`);
41
- assert.match(result, /blocked|denied|locked|busy/i);
42
- }
43
- });
44
-
45
- test("reports git and generic lock artifacts during delete failures", () => {
46
- const gitLock = getBlockedCleanupFailure(activity({
47
- command: "Remove-Item -LiteralPath .git\\config.lock",
48
- summary: "Access to the path '.git\\config.lock' is denied.",
49
- }));
50
- const genericLock = getBlockedCleanupFailure(activity({
51
- command: "del cache.lock",
52
- summary: "EPERM: operation not permitted, unlink 'cache.lock'",
53
- }));
54
-
55
- assert.match(gitLock ?? "", /lock artifact/i);
56
- assert.match(gitLock ?? "", /\.git\\config\.lock/i);
57
- assert.match(genericLock ?? "", /lock artifact/i);
58
- assert.match(genericLock ?? "", /cache\.lock/i);
59
- });
60
-
61
- test("ignores non-delete failures and non-failed activities", () => {
62
- assert.equal(getBlockedCleanupFailure(activity({
63
- command: "git status",
64
- summary: "fatal: Unable to create '.git/config.lock': File exists.",
65
- })), null);
66
-
67
- assert.equal(getBlockedCleanupFailure(activity({
68
- status: "completed",
69
- summary: "Access to the path 'x.pyc' is denied.",
70
- })), null);
71
-
72
- assert.equal(getBlockedCleanupFailure(activity({
73
- command: "rm -rf __pycache__",
74
- summary: "No such file or directory",
75
- })), null);
76
- });
@@ -1,195 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import test from "node:test";
3
- import { resolveRuntimeConfig, normalizeRuntimeConfig } from "../config/runtimeConfig.js";
4
- import { buildCodexExecArgs } from "./codexExecArgs.js";
5
- import type { CodexCliCapabilities } from "./models/codexCapabilities.js";
6
-
7
- const fullCapabilities: CodexCliCapabilities = {
8
- askForApproval: true,
9
- sandbox: true,
10
- config: true,
11
- fullAuto: true,
12
- };
13
-
14
- test("uses dedicated runtime policy flags when supported", () => {
15
- const result = buildCodexExecArgs({
16
- runtime: resolveRuntimeConfig(normalizeRuntimeConfig({
17
- model: "gpt-5.4",
18
- policy: {
19
- approvalPolicy: "untrusted",
20
- sandboxMode: "read-only",
21
- },
22
- })),
23
- cwd: "C:/repo",
24
- }, fullCapabilities);
25
-
26
- assert.deepEqual(result, {
27
- ok: true,
28
- strategy: "direct-flags",
29
- args: [
30
- "exec",
31
- "--experimental-json",
32
- "--skip-git-repo-check",
33
- "--cd",
34
- "C:/repo",
35
- "--model",
36
- "gpt-5.4",
37
- "--config",
38
- "model_reasoning_effort=high",
39
- "--ask-for-approval",
40
- "untrusted",
41
- "--sandbox",
42
- "read-only",
43
- "-",
44
- ],
45
- });
46
- });
47
-
48
- test("falls back to config overrides when dedicated policy flags are unavailable", () => {
49
- const result = buildCodexExecArgs({
50
- runtime: resolveRuntimeConfig(normalizeRuntimeConfig({
51
- model: "gpt-5.4-mini",
52
- reasoningLevel: "medium",
53
- policy: {
54
- approvalPolicy: "on-request",
55
- sandboxMode: "workspace-write",
56
- },
57
- })),
58
- cwd: "C:/repo",
59
- }, {
60
- askForApproval: false,
61
- sandbox: false,
62
- config: true,
63
- fullAuto: false,
64
- });
65
-
66
- assert.deepEqual(result, {
67
- ok: true,
68
- strategy: "config-overrides",
69
- args: [
70
- "exec",
71
- "--experimental-json",
72
- "--skip-git-repo-check",
73
- "--cd",
74
- "C:/repo",
75
- "--model",
76
- "gpt-5.4-mini",
77
- "--config",
78
- "model_reasoning_effort=medium",
79
- "-c",
80
- "approval_policy=on-request",
81
- "-c",
82
- "sandbox_mode=workspace-write",
83
- "-",
84
- ],
85
- });
86
- });
87
-
88
- test("fails clearly when the requested policy cannot be represented safely", () => {
89
- const result = buildCodexExecArgs({
90
- runtime: resolveRuntimeConfig(normalizeRuntimeConfig({
91
- model: "gpt-5.4",
92
- policy: {
93
- approvalPolicy: "on-request",
94
- sandboxMode: "workspace-write",
95
- },
96
- })),
97
- cwd: "C:/repo",
98
- }, {
99
- askForApproval: false,
100
- sandbox: false,
101
- config: false,
102
- fullAuto: false,
103
- });
104
-
105
- assert.equal(result.ok, false);
106
- assert.equal(result.strategy, "fail");
107
- assert.match(result.ok ? "" : result.error, /cannot safely apply the selected reasoning level/i);
108
- assert.match(result.ok ? "" : result.error, /does not support --config/i);
109
- });
110
-
111
- test("uses mixed config and direct flags when only approval direct flag is unavailable", () => {
112
- const result = buildCodexExecArgs({
113
- runtime: resolveRuntimeConfig(normalizeRuntimeConfig({
114
- model: "gpt-5.4",
115
- reasoningLevel: "medium",
116
- policy: {
117
- approvalPolicy: "on-request",
118
- sandboxMode: "workspace-write",
119
- },
120
- })),
121
- cwd: "C:/repo",
122
- }, {
123
- askForApproval: false,
124
- sandbox: true,
125
- config: true,
126
- fullAuto: true,
127
- });
128
-
129
- assert.deepEqual(result, {
130
- ok: true,
131
- strategy: "config-overrides",
132
- args: [
133
- "exec",
134
- "--experimental-json",
135
- "--skip-git-repo-check",
136
- "--cd",
137
- "C:/repo",
138
- "--model",
139
- "gpt-5.4",
140
- "--config",
141
- "model_reasoning_effort=medium",
142
- "-c",
143
- "approval_policy=on-request",
144
- "--sandbox",
145
- "workspace-write",
146
- "-",
147
- ],
148
- });
149
- });
150
-
151
- test("forwards network, writable roots, service tier, and personality as config overrides", () => {
152
- const result = buildCodexExecArgs({
153
- runtime: resolveRuntimeConfig(normalizeRuntimeConfig({
154
- model: "gpt-5.4",
155
- policy: {
156
- approvalPolicy: "never",
157
- sandboxMode: "workspace-write",
158
- networkAccess: "enabled",
159
- writableRoots: ["C:/repo/tmp", "C:/repo/cache"],
160
- serviceTier: "fast",
161
- personality: "pragmatic",
162
- },
163
- })),
164
- cwd: "C:/repo",
165
- }, fullCapabilities);
166
-
167
- assert.deepEqual(result, {
168
- ok: true,
169
- strategy: "direct-flags",
170
- args: [
171
- "exec",
172
- "--experimental-json",
173
- "--skip-git-repo-check",
174
- "--cd",
175
- "C:/repo",
176
- "--model",
177
- "gpt-5.4",
178
- "--config",
179
- "model_reasoning_effort=high",
180
- "--ask-for-approval",
181
- "never",
182
- "--sandbox",
183
- "workspace-write",
184
- "--config",
185
- "sandbox_workspace_write.network_access=true",
186
- "--config",
187
- "sandbox_workspace_write.writable_roots=[\"C:\\\\repo\\\\tmp\",\"C:\\\\repo\\\\cache\"]",
188
- "--config",
189
- "service_tier=fast",
190
- "--config",
191
- "personality=pragmatic",
192
- "-",
193
- ],
194
- });
195
- });