@golba98/codexa 1.0.2 → 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 (150) hide show
  1. package/README.md +391 -94
  2. package/package.json +12 -7
  3. package/src/app.tsx +149 -117
  4. package/src/commands/handler.ts +4 -4
  5. package/src/config/buildInfo.ts +2 -2
  6. package/src/config/persistence.ts +10 -0
  7. package/src/config/settings.ts +7 -15
  8. package/src/config/updateCheckCache.ts +19 -1
  9. package/src/core/channel.ts +23 -0
  10. package/src/core/executables/codexExecutable.ts +1 -0
  11. package/src/core/executables/executableResolver.ts +61 -43
  12. package/src/core/launchContext.ts +33 -33
  13. package/src/core/process/processValidation.ts +9 -5
  14. package/src/core/providerLauncher/launcher.ts +59 -42
  15. package/src/core/updateCheck.ts +111 -95
  16. package/src/session/types.ts +2 -1
  17. package/src/ui/ActionRequiredBlock.tsx +5 -5
  18. package/src/ui/ActivityBars.tsx +3 -3
  19. package/src/ui/ActivityIndicator.tsx +6 -6
  20. package/src/ui/AgentBlock.tsx +6 -6
  21. package/src/ui/AnimatedStatusText.tsx +1 -1
  22. package/src/ui/AppShell.tsx +9 -24
  23. package/src/ui/AttachmentImportPanel.tsx +8 -8
  24. package/src/ui/AuthPanel.tsx +20 -20
  25. package/src/ui/BottomComposer.tsx +117 -78
  26. package/src/ui/DashCard.tsx +3 -3
  27. package/src/ui/Markdown.tsx +17 -17
  28. package/src/ui/ModelPickerScreen.tsx +10 -10
  29. package/src/ui/ModelReasoningPicker.tsx +14 -14
  30. package/src/ui/Panel.tsx +3 -3
  31. package/src/ui/PlanActionPicker.tsx +6 -6
  32. package/src/ui/PlanReviewPanel.tsx +8 -8
  33. package/src/ui/ProviderPicker.tsx +18 -18
  34. package/src/ui/RunFooter.tsx +3 -3
  35. package/src/ui/SelectionPanel.tsx +4 -4
  36. package/src/ui/SettingsPanel.tsx +8 -8
  37. package/src/ui/Spinner.tsx +1 -1
  38. package/src/ui/TextEntryPanel.tsx +10 -10
  39. package/src/ui/ThinkingBlock.tsx +8 -8
  40. package/src/ui/Timeline.tsx +21 -17
  41. package/src/ui/TopHeader.tsx +421 -293
  42. package/src/ui/TurnGroup.tsx +31 -31
  43. package/src/ui/UpdateAvailableCard.tsx +41 -0
  44. package/src/ui/UpdatePromptPanel.tsx +203 -0
  45. package/src/ui/focus.ts +3 -0
  46. package/src/ui/layout.ts +1 -1
  47. package/src/ui/logoVariants.ts +107 -0
  48. package/src/ui/modeDisplay.ts +12 -12
  49. package/src/ui/runtimeDisplay.ts +100 -0
  50. package/src/ui/theme.tsx +274 -395
  51. package/src/ui/timelineMeasure.ts +29 -19
  52. package/scripts/audit-codexa-capabilities.mjs +0 -466
  53. package/scripts/gen-build-info.mjs +0 -33
  54. package/scripts/smoke-terminal-bench.mjs +0 -35
  55. package/src/appRenderStability.test.ts +0 -131
  56. package/src/commands/handler.test.ts +0 -655
  57. package/src/config/launchArgs.test.ts +0 -189
  58. package/src/config/layeredConfig.test.ts +0 -143
  59. package/src/config/persistence.test.ts +0 -114
  60. package/src/config/runtimeConfig.test.ts +0 -218
  61. package/src/config/settings.test.ts +0 -155
  62. package/src/config/trustStore.test.ts +0 -29
  63. package/src/core/attachments.test.ts +0 -155
  64. package/src/core/auth/codexAuth.test.ts +0 -68
  65. package/src/core/cleanupFastFail.test.ts +0 -76
  66. package/src/core/codexExecArgs.test.ts +0 -195
  67. package/src/core/codexLaunch.test.ts +0 -205
  68. package/src/core/codexPrompt.test.ts +0 -252
  69. package/src/core/executables/codexExecutable.test.ts +0 -212
  70. package/src/core/executables/executableResolver.test.ts +0 -129
  71. package/src/core/executables/geminiExecutable.test.ts +0 -116
  72. package/src/core/executables/pathSanityScan.test.ts +0 -47
  73. package/src/core/githubDiagnostics.test.ts +0 -92
  74. package/src/core/hollowResponseFormat.test.ts +0 -58
  75. package/src/core/launchContext.test.ts +0 -157
  76. package/src/core/models/codexCapabilities.test.ts +0 -45
  77. package/src/core/models/codexModelCapabilities.test.ts +0 -246
  78. package/src/core/models/modelSpecs.test.ts +0 -283
  79. package/src/core/perf/renderDebug.test.ts +0 -230
  80. package/src/core/planStorage.test.ts +0 -143
  81. package/src/core/process/CommandRunner.test.ts +0 -105
  82. package/src/core/projectInstructions.test.ts +0 -50
  83. package/src/core/providerLauncher/launcher.test.ts +0 -238
  84. package/src/core/providerLauncher/registry.test.ts +0 -324
  85. package/src/core/providerLauncher/workspaceConfig.test.ts +0 -638
  86. package/src/core/providerRuntime/anthropic.test.ts +0 -1120
  87. package/src/core/providerRuntime/capabilityProfile.test.ts +0 -311
  88. package/src/core/providerRuntime/contextMetadata.test.ts +0 -468
  89. package/src/core/providerRuntime/gemini.test.ts +0 -437
  90. package/src/core/providerRuntime/lmstudio.test.ts +0 -168
  91. package/src/core/providerRuntime/local.test.ts +0 -787
  92. package/src/core/providerRuntime/registry.test.ts +0 -233
  93. package/src/core/providers/codexJsonStream.test.ts +0 -148
  94. package/src/core/providers/codexSubprocess.test.ts +0 -68
  95. package/src/core/providers/codexTranscript.test.ts +0 -284
  96. package/src/core/terminal/startupClear.test.ts +0 -55
  97. package/src/core/terminal/terminalCapabilities.test.ts +0 -93
  98. package/src/core/terminal/terminalControl.test.ts +0 -75
  99. package/src/core/terminal/terminalSanitize.test.ts +0 -22
  100. package/src/core/terminal/terminalSelection.test.ts +0 -42
  101. package/src/core/terminal/terminalTitle.test.ts +0 -328
  102. package/src/core/updateCheck.test.ts +0 -194
  103. package/src/core/workspaceActivity.test.ts +0 -163
  104. package/src/core/workspaceGuard.test.ts +0 -151
  105. package/src/core/workspaceRoot.test.ts +0 -23
  106. package/src/exec.test.ts +0 -13
  107. package/src/headless/execArgs.test.ts +0 -147
  108. package/src/headless/execRunner.test.ts +0 -436
  109. package/src/index.test.tsx +0 -620
  110. package/src/session/appSession.test.ts +0 -897
  111. package/src/session/chatLifecycle.test.ts +0 -64
  112. package/src/session/liveRenderScheduler.test.ts +0 -201
  113. package/src/session/planFlow.test.ts +0 -103
  114. package/src/session/planTranscript.test.ts +0 -65
  115. package/src/session/promptRunSchedule.test.ts +0 -36
  116. package/src/ui/ActivityIndicator.test.tsx +0 -58
  117. package/src/ui/AgentBlock.test.ts +0 -6
  118. package/src/ui/AnimatedStatusText.test.ts +0 -16
  119. package/src/ui/AppShell.test.tsx +0 -1776
  120. package/src/ui/AttachmentImportPanel.test.tsx +0 -204
  121. package/src/ui/BottomComposer.test.ts +0 -674
  122. package/src/ui/CodexLogo.tsx +0 -55
  123. package/src/ui/Markdown.test.ts +0 -157
  124. package/src/ui/ModelPickerProviderScope.test.tsx +0 -411
  125. package/src/ui/ModelPickerScreen.test.tsx +0 -99
  126. package/src/ui/ModelPickerState.test.tsx +0 -151
  127. package/src/ui/ModelReasoningPicker.test.tsx +0 -447
  128. package/src/ui/PlanReviewPanel.test.tsx +0 -267
  129. package/src/ui/PromptCardBorder.test.tsx +0 -161
  130. package/src/ui/ProviderPicker.test.tsx +0 -289
  131. package/src/ui/ProviderShortcut.test.tsx +0 -143
  132. package/src/ui/SettingsPanel.test.tsx +0 -233
  133. package/src/ui/Timeline.test.ts +0 -2067
  134. package/src/ui/TimelineNavigation.test.tsx +0 -201
  135. package/src/ui/TopHeader.test.tsx +0 -254
  136. package/src/ui/TurnGroup.test.tsx +0 -365
  137. package/src/ui/busyStatusAnimation.test.ts +0 -30
  138. package/src/ui/commandNormalize.test.ts +0 -142
  139. package/src/ui/diffRenderer.test.ts +0 -102
  140. package/src/ui/focusFlow.test.tsx +0 -1098
  141. package/src/ui/inputBuffer.test.ts +0 -151
  142. package/src/ui/layout.test.ts +0 -146
  143. package/src/ui/modeDisplay.test.ts +0 -42
  144. package/src/ui/runActivityView.test.ts +0 -89
  145. package/src/ui/runLifecycleView.test.tsx +0 -237
  146. package/src/ui/statusRenderIsolation.test.tsx +0 -654
  147. package/src/ui/terminalAnswerFormat.test.ts +0 -19
  148. package/src/ui/textLayout.test.ts +0 -18
  149. package/src/ui/themeFlow.test.ts +0 -53
  150. 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
- });