@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,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
- });