@golba98/codexa 1.0.1 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +391 -94
- package/package.json +14 -8
- package/src/app.tsx +196 -63
- package/src/commands/handler.ts +9 -4
- package/src/config/buildInfo.ts +3 -0
- package/src/config/launchArgs.ts +10 -0
- package/src/config/layeredConfig.ts +0 -0
- package/src/config/persistence.ts +51 -0
- package/src/config/runtimeConfig.ts +4 -0
- package/src/config/settings.ts +6 -13
- package/src/config/toml-serialize.ts +0 -0
- package/src/config/trustStore.ts +0 -0
- package/src/config/updateCheckCache.ts +63 -0
- package/src/core/attachments.ts +0 -0
- package/src/core/auth/codexAuth.ts +0 -0
- package/src/core/channel.ts +23 -0
- package/src/core/cleanupFastFail.ts +0 -0
- package/src/core/clipboard.ts +0 -0
- package/src/core/codex.ts +0 -0
- package/src/core/codexExecArgs.ts +0 -0
- package/src/core/codexLaunch.ts +0 -0
- package/src/core/codexPrompt.ts +0 -0
- package/src/core/executables/claudeExecutable.ts +0 -0
- package/src/core/executables/codexExecutable.ts +1 -0
- package/src/core/executables/executableResolver.ts +61 -43
- package/src/core/executables/geminiExecutable.ts +0 -0
- package/src/core/githubDiagnostics.ts +0 -0
- package/src/core/hollowResponseFormat.ts +0 -0
- package/src/core/inputDebug.ts +0 -0
- package/src/core/launchContext.ts +33 -33
- package/src/core/models/codexCapabilities.ts +0 -0
- package/src/core/models/codexModelCapabilities.ts +0 -0
- package/src/core/models/modelSpecs.ts +0 -0
- package/src/core/perf/profiler.ts +0 -0
- package/src/core/perf/renderDebug.ts +0 -0
- package/src/core/planStorage.ts +0 -0
- package/src/core/process/CommandRunner.ts +0 -0
- package/src/core/process/processValidation.ts +9 -5
- package/src/core/projectInstructions.ts +0 -0
- package/src/core/providerLauncher/launcher.ts +59 -42
- package/src/core/providerLauncher/registry.ts +0 -0
- package/src/core/providerLauncher/types.ts +0 -0
- package/src/core/providerLauncher/workspaceConfig.ts +0 -0
- package/src/core/providerRuntime/anthropic.ts +0 -0
- package/src/core/providerRuntime/capabilityProfile.ts +0 -0
- package/src/core/providerRuntime/claudeCodeDiscovery.ts +0 -0
- package/src/core/providerRuntime/contextMetadata.ts +0 -0
- package/src/core/providerRuntime/gemini.ts +0 -0
- package/src/core/providerRuntime/lmstudio.ts +0 -0
- package/src/core/providerRuntime/local.ts +0 -0
- package/src/core/providerRuntime/models.ts +0 -0
- package/src/core/providerRuntime/reasoning.ts +0 -0
- package/src/core/providerRuntime/registry.ts +0 -0
- package/src/core/providerRuntime/types.ts +0 -0
- package/src/core/providers/codexJsonStream.ts +0 -0
- package/src/core/providers/codexSubprocess.ts +0 -0
- package/src/core/providers/codexTranscript.ts +0 -0
- package/src/core/providers/openaiNative.ts +0 -0
- package/src/core/providers/registry.ts +0 -0
- package/src/core/providers/types.ts +0 -0
- package/src/core/terminal/startupClear.ts +20 -0
- package/src/core/terminal/terminalCapabilities.ts +0 -0
- package/src/core/terminal/terminalControl.ts +0 -0
- package/src/core/terminal/terminalSanitize.ts +0 -0
- package/src/core/terminal/terminalSelection.ts +0 -0
- package/src/core/terminal/terminalTitle.ts +0 -0
- package/src/core/updateCheck.ts +188 -0
- package/src/core/workspaceActivity.ts +0 -0
- package/src/core/workspaceGuard.ts +0 -0
- package/src/core/workspaceRoot.ts +0 -0
- package/src/exec.ts +0 -0
- package/src/headless/execArgs.ts +1 -0
- package/src/headless/execRunner.ts +0 -0
- package/src/index.tsx +13 -9
- package/src/session/appSession.ts +0 -0
- package/src/session/chatLifecycle.ts +0 -0
- package/src/session/liveRenderScheduler.ts +0 -0
- package/src/session/planFlow.ts +0 -0
- package/src/session/planTranscript.ts +0 -0
- package/src/session/promptRunSchedule.ts +0 -0
- package/src/session/types.ts +2 -1
- package/src/test/runtimeTestUtils.ts +0 -0
- package/src/types/react-dom.d.ts +0 -0
- package/src/ui/ActionRequiredBlock.tsx +5 -5
- package/src/ui/ActivityBars.tsx +3 -3
- package/src/ui/ActivityIndicator.tsx +6 -6
- package/src/ui/AgentBlock.tsx +6 -6
- package/src/ui/AnimatedStatusText.tsx +1 -1
- package/src/ui/AppShell.tsx +13 -5
- package/src/ui/AttachmentImportPanel.tsx +8 -8
- package/src/ui/AuthPanel.tsx +20 -20
- package/src/ui/BackendPicker.tsx +0 -0
- package/src/ui/BottomComposer.tsx +117 -78
- package/src/ui/DashCard.tsx +3 -3
- package/src/ui/Markdown.tsx +17 -17
- package/src/ui/ModePicker.tsx +0 -0
- package/src/ui/ModelPicker.tsx +0 -0
- package/src/ui/ModelPickerScreen.tsx +10 -10
- package/src/ui/ModelReasoningPicker.tsx +14 -14
- package/src/ui/Panel.tsx +3 -3
- package/src/ui/PermissionsPanel.tsx +0 -0
- package/src/ui/PlanActionPicker.tsx +6 -6
- package/src/ui/PlanReviewPanel.tsx +8 -8
- package/src/ui/ProviderPicker.tsx +18 -18
- package/src/ui/ReasoningPicker.tsx +0 -0
- package/src/ui/RunFooter.tsx +3 -3
- package/src/ui/SelectionPanel.tsx +4 -4
- package/src/ui/SettingsPanel.tsx +8 -8
- package/src/ui/Spinner.tsx +1 -1
- package/src/ui/StaticIntroItem.tsx +0 -0
- package/src/ui/StaticTranscriptItem.tsx +0 -0
- package/src/ui/TextEntryPanel.tsx +10 -10
- package/src/ui/ThemePicker.tsx +0 -0
- package/src/ui/ThinkingBlock.tsx +8 -8
- package/src/ui/Timeline.tsx +21 -17
- package/src/ui/TopHeader.tsx +421 -257
- package/src/ui/TurnGroup.tsx +31 -31
- package/src/ui/UpdateAvailableCard.tsx +41 -0
- package/src/ui/UpdatePromptPanel.tsx +203 -0
- package/src/ui/busyStatusAnimation.ts +0 -0
- package/src/ui/commandNormalize.ts +0 -0
- package/src/ui/diffRenderer.ts +0 -0
- package/src/ui/focus.ts +3 -0
- package/src/ui/inputBuffer.ts +0 -0
- package/src/ui/layout.ts +1 -1
- package/src/ui/logoVariants.ts +107 -0
- package/src/ui/modeDisplay.ts +12 -12
- package/src/ui/outputPipeline.ts +0 -0
- package/src/ui/progressEntries.ts +0 -0
- package/src/ui/runActivityView.ts +0 -0
- package/src/ui/runtimeDisplay.ts +100 -0
- package/src/ui/slashCommands.ts +0 -0
- package/src/ui/terminalAnswerFormat.ts +0 -0
- package/src/ui/textLayout.ts +0 -0
- package/src/ui/theme.tsx +274 -395
- package/src/ui/themeFlow.ts +0 -0
- package/src/ui/timelineMeasure.ts +29 -19
- package/src/ui/useThrottledValue.ts +0 -0
- package/scripts/audit-codexa-capabilities.mjs +0 -466
- package/scripts/smoke-terminal-bench.mjs +0 -35
- package/src/appRenderStability.test.ts +0 -131
- package/src/commands/handler.test.ts +0 -643
- package/src/config/launchArgs.test.ts +0 -158
- package/src/config/layeredConfig.test.ts +0 -143
- package/src/config/persistence.test.ts +0 -110
- package/src/config/runtimeConfig.test.ts +0 -218
- package/src/config/settings.test.ts +0 -155
- package/src/config/trustStore.test.ts +0 -29
- package/src/core/attachments.test.ts +0 -155
- package/src/core/auth/codexAuth.test.ts +0 -68
- package/src/core/cleanupFastFail.test.ts +0 -76
- package/src/core/codexExecArgs.test.ts +0 -195
- package/src/core/codexLaunch.test.ts +0 -205
- package/src/core/codexPrompt.test.ts +0 -252
- package/src/core/executables/codexExecutable.test.ts +0 -212
- package/src/core/executables/executableResolver.test.ts +0 -129
- package/src/core/executables/geminiExecutable.test.ts +0 -116
- package/src/core/executables/pathSanityScan.test.ts +0 -47
- package/src/core/githubDiagnostics.test.ts +0 -92
- package/src/core/hollowResponseFormat.test.ts +0 -58
- package/src/core/launchContext.test.ts +0 -157
- package/src/core/models/codexCapabilities.test.ts +0 -45
- package/src/core/models/codexModelCapabilities.test.ts +0 -246
- package/src/core/models/modelSpecs.test.ts +0 -283
- package/src/core/perf/renderDebug.test.ts +0 -230
- package/src/core/planStorage.test.ts +0 -143
- package/src/core/process/CommandRunner.test.ts +0 -105
- package/src/core/projectInstructions.test.ts +0 -50
- package/src/core/providerLauncher/launcher.test.ts +0 -238
- package/src/core/providerLauncher/registry.test.ts +0 -324
- package/src/core/providerLauncher/workspaceConfig.test.ts +0 -638
- package/src/core/providerRuntime/anthropic.test.ts +0 -1120
- package/src/core/providerRuntime/capabilityProfile.test.ts +0 -311
- package/src/core/providerRuntime/contextMetadata.test.ts +0 -468
- package/src/core/providerRuntime/gemini.test.ts +0 -437
- package/src/core/providerRuntime/lmstudio.test.ts +0 -168
- package/src/core/providerRuntime/local.test.ts +0 -787
- package/src/core/providerRuntime/registry.test.ts +0 -233
- package/src/core/providers/codexJsonStream.test.ts +0 -148
- package/src/core/providers/codexSubprocess.test.ts +0 -68
- package/src/core/providers/codexTranscript.test.ts +0 -284
- package/src/core/terminal/terminalCapabilities.test.ts +0 -93
- package/src/core/terminal/terminalControl.test.ts +0 -75
- package/src/core/terminal/terminalSanitize.test.ts +0 -22
- package/src/core/terminal/terminalSelection.test.ts +0 -42
- package/src/core/terminal/terminalTitle.test.ts +0 -328
- package/src/core/workspaceActivity.test.ts +0 -163
- package/src/core/workspaceGuard.test.ts +0 -151
- package/src/core/workspaceRoot.test.ts +0 -23
- package/src/exec.test.ts +0 -13
- package/src/headless/execArgs.test.ts +0 -147
- package/src/headless/execRunner.test.ts +0 -434
- package/src/index.test.tsx +0 -618
- package/src/session/appSession.test.ts +0 -897
- package/src/session/chatLifecycle.test.ts +0 -64
- package/src/session/liveRenderScheduler.test.ts +0 -201
- package/src/session/planFlow.test.ts +0 -103
- package/src/session/planTranscript.test.ts +0 -65
- package/src/session/promptRunSchedule.test.ts +0 -36
- package/src/ui/ActivityIndicator.test.tsx +0 -58
- package/src/ui/AgentBlock.test.ts +0 -6
- package/src/ui/AnimatedStatusText.test.ts +0 -16
- package/src/ui/AppShell.test.tsx +0 -1739
- package/src/ui/AttachmentImportPanel.test.tsx +0 -204
- package/src/ui/BottomComposer.test.ts +0 -674
- package/src/ui/CodexLogo.tsx +0 -55
- package/src/ui/Markdown.test.ts +0 -157
- package/src/ui/ModelPickerProviderScope.test.tsx +0 -411
- package/src/ui/ModelPickerScreen.test.tsx +0 -99
- package/src/ui/ModelPickerState.test.tsx +0 -151
- package/src/ui/ModelReasoningPicker.test.tsx +0 -447
- package/src/ui/PlanReviewPanel.test.tsx +0 -267
- package/src/ui/PromptCardBorder.test.tsx +0 -161
- package/src/ui/ProviderPicker.test.tsx +0 -289
- package/src/ui/ProviderShortcut.test.tsx +0 -143
- package/src/ui/SettingsPanel.test.tsx +0 -233
- package/src/ui/Timeline.test.ts +0 -2067
- package/src/ui/TimelineNavigation.test.tsx +0 -201
- package/src/ui/TopHeader.test.tsx +0 -239
- package/src/ui/TurnGroup.test.tsx +0 -365
- package/src/ui/busyStatusAnimation.test.ts +0 -30
- package/src/ui/commandNormalize.test.ts +0 -142
- package/src/ui/diffRenderer.test.ts +0 -102
- package/src/ui/focusFlow.test.tsx +0 -1098
- package/src/ui/inputBuffer.test.ts +0 -151
- package/src/ui/layout.test.ts +0 -145
- package/src/ui/modeDisplay.test.ts +0 -42
- package/src/ui/runActivityView.test.ts +0 -89
- package/src/ui/runLifecycleView.test.tsx +0 -237
- package/src/ui/statusRenderIsolation.test.tsx +0 -654
- package/src/ui/terminalAnswerFormat.test.ts +0 -19
- package/src/ui/textLayout.test.ts +0 -18
- package/src/ui/themeFlow.test.ts +0 -53
- 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,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
|
-
});
|