@golba98/codexa 1.0.2 → 1.0.4

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 (206) hide show
  1. package/README.md +396 -100
  2. package/bin/codexa.js +62 -144
  3. package/package.json +14 -8
  4. package/src/app.tsx +596 -306
  5. package/src/commands/handler.ts +6 -6
  6. package/src/config/buildInfo.ts +2 -2
  7. package/src/config/layeredConfig.ts +1 -1
  8. package/src/config/persistence.ts +10 -0
  9. package/src/config/runtimeConfig.ts +1 -1
  10. package/src/config/settings.ts +8 -16
  11. package/src/config/trustStore.ts +1 -1
  12. package/src/config/updateCheckCache.ts +19 -1
  13. package/src/core/README.md +52 -0
  14. package/src/core/agent/loop.ts +282 -0
  15. package/src/core/agent/protocol.ts +211 -0
  16. package/src/core/agent/tools.ts +414 -0
  17. package/src/core/{codexExecArgs.ts → codex/codexExecArgs.ts} +2 -2
  18. package/src/core/{codexLaunch.ts → codex/codexLaunch.ts} +3 -3
  19. package/src/core/{codexPrompt.ts → codex/codexPrompt.ts} +3 -3
  20. package/src/core/debug/modelStateDebug.ts +34 -0
  21. package/src/core/executables/antigravityExecutable.ts +48 -0
  22. package/src/core/executables/codexExecutable.ts +1 -0
  23. package/src/core/executables/executableResolver.ts +65 -43
  24. package/src/core/perf/renderDebug.ts +10 -6
  25. package/src/core/process/processValidation.ts +9 -5
  26. package/src/core/providerLauncher/launcher.ts +59 -42
  27. package/src/core/providerLauncher/registry.ts +30 -14
  28. package/src/core/providerLauncher/types.ts +11 -9
  29. package/src/core/providerLauncher/workspaceConfig.ts +41 -26
  30. package/src/core/providerRuntime/anthropic.ts +7 -1
  31. package/src/core/providerRuntime/antigravity.ts +305 -0
  32. package/src/core/providerRuntime/claudeCodeDiscovery.ts +268 -22
  33. package/src/core/providerRuntime/claudeCodeDiscoveryDebug.ts +55 -0
  34. package/src/core/providerRuntime/contextMetadata.ts +12 -24
  35. package/src/core/providerRuntime/local.ts +129 -51
  36. package/src/core/providerRuntime/models.ts +22 -11
  37. package/src/core/providerRuntime/registry.ts +58 -31
  38. package/src/core/providerRuntime/types.ts +19 -14
  39. package/src/core/providers/codexSubprocess.ts +2 -2
  40. package/src/core/providers/types.ts +1 -1
  41. package/src/core/{attachments.ts → shared/attachments.ts} +27 -4
  42. package/src/core/{cleanupFastFail.ts → shared/cleanupFastFail.ts} +1 -1
  43. package/src/core/{hollowResponseFormat.ts → shared/hollowResponseFormat.ts} +1 -1
  44. package/src/core/terminal/clearFrameBoundary.ts +814 -0
  45. package/src/core/terminal/frameLock.ts +109 -0
  46. package/src/core/terminal/inkRenderReset.ts +123 -0
  47. package/src/core/terminal/terminalControl.ts +22 -0
  48. package/src/core/terminal/terminalTitle.ts +16 -102
  49. package/src/core/version/channel.ts +23 -0
  50. package/src/core/version/updateCheck.ts +193 -0
  51. package/src/core/{launchContext.ts → workspace/launchContext.ts} +32 -39
  52. package/src/core/{planStorage.ts → workspace/planStorage.ts} +2 -2
  53. package/src/core/{workspaceGuard.ts → workspace/workspaceGuard.ts} +97 -13
  54. package/src/headless/execRunner.ts +2 -2
  55. package/src/index.tsx +43 -89
  56. package/src/session/appSession.ts +10 -7
  57. package/src/session/chatLifecycle.ts +1 -1
  58. package/src/session/liveRenderScheduler.ts +1 -1
  59. package/src/session/types.ts +3 -2
  60. package/src/ui/ActionRequiredBlock.tsx +5 -5
  61. package/src/ui/ActivityBars.tsx +3 -3
  62. package/src/ui/ActivityIndicator.tsx +6 -6
  63. package/src/ui/AgentBlock.tsx +6 -6
  64. package/src/ui/AnimatedStatusText.tsx +1 -1
  65. package/src/ui/AppShell.tsx +670 -719
  66. package/src/ui/AttachmentImportPanel.tsx +8 -8
  67. package/src/ui/AuthPanel.tsx +20 -20
  68. package/src/ui/BottomComposer.tsx +158 -118
  69. package/src/ui/DashCard.tsx +3 -3
  70. package/src/ui/Markdown.tsx +17 -17
  71. package/src/ui/ModelPickerScreen.tsx +222 -42
  72. package/src/ui/ModelReasoningPicker.tsx +15 -15
  73. package/src/ui/Panel.tsx +3 -3
  74. package/src/ui/PlanActionPicker.tsx +6 -6
  75. package/src/ui/PlanReviewPanel.tsx +9 -9
  76. package/src/ui/ProviderPicker.tsx +735 -321
  77. package/src/ui/RunFooter.tsx +3 -3
  78. package/src/ui/RuntimeStatusBar.tsx +108 -0
  79. package/src/ui/SelectionPanel.tsx +8 -4
  80. package/src/ui/SettingsPanel.tsx +9 -9
  81. package/src/ui/Spinner.tsx +1 -1
  82. package/src/ui/TextEntryPanel.tsx +11 -11
  83. package/src/ui/ThinkingBlock.tsx +8 -8
  84. package/src/ui/Timeline.tsx +1625 -1472
  85. package/src/ui/TopHeader.tsx +437 -293
  86. package/src/ui/TranscriptShell.tsx +322 -0
  87. package/src/ui/TurnGroup.tsx +33 -33
  88. package/src/ui/UpdateAvailableCard.tsx +41 -0
  89. package/src/ui/UpdatePromptPanel.tsx +197 -0
  90. package/src/ui/focus.ts +3 -0
  91. package/src/ui/layout.ts +299 -25
  92. package/src/ui/layoutListWindow.ts +145 -0
  93. package/src/ui/logoVariants.ts +103 -0
  94. package/src/ui/modeDisplay.ts +12 -12
  95. package/src/ui/runtimeDisplay.ts +112 -0
  96. package/src/ui/textLayout.ts +15 -4
  97. package/src/ui/theme.tsx +274 -395
  98. package/src/ui/timelineMeasure.ts +218 -136
  99. package/scripts/audit-codexa-capabilities.mjs +0 -466
  100. package/scripts/gen-build-info.mjs +0 -33
  101. package/scripts/smoke-terminal-bench.mjs +0 -35
  102. package/src/appRenderStability.test.ts +0 -131
  103. package/src/commands/handler.test.ts +0 -655
  104. package/src/config/launchArgs.test.ts +0 -189
  105. package/src/config/layeredConfig.test.ts +0 -143
  106. package/src/config/persistence.test.ts +0 -114
  107. package/src/config/runtimeConfig.test.ts +0 -218
  108. package/src/config/settings.test.ts +0 -155
  109. package/src/config/trustStore.test.ts +0 -29
  110. package/src/core/attachments.test.ts +0 -155
  111. package/src/core/auth/codexAuth.test.ts +0 -68
  112. package/src/core/cleanupFastFail.test.ts +0 -76
  113. package/src/core/codex.ts +0 -124
  114. package/src/core/codexExecArgs.test.ts +0 -195
  115. package/src/core/codexLaunch.test.ts +0 -205
  116. package/src/core/codexPrompt.test.ts +0 -252
  117. package/src/core/executables/codexExecutable.test.ts +0 -212
  118. package/src/core/executables/executableResolver.test.ts +0 -129
  119. package/src/core/executables/geminiExecutable.test.ts +0 -116
  120. package/src/core/executables/pathSanityScan.test.ts +0 -47
  121. package/src/core/githubDiagnostics.test.ts +0 -92
  122. package/src/core/hollowResponseFormat.test.ts +0 -58
  123. package/src/core/launchContext.test.ts +0 -157
  124. package/src/core/models/codexCapabilities.test.ts +0 -45
  125. package/src/core/models/codexModelCapabilities.test.ts +0 -246
  126. package/src/core/models/modelSpecs.test.ts +0 -283
  127. package/src/core/perf/renderDebug.test.ts +0 -230
  128. package/src/core/planStorage.test.ts +0 -143
  129. package/src/core/process/CommandRunner.test.ts +0 -105
  130. package/src/core/projectInstructions.test.ts +0 -50
  131. package/src/core/providerLauncher/launcher.test.ts +0 -238
  132. package/src/core/providerLauncher/registry.test.ts +0 -324
  133. package/src/core/providerLauncher/workspaceConfig.test.ts +0 -638
  134. package/src/core/providerRuntime/anthropic.test.ts +0 -1120
  135. package/src/core/providerRuntime/capabilityProfile.test.ts +0 -311
  136. package/src/core/providerRuntime/contextMetadata.test.ts +0 -468
  137. package/src/core/providerRuntime/gemini.test.ts +0 -437
  138. package/src/core/providerRuntime/lmstudio.test.ts +0 -168
  139. package/src/core/providerRuntime/local.test.ts +0 -787
  140. package/src/core/providerRuntime/registry.test.ts +0 -233
  141. package/src/core/providers/codexJsonStream.test.ts +0 -148
  142. package/src/core/providers/codexSubprocess.test.ts +0 -68
  143. package/src/core/providers/codexTranscript.test.ts +0 -284
  144. package/src/core/terminal/startupClear.test.ts +0 -55
  145. package/src/core/terminal/terminalCapabilities.test.ts +0 -93
  146. package/src/core/terminal/terminalControl.test.ts +0 -75
  147. package/src/core/terminal/terminalSanitize.test.ts +0 -22
  148. package/src/core/terminal/terminalSelection.test.ts +0 -42
  149. package/src/core/terminal/terminalTitle.test.ts +0 -328
  150. package/src/core/updateCheck.test.ts +0 -194
  151. package/src/core/updateCheck.ts +0 -172
  152. package/src/core/workspaceActivity.test.ts +0 -163
  153. package/src/core/workspaceGuard.test.ts +0 -151
  154. package/src/core/workspaceRoot.test.ts +0 -23
  155. package/src/exec.test.ts +0 -13
  156. package/src/headless/execArgs.test.ts +0 -147
  157. package/src/headless/execRunner.test.ts +0 -436
  158. package/src/index.test.tsx +0 -620
  159. package/src/session/appSession.test.ts +0 -897
  160. package/src/session/chatLifecycle.test.ts +0 -64
  161. package/src/session/liveRenderScheduler.test.ts +0 -201
  162. package/src/session/planFlow.test.ts +0 -103
  163. package/src/session/planTranscript.test.ts +0 -65
  164. package/src/session/promptRunSchedule.test.ts +0 -36
  165. package/src/ui/ActivityIndicator.test.tsx +0 -58
  166. package/src/ui/AgentBlock.test.ts +0 -6
  167. package/src/ui/AnimatedStatusText.test.ts +0 -16
  168. package/src/ui/AppShell.test.tsx +0 -1776
  169. package/src/ui/AttachmentImportPanel.test.tsx +0 -204
  170. package/src/ui/BottomComposer.test.ts +0 -674
  171. package/src/ui/CodexLogo.tsx +0 -55
  172. package/src/ui/Markdown.test.ts +0 -157
  173. package/src/ui/ModelPickerProviderScope.test.tsx +0 -411
  174. package/src/ui/ModelPickerScreen.test.tsx +0 -99
  175. package/src/ui/ModelPickerState.test.tsx +0 -151
  176. package/src/ui/ModelReasoningPicker.test.tsx +0 -447
  177. package/src/ui/PlanReviewPanel.test.tsx +0 -267
  178. package/src/ui/PromptCardBorder.test.tsx +0 -161
  179. package/src/ui/ProviderPicker.test.tsx +0 -289
  180. package/src/ui/ProviderShortcut.test.tsx +0 -143
  181. package/src/ui/SettingsPanel.test.tsx +0 -233
  182. package/src/ui/StaticTranscriptItem.tsx +0 -56
  183. package/src/ui/Timeline.test.ts +0 -2067
  184. package/src/ui/TimelineNavigation.test.tsx +0 -201
  185. package/src/ui/TopHeader.test.tsx +0 -254
  186. package/src/ui/TurnGroup.test.tsx +0 -365
  187. package/src/ui/busyStatusAnimation.test.ts +0 -30
  188. package/src/ui/commandNormalize.test.ts +0 -142
  189. package/src/ui/diffRenderer.test.ts +0 -102
  190. package/src/ui/focusFlow.test.tsx +0 -1098
  191. package/src/ui/inputBuffer.test.ts +0 -151
  192. package/src/ui/layout.test.ts +0 -146
  193. package/src/ui/modeDisplay.test.ts +0 -42
  194. package/src/ui/runActivityView.test.ts +0 -89
  195. package/src/ui/runLifecycleView.test.tsx +0 -237
  196. package/src/ui/statusRenderIsolation.test.tsx +0 -654
  197. package/src/ui/terminalAnswerFormat.test.ts +0 -19
  198. package/src/ui/textLayout.test.ts +0 -18
  199. package/src/ui/themeFlow.test.ts +0 -53
  200. package/src/ui/timelineMeasureCache.test.ts +0 -986
  201. /package/src/core/{inputDebug.ts → debug/inputDebug.ts} +0 -0
  202. /package/src/core/{clipboard.ts → shared/clipboard.ts} +0 -0
  203. /package/src/core/{githubDiagnostics.ts → shared/githubDiagnostics.ts} +0 -0
  204. /package/src/core/{projectInstructions.ts → workspace/projectInstructions.ts} +0 -0
  205. /package/src/core/{workspaceActivity.ts → workspace/workspaceActivity.ts} +0 -0
  206. /package/src/core/{workspaceRoot.ts → workspace/workspaceRoot.ts} +0 -0
