@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,189 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import test from "node:test";
3
- import { parseLaunchArgs } from "./launchArgs.js";
4
-
5
- test("parses profile and repeated config overrides", () => {
6
- const parsed = parseLaunchArgs([
7
- "--profile",
8
- "review",
9
- "--config",
10
- "model=\"gpt-5.4\"",
11
- "-c",
12
- "codexa.mode=\"suggest\"",
13
- ]);
14
-
15
- assert.equal(parsed.ok, true);
16
- if (!parsed.ok) return;
17
-
18
- assert.equal(parsed.value.help, false);
19
- assert.equal(parsed.value.version, false);
20
- assert.equal(parsed.value.initialPrompt, null);
21
- assert.equal(parsed.value.profile, "review");
22
- assert.deepEqual(parsed.value.configOverrides, [
23
- "model=\"gpt-5.4\"",
24
- "codexa.mode=\"suggest\"",
25
- ]);
26
- assert.deepEqual(parsed.value.passthroughArgs, [
27
- "--profile",
28
- "review",
29
- "--config",
30
- "model=\"gpt-5.4\"",
31
- "-c",
32
- "codexa.mode=\"suggest\"",
33
- ]);
34
- });
35
-
36
- test("rejects missing profile values", () => {
37
- const parsed = parseLaunchArgs(["--profile"]);
38
- assert.equal(parsed.ok, false);
39
- if (parsed.ok) return;
40
- assert.match(parsed.error, /--profile/i);
41
- });
42
-
43
- test("rejects malformed config payloads", () => {
44
- const parsed = parseLaunchArgs(["--config", "model"]);
45
- assert.equal(parsed.ok, false);
46
- if (parsed.ok) return;
47
- assert.match(parsed.error, /key=value/i);
48
- });
49
-
50
- test("parses inline profile and config assignments", () => {
51
- const parsed = parseLaunchArgs([
52
- "--profile=review",
53
- "--config=model=\"gpt-5.4-mini\"",
54
- "-c=codexa.mode=\"auto-edit\"",
55
- ]);
56
-
57
- assert.equal(parsed.ok, true);
58
- if (!parsed.ok) return;
59
-
60
- assert.equal(parsed.value.profile, "review");
61
- assert.deepEqual(parsed.value.configOverrides, [
62
- "model=\"gpt-5.4-mini\"",
63
- "codexa.mode=\"auto-edit\"",
64
- ]);
65
- assert.deepEqual(parsed.value.passthroughArgs, [
66
- "--profile=review",
67
- "--config=model=\"gpt-5.4-mini\"",
68
- "-c=codexa.mode=\"auto-edit\"",
69
- ]);
70
- });
71
-
72
- test("parses model flags as runtime config overrides", () => {
73
- const parsed = parseLaunchArgs([
74
- "--model",
75
- "gpt-5.3-codex",
76
- "-m",
77
- "gpt-5.4-mini",
78
- "Say",
79
- "READY",
80
- "only.",
81
- ]);
82
-
83
- assert.equal(parsed.ok, true);
84
- if (!parsed.ok) return;
85
-
86
- assert.equal(parsed.value.initialPrompt, "Say READY only.");
87
- assert.deepEqual(parsed.value.configOverrides, [
88
- "model=\"gpt-5.3-codex\"",
89
- "model=\"gpt-5.4-mini\"",
90
- ]);
91
- assert.deepEqual(parsed.value.passthroughArgs, [
92
- "--model",
93
- "gpt-5.3-codex",
94
- "-m",
95
- "gpt-5.4-mini",
96
- ]);
97
- // Last --model flag wins (same order as layered config)
98
- assert.equal(parsed.value.modelOverride, "gpt-5.4-mini");
99
- });
100
-
101
- test("parses inline model flag assignment", () => {
102
- const parsed = parseLaunchArgs(["--model=gpt-5.5", "compare", "this"]);
103
-
104
- assert.equal(parsed.ok, true);
105
- if (!parsed.ok) return;
106
-
107
- assert.equal(parsed.value.initialPrompt, "compare this");
108
- assert.deepEqual(parsed.value.configOverrides, ["model=\"gpt-5.5\""]);
109
- assert.deepEqual(parsed.value.passthroughArgs, ["--model=gpt-5.5"]);
110
- assert.equal(parsed.value.modelOverride, "gpt-5.5");
111
- });
112
-
113
- test("modelOverride is null when no --model flag is given", () => {
114
- const parsed = parseLaunchArgs(["--profile", "review", "some", "prompt"]);
115
-
116
- assert.equal(parsed.ok, true);
117
- if (!parsed.ok) return;
118
-
119
- assert.equal(parsed.value.modelOverride, null);
120
- });
121
-
122
- test("rejects missing model flag values", () => {
123
- const parsed = parseLaunchArgs(["--model"]);
124
- assert.equal(parsed.ok, false);
125
- if (parsed.ok) return;
126
- assert.match(parsed.error, /--model/i);
127
- });
128
-
129
- test("parses help and version flags without passthrough", () => {
130
- const parsed = parseLaunchArgs(["--help", "-v"]);
131
- assert.equal(parsed.ok, true);
132
- if (!parsed.ok) return;
133
-
134
- assert.equal(parsed.value.help, true);
135
- assert.equal(parsed.value.version, true);
136
- assert.equal(parsed.value.initialPrompt, null);
137
- assert.deepEqual(parsed.value.passthroughArgs, []);
138
- });
139
-
140
- test("parses positional arguments as an initial prompt", () => {
141
- const parsed = parseLaunchArgs(["--profile", "review", "explain", "this", "repo"]);
142
- assert.equal(parsed.ok, true);
143
- if (!parsed.ok) return;
144
-
145
- assert.equal(parsed.value.profile, "review");
146
- assert.equal(parsed.value.initialPrompt, "explain this repo");
147
- assert.deepEqual(parsed.value.passthroughArgs, ["--profile", "review"]);
148
- });
149
-
150
- test("parses arguments after -- as an initial prompt", () => {
151
- const parsed = parseLaunchArgs(["--", "--help", "as", "text"]);
152
- assert.equal(parsed.ok, true);
153
- if (!parsed.ok) return;
154
-
155
- assert.equal(parsed.value.help, false);
156
- assert.equal(parsed.value.initialPrompt, "--help as text");
157
- assert.deepEqual(parsed.value.passthroughArgs, []);
158
- });
159
-
160
- test("--no-clear sets noClear to true", () => {
161
- const parsed = parseLaunchArgs(["--no-clear"]);
162
- assert.equal(parsed.ok, true);
163
- if (!parsed.ok) return;
164
- assert.equal(parsed.value.noClear, true);
165
- });
166
-
167
- test("noClear defaults to false when --no-clear is absent", () => {
168
- const parsed = parseLaunchArgs(["--profile", "review"]);
169
- assert.equal(parsed.ok, true);
170
- if (!parsed.ok) return;
171
- assert.equal(parsed.value.noClear, false);
172
- });
173
-
174
- test("--no-clear does not consume the next argument as a value", () => {
175
- const parsed = parseLaunchArgs(["--no-clear", "my prompt here"]);
176
- assert.equal(parsed.ok, true);
177
- if (!parsed.ok) return;
178
- assert.equal(parsed.value.noClear, true);
179
- assert.equal(parsed.value.initialPrompt, "my prompt here");
180
- });
181
-
182
- test("--no-clear can be combined with other flags", () => {
183
- const parsed = parseLaunchArgs(["--no-clear", "--profile", "fast", "do something"]);
184
- assert.equal(parsed.ok, true);
185
- if (!parsed.ok) return;
186
- assert.equal(parsed.value.noClear, true);
187
- assert.equal(parsed.value.profile, "fast");
188
- assert.equal(parsed.value.initialPrompt, "do something");
189
- });
@@ -1,143 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "fs";
3
- import { tmpdir } from "os";
4
- import { join } from "path";
5
- import test from "node:test";
6
-
7
- function writeText(filePath: string, contents: string): void {
8
- mkdirSync(join(filePath, ".."), { recursive: true });
9
- writeFileSync(filePath, contents, "utf-8");
10
- }
11
-
12
- test("resolves user config, trusted project config, profiles, and CLI overrides deterministically", async () => {
13
- const tempRoot = mkdtempSync(join(tmpdir(), "codexa-layered-config-"));
14
- const tempHome = join(tempRoot, "home");
15
- const workspaceRoot = join(tempRoot, "repo", "packages", "app");
16
- const projectRoot = join(tempRoot, "repo");
17
- const previousCodexHome = process.env.CODEX_HOME;
18
- process.env.CODEX_HOME = tempHome;
19
-
20
- try {
21
- mkdirSync(join(projectRoot, ".git"), { recursive: true });
22
- mkdirSync(join(workspaceRoot, ".codex"), { recursive: true });
23
- mkdirSync(join(projectRoot, ".codex"), { recursive: true });
24
- mkdirSync(tempHome, { recursive: true });
25
-
26
- writeFileSync(join(tempHome, "config.toml"), [
27
- "model = \"gpt-5.2\"",
28
- "[codexa]",
29
- "backend = \"openai-native\"",
30
- "",
31
- "[profiles.review]",
32
- "service_tier = \"fast\"",
33
- ].join("\n"), "utf-8");
34
-
35
- writeFileSync(join(projectRoot, ".codex", "config.toml"), [
36
- "model = \"gpt-5.4\"",
37
- "profile = \"review\"",
38
- "[codexa]",
39
- "mode = \"suggest\"",
40
- "",
41
- "[profiles.review]",
42
- "approval_policy = \"never\"",
43
- "",
44
- "[profiles.review.sandbox_workspace_write]",
45
- "network_access = true",
46
- "writable_roots = [\"./roots/project\"]",
47
- ].join("\n"), "utf-8");
48
-
49
- writeFileSync(join(workspaceRoot, ".codex", "config.toml"), [
50
- "personality = \"pragmatic\"",
51
- "",
52
- "[profiles.review]",
53
- "model_reasoning_effort = \"high\"",
54
- ].join("\n"), "utf-8");
55
-
56
- const trustStore = await import(`./trustStore.js?layered-trust=${Date.now()}`);
57
- trustStore.setProjectTrust(projectRoot, true);
58
-
59
- const layeredConfig = await import(`./layeredConfig.js?layered=${Date.now()}`);
60
- const result = layeredConfig.resolveLayeredConfig({
61
- workspaceRoot,
62
- launchArgs: {
63
- help: false,
64
- version: false,
65
- initialPrompt: null,
66
- profile: null,
67
- configOverrides: [
68
- "model=\"gpt-5.4-mini\"",
69
- "codexa.mode=\"full-auto\"",
70
- "mcp.enabled=true",
71
- ],
72
- passthroughArgs: [],
73
- modelOverride: "gpt-5.4-mini",
74
- },
75
- });
76
-
77
- assert.equal(result.runtime.provider, "openai-native");
78
- assert.equal(result.runtime.model, "gpt-5.4-mini");
79
- assert.equal(result.runtime.reasoningLevel, "high");
80
- assert.equal(result.runtime.mode, "full-auto");
81
- assert.equal(result.runtime.policy.approvalPolicy, "never");
82
- assert.equal(result.runtime.policy.networkAccess, "enabled");
83
- assert.equal(result.runtime.policy.serviceTier, "fast");
84
- assert.equal(result.runtime.policy.personality, "pragmatic");
85
- assert.equal(result.diagnostics.selectedProfile, "review");
86
- assert.equal(result.diagnostics.selectedProfileSource, "Project config");
87
- assert.equal(result.diagnostics.projectTrusted, true);
88
- assert.match(result.runtime.policy.writableRoots[0] ?? "", /roots[\\/]project/i);
89
- assert.match(result.diagnostics.fieldSources.model, /CLI override/i);
90
- assert.match(result.diagnostics.fieldSources["policy.approvalPolicy"], /Profile review from Project config/i);
91
- assert.ok(result.diagnostics.ignoredEntries.some((entry: string) => /mcp\.enabled/i.test(entry)));
92
- } finally {
93
- if (previousCodexHome === undefined) {
94
- delete process.env.CODEX_HOME;
95
- } else {
96
- process.env.CODEX_HOME = previousCodexHome;
97
- }
98
- rmSync(tempRoot, { recursive: true, force: true });
99
- }
100
- });
101
-
102
- test("blocks project config when the detected project root is untrusted", async () => {
103
- const tempRoot = mkdtempSync(join(tmpdir(), "codexa-layered-untrusted-"));
104
- const tempHome = join(tempRoot, "home");
105
- const workspaceRoot = join(tempRoot, "repo", "app");
106
- const projectRoot = join(tempRoot, "repo");
107
- const previousCodexHome = process.env.CODEX_HOME;
108
- process.env.CODEX_HOME = tempHome;
109
-
110
- try {
111
- mkdirSync(join(projectRoot, ".git"), { recursive: true });
112
- mkdirSync(join(projectRoot, ".codex"), { recursive: true });
113
- mkdirSync(tempHome, { recursive: true });
114
-
115
- writeFileSync(join(tempHome, "config.toml"), "model = \"gpt-5.2\"\n", "utf-8");
116
- writeFileSync(join(projectRoot, ".codex", "config.toml"), "model = \"gpt-5.4-mini\"\n", "utf-8");
117
-
118
- const layeredConfig = await import(`./layeredConfig.js?untrusted=${Date.now()}`);
119
- const result = layeredConfig.resolveLayeredConfig({
120
- workspaceRoot,
121
- launchArgs: {
122
- help: false,
123
- version: false,
124
- initialPrompt: null,
125
- profile: null,
126
- configOverrides: [],
127
- passthroughArgs: [],
128
- modelOverride: null,
129
- },
130
- });
131
-
132
- assert.equal(result.runtime.model, "gpt-5.2");
133
- assert.equal(result.diagnostics.projectTrusted, false);
134
- assert.ok(result.diagnostics.layers.some((layer: { status: string }) => layer.status === "blocked"));
135
- } finally {
136
- if (previousCodexHome === undefined) {
137
- delete process.env.CODEX_HOME;
138
- } else {
139
- process.env.CODEX_HOME = previousCodexHome;
140
- }
141
- rmSync(tempRoot, { recursive: true, force: true });
142
- }
143
- });
@@ -1,114 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import test from "node:test";
3
- import { mergeRuntimeIntoTomlConfig } from "./layeredConfig.js";
4
- import { DEFAULT_RUNTIME_CONFIG } from "./runtimeConfig.js";
5
- import {
6
- extractLegacyRuntime,
7
- getDefaultSettings,
8
- parseSettingsData,
9
- serializeSettings,
10
- } from "./persistence.js";
11
-
12
- test("extracts legacy flat runtime settings for migration", () => {
13
- const runtime = extractLegacyRuntime({
14
- backend: "codex-subprocess",
15
- model: "gpt-5.4-mini",
16
- mode: "suggest",
17
- reasoning_level: "medium",
18
- });
19
-
20
- assert.equal(runtime?.model, "gpt-5.4-mini");
21
- assert.equal(runtime?.mode, "suggest");
22
- assert.equal(runtime?.reasoningLevel, "medium");
23
- });
24
-
25
- test("keeps UI and auth settings separate from runtime persistence", () => {
26
- const initial = {
27
- ui: {
28
- layoutStyle: "gemini-shell",
29
- theme: "purple",
30
- workspaceDisplayMode: "simple" as const,
31
- terminalTitleMode: "name" as const,
32
- showBusyLoader: false,
33
- terminalMouseMode: "selection" as const,
34
- customTheme: { TEXT: "#fff" },
35
- },
36
- auth: {
37
- preference: "runner-managed" as const,
38
- },
39
- header: {
40
- showBrand: true,
41
- showWorkspace: true,
42
- showProvider: true,
43
- showModel: true,
44
- showReasoning: false,
45
- showContext: false,
46
- showAuthStatus: false,
47
- },
48
- updateCheck: {
49
- enabled: true,
50
- intervalHours: 6,
51
- },
52
- };
53
-
54
- const serialized = serializeSettings(initial);
55
- const parsed = parseSettingsData(serialized);
56
-
57
- assert.equal("runtime" in serialized, false);
58
- assert.equal(parsed.ui.theme, "purple");
59
- assert.equal(parsed.ui.workspaceDisplayMode, "simple");
60
- assert.equal(parsed.ui.terminalTitleMode, "name");
61
- assert.equal(parsed.ui.showBusyLoader, false);
62
- assert.equal(parsed.ui.terminalMouseMode, "selection");
63
- assert.equal(parsed.auth.preference, "runner-managed");
64
- });
65
-
66
- test("falls back to defaults for missing UI or auth preferences", () => {
67
- const parsed = parseSettingsData({});
68
- const defaults = getDefaultSettings();
69
-
70
- assert.equal(parsed.ui.layoutStyle, defaults.ui.layoutStyle);
71
- assert.equal(parsed.ui.theme, defaults.ui.theme);
72
- assert.equal(parsed.ui.workspaceDisplayMode, defaults.ui.workspaceDisplayMode);
73
- assert.equal(parsed.ui.terminalTitleMode, defaults.ui.terminalTitleMode);
74
- assert.equal(parsed.ui.showBusyLoader, defaults.ui.showBusyLoader);
75
- assert.equal(parsed.auth.preference, defaults.auth.preference);
76
- });
77
-
78
- test("maps legacy directory display settings into workspace display mode", () => {
79
- assert.equal(parseSettingsData({ directory_display_mode: "normal" }).ui.workspaceDisplayMode, "dir");
80
- assert.equal(parseSettingsData({ directoryDisplayMode: "simple" }).ui.workspaceDisplayMode, "simple");
81
- });
82
-
83
- test("parses workspace display and busy loader from camel and snake case", () => {
84
- assert.equal(parseSettingsData({ workspace_display_mode: "name", terminal_title_mode: "simple", show_busy_loader: false }).ui.workspaceDisplayMode, "name");
85
- assert.equal(parseSettingsData({ workspaceDisplayMode: "dir", terminalTitleMode: "name", showBusyLoader: true }).ui.showBusyLoader, true);
86
- assert.equal(parseSettingsData({ terminalTitleMode: "name" }).ui.terminalTitleMode, "name");
87
- });
88
-
89
- test("merges legacy runtime fields into TOML without overwriting existing values", () => {
90
- const merged = mergeRuntimeIntoTomlConfig({
91
- model: "gpt-5.4",
92
- codexa: {
93
- mode: "suggest",
94
- },
95
- }, {
96
- ...DEFAULT_RUNTIME_CONFIG,
97
- model: "gpt-5.4-mini",
98
- mode: "full-auto",
99
- policy: {
100
- ...DEFAULT_RUNTIME_CONFIG.policy,
101
- networkAccess: "enabled",
102
- writableRoots: ["C:\\safe"],
103
- personality: "pragmatic",
104
- },
105
- });
106
-
107
- assert.equal(merged.model, "gpt-5.4");
108
- assert.deepEqual(merged.codexa, { mode: "suggest" });
109
- assert.deepEqual(merged.sandbox_workspace_write, {
110
- network_access: true,
111
- writable_roots: ["C:\\safe"],
112
- });
113
- assert.equal(merged.personality, "pragmatic");
114
- });
@@ -1,218 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import test from "node:test";
3
- import {
4
- DEFAULT_RUNTIME_CONFIG,
5
- addWritableRoot,
6
- buildRuntimeSummary,
7
- buildCodexConfigOverrides,
8
- diffRuntimeConfig,
9
- formatPermissionsStatus,
10
- formatRuntimeStatus,
11
- mergeRuntimeConfig,
12
- normalizeRuntimeConfig,
13
- removeWritableRoot,
14
- resolveRuntimeConfig,
15
- } from "./runtimeConfig.js";
16
-
17
- test("defaults resolve into a concrete runtime config", () => {
18
- const resolved = resolveRuntimeConfig(DEFAULT_RUNTIME_CONFIG);
19
-
20
- assert.equal(resolved.model, DEFAULT_RUNTIME_CONFIG.model);
21
- assert.equal(resolved.mode, DEFAULT_RUNTIME_CONFIG.mode);
22
- assert.equal(resolved.planMode, false);
23
- assert.equal(resolved.policy.approvalPolicy, "on-request");
24
- assert.equal(resolved.policy.sandboxMode, "workspace-write");
25
- assert.equal(resolved.policy.networkAccess, false);
26
- assert.equal(resolved.policy.serviceTier, "flex");
27
- assert.equal(resolved.policy.personality, "none");
28
- });
29
-
30
- test("mode inheritance resolves to expected approval and sandbox policies", () => {
31
- const suggest = resolveRuntimeConfig(normalizeRuntimeConfig({ mode: "suggest" }));
32
- const autoEdit = resolveRuntimeConfig(normalizeRuntimeConfig({ mode: "auto-edit" }));
33
- const fullAuto = resolveRuntimeConfig(normalizeRuntimeConfig({ mode: "full-auto" }));
34
-
35
- assert.equal(suggest.policy.sandboxMode, "read-only");
36
- assert.equal(suggest.policy.approvalPolicy, "on-request");
37
- assert.equal(autoEdit.policy.sandboxMode, "workspace-write");
38
- assert.equal(fullAuto.policy.sandboxMode, "workspace-write");
39
- });
40
-
41
- test("explicit policy overrides beat inherited mode defaults", () => {
42
- const resolved = resolveRuntimeConfig(normalizeRuntimeConfig({
43
- mode: "suggest",
44
- policy: {
45
- approvalPolicy: "never",
46
- sandboxMode: "danger-full-access",
47
- networkAccess: "enabled",
48
- },
49
- }));
50
-
51
- assert.equal(resolved.policy.approvalPolicy, "never");
52
- assert.equal(resolved.policy.sandboxMode, "danger-full-access");
53
- assert.equal(resolved.policy.networkAccess, true);
54
- });
55
-
56
- test("writable roots normalize and dedupe", () => {
57
- const withRoots = addWritableRoot(addWritableRoot(DEFAULT_RUNTIME_CONFIG, "C:/Repo"), "C:\\Repo\\");
58
- assert.equal(withRoots.policy.writableRoots.length, 1);
59
-
60
- const removed = removeWritableRoot(withRoots, "c:/repo");
61
- assert.equal(removed.policy.writableRoots.length, 0);
62
- });
63
-
64
- test("writable root normalization strips redundant trailing separators", () => {
65
- const normalized = normalizeRuntimeConfig({
66
- policy: {
67
- writableRoots: ["C:\\Repo\\", "C:\\Repo", "C:\\Repo\\\\nested\\..\\"],
68
- },
69
- });
70
-
71
- assert.deepEqual(normalized.policy.writableRoots, ["C:\\Repo"]);
72
- });
73
-
74
- test("merges runtime overrides onto a canonical base", () => {
75
- const merged = mergeRuntimeConfig(DEFAULT_RUNTIME_CONFIG, {
76
- model: "gpt-5.4-mini",
77
- planMode: true,
78
- policy: {
79
- writableRoots: ["C:/Repo/extra"],
80
- serviceTier: "fast",
81
- },
82
- });
83
-
84
- assert.equal(merged.model, "gpt-5.4-mini");
85
- assert.equal(merged.planMode, true);
86
- assert.deepEqual(merged.policy.writableRoots, ["C:\\Repo\\extra"]);
87
- assert.equal(merged.policy.serviceTier, "fast");
88
- });
89
-
90
- test("diffRuntimeConfig emits only fields that differ from the base", () => {
91
- const target = normalizeRuntimeConfig({
92
- model: "gpt-5.4-mini",
93
- planMode: true,
94
- policy: {
95
- networkAccess: "enabled",
96
- personality: "pragmatic",
97
- },
98
- });
99
-
100
- assert.deepEqual(diffRuntimeConfig(DEFAULT_RUNTIME_CONFIG, target), {
101
- model: "gpt-5.4-mini",
102
- planMode: true,
103
- policy: {
104
- networkAccess: "enabled",
105
- personality: "pragmatic",
106
- },
107
- });
108
- });
109
-
110
- test("builds deterministic codex config overrides", () => {
111
- const resolved = resolveRuntimeConfig(normalizeRuntimeConfig({
112
- model: "gpt-5.4-mini",
113
- mode: "suggest",
114
- reasoningLevel: "medium",
115
- policy: {
116
- approvalPolicy: "never",
117
- sandboxMode: "workspace-write",
118
- networkAccess: "enabled",
119
- writableRoots: ["C:/Repo/extra"],
120
- serviceTier: "fast",
121
- personality: "pragmatic",
122
- },
123
- }));
124
- const writableRoot = resolved.policy.writableRoots[0];
125
-
126
- assert.deepEqual(buildCodexConfigOverrides(resolved), [
127
- "model_reasoning_effort=medium",
128
- "approval_policy=never",
129
- "sandbox_workspace_write.network_access=true",
130
- `sandbox_workspace_write.writable_roots=${JSON.stringify([writableRoot])}`,
131
- "service_tier=fast",
132
- "personality=pragmatic",
133
- ]);
134
- });
135
-
136
- test("builds runtime summary with network and writable-root indicators", () => {
137
- const summary = buildRuntimeSummary(resolveRuntimeConfig(normalizeRuntimeConfig({
138
- policy: {
139
- networkAccess: "enabled",
140
- writableRoots: ["C:/Repo/extra"],
141
- },
142
- })));
143
-
144
- assert.equal(summary.networkLabel, "Net: on");
145
- assert.equal(summary.writableRootsLabel, "Roots: 1");
146
- });
147
-
148
- test("formats permissions status with configured and effective policy", () => {
149
- const runtime = normalizeRuntimeConfig({
150
- mode: "suggest",
151
- policy: {
152
- approvalPolicy: "inherit",
153
- sandboxMode: "inherit",
154
- networkAccess: "enabled",
155
- writableRoots: ["C:/Repo/extra"],
156
- },
157
- });
158
- const status = formatPermissionsStatus(runtime, resolveRuntimeConfig(runtime), "C:\\Workspace");
159
-
160
- assert.match(status, /Permissions status:/);
161
- assert.match(status, /Approval policy: configured Inherit; effective On request/);
162
- assert.match(status, /Network access: configured Enabled; effective Enabled/);
163
- assert.match(status, /Configured:/);
164
- assert.match(status, /Effective:/);
165
- });
166
-
167
- test("formats runtime status with effective policy details", () => {
168
- const resolved = resolveRuntimeConfig(DEFAULT_RUNTIME_CONFIG);
169
- const status = formatRuntimeStatus(resolved, {
170
- workspaceRoot: "C:\\Workspace",
171
- tokensUsed: 512,
172
- });
173
-
174
- assert.match(status, /Provider:/);
175
- assert.match(status, /Plan mode: Disabled/);
176
- assert.match(status, /Approval policy:/);
177
- assert.match(status, /Sandbox mode:/);
178
- assert.match(status, /Tokens used: ~512/);
179
- });
180
-
181
- test("formats runtime status with loaded project instructions", () => {
182
- const resolved = resolveRuntimeConfig(DEFAULT_RUNTIME_CONFIG);
183
- const status = formatRuntimeStatus(resolved, {
184
- workspaceRoot: "C:\\Workspace",
185
- projectInstructions: {
186
- status: "loaded",
187
- instructions: { path: "C:\\Workspace\\AGENTS.md", content: "# Instructions" },
188
- },
189
- });
190
-
191
- assert.match(status, /Project instructions: loaded/);
192
- assert.match(status, /Source: C:\\Workspace\\AGENTS\.md/);
193
- });
194
-
195
- test("formats runtime status with project instructions error", () => {
196
- const resolved = resolveRuntimeConfig(DEFAULT_RUNTIME_CONFIG);
197
- const status = formatRuntimeStatus(resolved, {
198
- workspaceRoot: "C:\\Workspace",
199
- projectInstructions: {
200
- status: "error",
201
- path: "C:\\Workspace\\AGENTS.md",
202
- message: "ENOENT: file not found",
203
- },
204
- });
205
-
206
- assert.match(status, /Project instructions: error/);
207
- assert.match(status, /ENOENT/);
208
- });
209
-
210
- test("formats runtime status with missing project instructions", () => {
211
- const resolved = resolveRuntimeConfig(DEFAULT_RUNTIME_CONFIG);
212
- const status = formatRuntimeStatus(resolved, {
213
- workspaceRoot: "C:\\Workspace",
214
- projectInstructions: { status: "missing" },
215
- });
216
-
217
- assert.match(status, /Project instructions: none/);
218
- });