@@ -1,284 +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 {
5
- createCodexTranscriptStreamParser,
6
- sanitizeCodexTranscript,
7
- stripNonPrintableControls,
8
- } from "./codexTranscript.js";
9
-
10
- test("extracts the assistant reply from noisy codex transcript output", () => {
11
- const raw = [
12
- "Reading additional input from stdin...",
13
- "OpenAI Codex v0.118.0 (research preview)",
14
- "--------",
15
- "workdir: C:\\Development\\1-JavaScript\\13-Custom CLI",
16
- "model: gpt-5.4",
17
- "provider: openai",
18
- "approval: never",
19
- "sandbox: read-only",
20
- "reasoning effort: xhigh",
21
- "reasoning summaries: none",
22
- "session id: 019d4984-90c0-7402-80c9-3d55a8e0373f",
23
- "--------",
24
- "user",
25
- "Hello",
26
- "codex",
27
- "Hello. What do you need help with?",
28
- "tokens used",
29
- "1,390",
30
- "Hello. What do you need help with?",
31
- ].join("\n");
32
-
33
- assert.equal(sanitizeCodexTranscript(raw), "Hello. What do you need help with?");
34
- });
35
-
36
- test("falls back to filtered plain output when no labeled assistant block exists", () => {
37
- const raw = [
38
- "\u001b[32mOpenAI Codex v0.118.0\u001b[0m",
39
- "--------",
40
- "workdir: C:\\repo",
41
- "",
42
- "First useful line",
43
- "Second useful line",
44
- ].join("\n");
45
-
46
- assert.equal(sanitizeCodexTranscript(raw), "First useful line\nSecond useful line");
47
- });
48
-
49
- test("filters echoed clarification-policy prompt lines from finalized transcripts", () => {
50
- const raw = [
51
- "default to best-effort continuation instead of stopping for clarification.",
52
- "if a detail is missing but non-critical, make the most reasonable assumption and state it briefly.",
53
- "if multiple paths are possible, choose one sensible path and continue.",
54
- "only ask a blocking follow-up question if proceeding would likely use the wrong file, wrong command, destructive behavior, or produce fundamentally incorrect output.",
55
- "if you are truly blocked on one critical missing fact, end the response with exactly one line in this format: [QUESTION]: <your question>",
56
- "",
57
- "Implemented the fix and assumed src/app.tsx was the target entrypoint.",
58
- ].join("\n");
59
-
60
- assert.equal(
61
- sanitizeCodexTranscript(raw),
62
- "Implemented the fix and assumed src/app.tsx was the target entrypoint.",
63
- );
64
- });
65
-
66
- test("returns a readable fallback when only noise is present", () => {
67
- const raw = [
68
- "Reading additional input from stdin...",
69
- "OpenAI Codex v0.118.0",
70
- "--------",
71
- "model: gpt-5.4",
72
- "provider: openai",
73
- ].join("\n");
74
-
75
- assert.match(
76
- sanitizeCodexTranscript(raw),
77
- /no assistant response text was detected/i,
78
- );
79
- });
80
-
81
- test("streams thinking lines separately from assistant deltas", () => {
82
- const thinking: string[] = [];
83
- const assistant: string[] = [];
84
- const parser = createCodexTranscriptStreamParser({
85
- onThinkingLine: (line) => thinking.push(line),
86
- onAssistantDelta: (chunk) => assistant.push(chunk),
87
- });
88
-
89
- parser.feed([
90
- "OpenAI Codex v0.118.0",
91
- "Checking src/app.tsx",
92
- "Task:",
93
- "Refactor the CLI",
94
- "",
95
- "assistant",
96
- "First line",
97
- "Second line",
98
- ].join("\n"));
99
- parser.flush();
100
-
101
- assert.deepEqual(thinking, ["Checking src/app.tsx"]);
102
- assert.deepEqual(assistant, ["First line", "\nSecond line"]);
103
- });
104
-
105
- test("emits fenced code blocks atomically when streaming", () => {
106
- const assistant: string[] = [];
107
- const parser = createCodexTranscriptStreamParser({
108
- onAssistantDelta: (chunk) => assistant.push(chunk),
109
- });
110
-
111
- parser.feed("assistant\n```ts\n");
112
- parser.feed("const value = 1;\n");
113
- parser.feed("```\n");
114
- parser.flush();
115
-
116
- // Code fences are buffered and emitted as a single atomic chunk
117
- assert.deepEqual(assistant, ["```ts\nconst value = 1;\n```"]);
118
- });
119
-
120
- test("emits tool activity separately from assistant prose while streaming", () => {
121
- const assistant: string[] = [];
122
- const toolActivity: RunToolActivity[] = [];
123
- const parser = createCodexTranscriptStreamParser({
124
- onAssistantDelta: (chunk) => assistant.push(chunk),
125
- onToolActivity: (activity) => toolActivity.push(activity),
126
- });
127
-
128
- parser.feed([
129
- "assistant",
130
- "$ rg --files",
131
- "src/app.tsx",
132
- "src/ui/BottomComposer.tsx",
133
- "",
134
- "I found the relevant files.",
135
- ].join("\n"));
136
- parser.flush();
137
-
138
- assert.deepEqual(assistant, ["I found the relevant files."]);
139
- assert.equal(toolActivity.length, 2);
140
- assert.equal(toolActivity[0]?.command, "rg --files");
141
- assert.equal(toolActivity[0]?.status, "running");
142
- assert.equal(toolActivity[1]?.command, "rg --files");
143
- assert.equal(toolActivity[1]?.status, "completed");
144
- assert.equal(toolActivity[1]?.summary, "Found 2 files");
145
- });
146
-
147
- test("removes tool execution stdout from the finalized assistant transcript", () => {
148
- const raw = [
149
- "assistant",
150
- "$ rg --files",
151
- "src/app.tsx",
152
- "src/ui/BottomComposer.tsx",
153
- "",
154
- "I found the relevant files and updated the composer.",
155
- ].join("\n");
156
-
157
- assert.equal(
158
- sanitizeCodexTranscript(raw),
159
- "I found the relevant files and updated the composer.",
160
- );
161
- });
162
-
163
- // ─── Progressive streaming tests ──────────────────────────────────────────────
164
-
165
- test("emits partial lines after timeout when no newline arrives", async () => {
166
- const assistant: string[] = [];
167
- const parser = createCodexTranscriptStreamParser({
168
- onAssistantDelta: (chunk) => assistant.push(chunk),
169
- });
170
-
171
- parser.feed("assistant\n");
172
- parser.feed("Hello wor");
173
- // No newline yet — partial flush timer should fire after ~100ms
174
- assert.equal(assistant.length, 0);
175
-
176
- await new Promise((resolve) => setTimeout(resolve, 150));
177
- assert.equal(assistant.length, 1);
178
- assert.equal(assistant[0], "Hello wor");
179
-
180
- parser.flush();
181
- // flush should not re-emit the already-emitted partial
182
- assert.equal(assistant.length, 1);
183
- });
184
-
185
- test("does not duplicate content when partial is followed by complete line", async () => {
186
- const assistant: string[] = [];
187
- const parser = createCodexTranscriptStreamParser({
188
- onAssistantDelta: (chunk) => assistant.push(chunk),
189
- });
190
-
191
- parser.feed("assistant\n");
192
- parser.feed("Start of line");
193
- // Wait for partial flush
194
- await new Promise((resolve) => setTimeout(resolve, 150));
195
- assert.equal(assistant.length, 1);
196
- assert.equal(assistant[0], "Start of line");
197
-
198
- // Now the rest of the line arrives with newline
199
- parser.feed(" and the rest\n");
200
- // The partial was already emitted; the remaining text appends naturally
201
- // The feed should process the complete line that includes both parts
202
- assert.ok(assistant.length >= 1);
203
-
204
- parser.flush();
205
- });
206
-
207
- test("code fence with prose before and after emits prose immediately and fence atomically", () => {
208
- const assistant: string[] = [];
209
- const parser = createCodexTranscriptStreamParser({
210
- onAssistantDelta: (chunk) => assistant.push(chunk),
211
- });
212
-
213
- parser.feed("assistant\nHere is the code:\n```ts\nconst x = 1;\n```\nDone.\n");
214
- parser.flush();
215
-
216
- assert.equal(assistant.length, 3);
217
- assert.equal(assistant[0], "Here is the code:");
218
- assert.equal(assistant[1], "\n```ts\nconst x = 1;\n```");
219
- assert.equal(assistant[2], "\nDone.");
220
- });
221
-
222
- test("code fence safety timeout emits buffered content after 3 seconds", async () => {
223
- const assistant: string[] = [];
224
- const parser = createCodexTranscriptStreamParser({
225
- onAssistantDelta: (chunk) => assistant.push(chunk),
226
- });
227
-
228
- parser.feed("assistant\n```ts\nconst x = 1;\n");
229
- // No closing fence — wait for safety timeout (3s)
230
- // We'll test that flush() force-emits instead of waiting the full 3s
231
- assert.equal(assistant.length, 0);
232
-
233
- parser.flush();
234
- assert.equal(assistant.length, 1);
235
- assert.equal(assistant[0], "```ts\nconst x = 1;");
236
- });
237
-
238
- test("auto-promotes long prose lines from preamble to assistant section", () => {
239
- const thinking: string[] = [];
240
- const assistant: string[] = [];
241
- const parser = createCodexTranscriptStreamParser({
242
- onThinkingLine: (line) => thinking.push(line),
243
- onAssistantDelta: (chunk) => assistant.push(chunk),
244
- });
245
-
246
- parser.feed([
247
- "I have analyzed the codebase and here is a comprehensive summary of all the changes that need to be made to fix this issue.",
248
- ].join("\n"));
249
- parser.flush();
250
-
251
- // Long prose with many words should auto-promote to assistant
252
- assert.equal(thinking.length, 0);
253
- assert.equal(assistant.length, 1);
254
- assert.match(assistant[0]!, /analyzed the codebase/);
255
- });
256
-
257
- test("does not auto-promote short status lines from preamble", () => {
258
- const thinking: string[] = [];
259
- const assistant: string[] = [];
260
- const parser = createCodexTranscriptStreamParser({
261
- onThinkingLine: (line) => thinking.push(line),
262
- onAssistantDelta: (chunk) => assistant.push(chunk),
263
- });
264
-
265
- parser.feed("Checking src/app.tsx\n");
266
- parser.flush();
267
-
268
- assert.equal(thinking.length, 1);
269
- assert.equal(assistant.length, 0);
270
- });
271
-
272
- test("strips control characters from streamed and finalized assistant text", () => {
273
- const assistant: string[] = [];
274
- const parser = createCodexTranscriptStreamParser({
275
- onAssistantDelta: (chunk) => assistant.push(chunk),
276
- });
277
-
278
- parser.feed("assistant\nHello\u0007 world\u0001\n");
279
- parser.flush();
280
-
281
- assert.deepEqual(assistant, ["Hello world"]);
282
- assert.equal(stripNonPrintableControls("A\u0000B\u001fC"), "ABC");
283
- assert.equal(sanitizeCodexTranscript("assistant\nDone\u0007\n"), "Done");
284
- });
@@ -1,55 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import test from "node:test";
3
- import { performStartupClear } from "./startupClear.js";
4
-
5
- function makeCapture(): { written: string[]; write: (chunk: string) => boolean } {
6
- const written: string[] = [];
7
- return { written, write: (chunk) => { written.push(chunk); return true; } };
8
- }
9
-
10
- const TRANSCRIPT_CLEAR = "\x1b[2J\x1b[3J\x1b[H";
11
-
12
- test("emits full transcript clear in normal interactive mode", () => {
13
- const cap = makeCapture();
14
- performStartupClear({ write: cap.write, noClear: false, env: {} });
15
- assert.deepEqual(cap.written, [TRANSCRIPT_CLEAR]);
16
- });
17
-
18
- test("skips clear when noClear flag is true (--no-clear)", () => {
19
- const cap = makeCapture();
20
- performStartupClear({ write: cap.write, noClear: true, env: {} });
21
- assert.equal(cap.written.length, 0);
22
- });
23
-
24
- test("skips clear when CODEXA_NO_CLEAR=1 env var is set", () => {
25
- const cap = makeCapture();
26
- performStartupClear({ write: cap.write, noClear: false, env: { CODEXA_NO_CLEAR: "1" } });
27
- assert.equal(cap.written.length, 0);
28
- });
29
-
30
- test("noClear flag takes precedence over unset env var", () => {
31
- const cap = makeCapture();
32
- performStartupClear({ write: cap.write, noClear: true, env: { CODEXA_NO_CLEAR: "0" } });
33
- assert.equal(cap.written.length, 0);
34
- });
35
-
36
- test("emits clear when CODEXA_NO_CLEAR is not '1'", () => {
37
- const cap = makeCapture();
38
- performStartupClear({ write: cap.write, noClear: false, env: { CODEXA_NO_CLEAR: "0" } });
39
- assert.deepEqual(cap.written, [TRANSCRIPT_CLEAR]);
40
- });
41
-
42
- test("emits clear when CODEXA_NO_CLEAR is undefined", () => {
43
- const cap = makeCapture();
44
- performStartupClear({ write: cap.write, noClear: false, env: { CODEXA_NO_CLEAR: undefined } });
45
- assert.deepEqual(cap.written, [TRANSCRIPT_CLEAR]);
46
- });
47
-
48
- test("clear sequence includes viewport clear, scrollback clear, and cursor home", () => {
49
- const cap = makeCapture();
50
- performStartupClear({ write: cap.write, noClear: false, env: {} });
51
- const seq = cap.written[0] ?? "";
52
- assert.ok(seq.includes("\x1b[2J"), "must include viewport clear");
53
- assert.ok(seq.includes("\x1b[3J"), "must include scrollback clear");
54
- assert.ok(seq.includes("\x1b[H"), "must include cursor home");
55
- });
@@ -1,93 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import test from "node:test";
3
- import { getTerminalCapability } from "./terminalCapabilities.js";
4
-
5
- test("allows Windows Terminal", () => {
6
- const result = getTerminalCapability({
7
- stdinIsTTY: true,
8
- stdoutIsTTY: true,
9
- platform: "win32",
10
- env: { WT_SESSION: "1" },
11
- });
12
-
13
- assert.equal(result.supported, true);
14
- assert.equal(result.reason, "supported");
15
- assert.equal(result.warning, undefined);
16
- });
17
-
18
- test("allows the VS Code terminal", () => {
19
- const result = getTerminalCapability({
20
- stdinIsTTY: true,
21
- stdoutIsTTY: true,
22
- platform: "win32",
23
- env: { TERM_PROGRAM: "vscode" },
24
- });
25
-
26
- assert.equal(result.supported, true);
27
- assert.equal(result.reason, "supported");
28
- assert.equal(result.warning, undefined);
29
- });
30
-
31
- test("allows modern Windows TTY when TERM is missing but warns", () => {
32
- const result = getTerminalCapability({
33
- stdinIsTTY: true,
34
- stdoutIsTTY: true,
35
- platform: "win32",
36
- env: {},
37
- });
38
-
39
- assert.equal(result.supported, true);
40
- assert.equal(result.reason, "supported");
41
- assert.match(result.warning ?? "", /continue/i);
42
- });
43
-
44
- test("CODEXA_FORCE_VT bypasses VT compatibility detection", () => {
45
- const result = getTerminalCapability({
46
- stdinIsTTY: true,
47
- stdoutIsTTY: true,
48
- platform: "win32",
49
- env: { CODEXA_FORCE_VT: "1", TERM: "dumb" },
50
- });
51
-
52
- assert.equal(result.supported, true);
53
- assert.equal(result.reason, "supported");
54
- assert.equal(result.warning, undefined);
55
- });
56
-
57
- test("CODEXA_REQUIRE_VT hard-fails when Windows VT support is not detected", () => {
58
- const result = getTerminalCapability({
59
- stdinIsTTY: true,
60
- stdoutIsTTY: true,
61
- platform: "win32",
62
- env: { CODEXA_REQUIRE_VT: "1" },
63
- });
64
-
65
- assert.equal(result.supported, false);
66
- assert.equal(result.reason, "unsupported-terminal");
67
- assert.match(result.message, /CODEXA_FORCE_VT=1/i);
68
- });
69
-
70
- test("rejects a dumb terminal even when it is interactive", () => {
71
- const result = getTerminalCapability({
72
- stdinIsTTY: true,
73
- stdoutIsTTY: true,
74
- platform: "linux",
75
- env: { TERM: "dumb" },
76
- });
77
-
78
- assert.equal(result.supported, false);
79
- assert.equal(result.reason, "unsupported-terminal");
80
- });
81
-
82
- test("rejects redirected or non-interactive output", () => {
83
- const result = getTerminalCapability({
84
- stdinIsTTY: true,
85
- stdoutIsTTY: false,
86
- platform: "linux",
87
- env: { TERM: "xterm-256color" },
88
- });
89
-
90
- assert.equal(result.supported, false);
91
- assert.equal(result.reason, "notty");
92
- assert.match(result.message, /interactive terminal/i);
93
- });
@@ -1,75 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import test from "node:test";
3
- import { createTerminalModeController, setTerminalControlUIState, TERMINAL_SEQUENCES, writeTerminalControl } from "./terminalControl.js";
4
-
5
- test("mouse reporting enables only normal mouse tracking with SGR coordinates", () => {
6
- let writes = "";
7
- const controller = createTerminalModeController((chunk) => {
8
- writes += chunk;
9
- });
10
-
11
- controller.setMouseReporting(true, "test");
12
-
13
- assert.equal(writes, "\x1b[?1000h\x1b[?1006h");
14
- assert.equal(TERMINAL_SEQUENCES.mouseEnable, "\x1b[?1000h\x1b[?1006h");
15
- assert.doesNotMatch(writes, /\x1b\[\?1002h|\x1b\[\?1003h|\x1b\[\?1004h|\x1b\[\?1005h|\x1b\[\?1015h/);
16
- assert.doesNotMatch(writes, /\x1b\[\?1049h|\x1b\[\?1049l|\x1b\[3J/);
17
- });
18
-
19
- test("mouse reporting cleanup disables broad modes defensively", () => {
20
- let writes = "";
21
- const controller = createTerminalModeController((chunk) => {
22
- writes += chunk;
23
- });
24
-
25
- controller.setMouseReporting(false, "test");
26
-
27
- assert.match(writes, /\x1b\[\?1000l/);
28
- assert.match(writes, /\x1b\[\?1002l/);
29
- assert.match(writes, /\x1b\[\?1003l/);
30
- assert.match(writes, /\x1b\[\?1006l/);
31
- assert.match(writes, /\x1b\[\?1015l/);
32
- });
33
-
34
- test("post-startup viewport clears are blocked except for transcript clear", () => {
35
- let writes = "";
36
- const write = (chunk: string) => {
37
- writes += chunk;
38
- };
39
-
40
- writeTerminalControl(write, "stdout", "test:resize", TERMINAL_SEQUENCES.viewportClear);
41
- assert.equal(writes, "");
42
-
43
- writeTerminalControl(write, "stdout", "test:transcriptClear", TERMINAL_SEQUENCES.transcriptClear);
44
- assert.equal(writes, TERMINAL_SEQUENCES.transcriptClear);
45
- assert.match(writes, /\x1b\[3J/);
46
- });
47
-
48
- test("terminal controller exposes an intentional transcript clear with scrollback erase", () => {
49
- let writes = "";
50
- const controller = createTerminalModeController((chunk) => {
51
- writes += chunk;
52
- });
53
-
54
- controller.clearTranscript("test");
55
-
56
- assert.equal(writes, TERMINAL_SEQUENCES.transcriptClear);
57
- assert.match(writes, /\x1b\[2J/);
58
- assert.match(writes, /\x1b\[3J/);
59
- assert.match(writes, /\x1b\[H/);
60
- });
61
-
62
- test("transcript clear is allowed while streaming state is active", () => {
63
- let writes = "";
64
- setTerminalControlUIState("RESPONDING");
65
-
66
- try {
67
- writeTerminalControl((chunk) => {
68
- writes += chunk;
69
- }, "stdout", "test:transcriptClear", TERMINAL_SEQUENCES.transcriptClear);
70
- } finally {
71
- setTerminalControlUIState("IDLE");
72
- }
73
-
74
- assert.equal(writes, TERMINAL_SEQUENCES.transcriptClear);
75
- });
@@ -1,22 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import test from "node:test";
3
- import {
4
- sanitizeTerminalInput,
5
- sanitizeTerminalLines,
6
- sanitizeTerminalOutput,
7
- } from "./terminalSanitize.js";
8
-
9
- test("sanitizeTerminalOutput strips ANSI/OSC/control bytes and normalizes line breaks", () => {
10
- const raw = "A\u001B[31mred\u001B[0m\u001B]0;title\u0007\r\nB\rC\u0007";
11
- assert.equal(sanitizeTerminalOutput(raw), "Ared\nB\nC");
12
- });
13
-
14
- test("sanitizeTerminalInput removes layout-breaking escapes", () => {
15
- const raw = "hello\u001B[2J\u001B[H\tworld";
16
- assert.equal(sanitizeTerminalInput(raw), "hello world");
17
- });
18
-
19
- test("sanitizeTerminalLines sanitizes and drops empty lines", () => {
20
- const lines = ["\u001B[32mok\u001B[0m", " \u0007", "next\rline"];
21
- assert.deepEqual(sanitizeTerminalLines(lines), ["ok", "next\nline"]);
22
- });
@@ -1,42 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import test from "node:test";
3
- import { getTerminalSelectionProfile } from "./terminalSelection.js";
4
-
5
- test("detects Windows Terminal selection override", () => {
6
- const profile = getTerminalSelectionProfile({ WT_SESSION: "abc", TERM_PROGRAM: "vscode" }, "win32");
7
-
8
- assert.equal(profile.id, "windows-terminal");
9
- assert.equal(profile.shortHint, "Shift+drag selects");
10
- assert.match(profile.selectionHint, /Shift\+drag to select instantly/i);
11
- });
12
-
13
- test("detects VS Code selection override on Windows and Linux", () => {
14
- const windows = getTerminalSelectionProfile({ TERM_PROGRAM: "vscode" }, "win32");
15
- const linux = getTerminalSelectionProfile({ TERM_PROGRAM: "vscode" }, "linux");
16
-
17
- assert.equal(windows.id, "vscode");
18
- assert.equal(windows.shortHint, "Alt+drag selects");
19
- assert.equal(linux.shortHint, "Alt+drag selects");
20
- });
21
-
22
- test("detects VS Code macOS option-drag wording", () => {
23
- const profile = getTerminalSelectionProfile({ TERM_PROGRAM: "vscode" }, "darwin");
24
-
25
- assert.equal(profile.id, "vscode");
26
- assert.equal(profile.shortHint, "Option+drag selects");
27
- });
28
-
29
- test("falls back to xterm-compatible Shift selection", () => {
30
- const profile = getTerminalSelectionProfile({ TERM: "xterm-256color" }, "linux");
31
-
32
- assert.equal(profile.id, "xterm-like");
33
- assert.equal(profile.shortHint, "Shift+drag selects");
34
- });
35
-
36
- test("unknown VT terminals get a practical default hint", () => {
37
- const profile = getTerminalSelectionProfile({}, "linux");
38
-
39
- assert.equal(profile.id, "unknown-vt");
40
- assert.equal(profile.shortHint, "Shift+drag selects");
41
- assert.match(profile.selectionHint, /terminal mouse-selection override/i);
42
- });