@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.
Files changed (234) hide show
  1. package/README.md +391 -94
  2. package/package.json +14 -8
  3. package/src/app.tsx +196 -63
  4. package/src/commands/handler.ts +9 -4
  5. package/src/config/buildInfo.ts +3 -0
  6. package/src/config/launchArgs.ts +10 -0
  7. package/src/config/layeredConfig.ts +0 -0
  8. package/src/config/persistence.ts +51 -0
  9. package/src/config/runtimeConfig.ts +4 -0
  10. package/src/config/settings.ts +6 -13
  11. package/src/config/toml-serialize.ts +0 -0
  12. package/src/config/trustStore.ts +0 -0
  13. package/src/config/updateCheckCache.ts +63 -0
  14. package/src/core/attachments.ts +0 -0
  15. package/src/core/auth/codexAuth.ts +0 -0
  16. package/src/core/channel.ts +23 -0
  17. package/src/core/cleanupFastFail.ts +0 -0
  18. package/src/core/clipboard.ts +0 -0
  19. package/src/core/codex.ts +0 -0
  20. package/src/core/codexExecArgs.ts +0 -0
  21. package/src/core/codexLaunch.ts +0 -0
  22. package/src/core/codexPrompt.ts +0 -0
  23. package/src/core/executables/claudeExecutable.ts +0 -0
  24. package/src/core/executables/codexExecutable.ts +1 -0
  25. package/src/core/executables/executableResolver.ts +61 -43
  26. package/src/core/executables/geminiExecutable.ts +0 -0
  27. package/src/core/githubDiagnostics.ts +0 -0
  28. package/src/core/hollowResponseFormat.ts +0 -0
  29. package/src/core/inputDebug.ts +0 -0
  30. package/src/core/launchContext.ts +33 -33
  31. package/src/core/models/codexCapabilities.ts +0 -0
  32. package/src/core/models/codexModelCapabilities.ts +0 -0
  33. package/src/core/models/modelSpecs.ts +0 -0
  34. package/src/core/perf/profiler.ts +0 -0
  35. package/src/core/perf/renderDebug.ts +0 -0
  36. package/src/core/planStorage.ts +0 -0
  37. package/src/core/process/CommandRunner.ts +0 -0
  38. package/src/core/process/processValidation.ts +9 -5
  39. package/src/core/projectInstructions.ts +0 -0
  40. package/src/core/providerLauncher/launcher.ts +59 -42
  41. package/src/core/providerLauncher/registry.ts +0 -0
  42. package/src/core/providerLauncher/types.ts +0 -0
  43. package/src/core/providerLauncher/workspaceConfig.ts +0 -0
  44. package/src/core/providerRuntime/anthropic.ts +0 -0
  45. package/src/core/providerRuntime/capabilityProfile.ts +0 -0
  46. package/src/core/providerRuntime/claudeCodeDiscovery.ts +0 -0
  47. package/src/core/providerRuntime/contextMetadata.ts +0 -0
  48. package/src/core/providerRuntime/gemini.ts +0 -0
  49. package/src/core/providerRuntime/lmstudio.ts +0 -0
  50. package/src/core/providerRuntime/local.ts +0 -0
  51. package/src/core/providerRuntime/models.ts +0 -0
  52. package/src/core/providerRuntime/reasoning.ts +0 -0
  53. package/src/core/providerRuntime/registry.ts +0 -0
  54. package/src/core/providerRuntime/types.ts +0 -0
  55. package/src/core/providers/codexJsonStream.ts +0 -0
  56. package/src/core/providers/codexSubprocess.ts +0 -0
  57. package/src/core/providers/codexTranscript.ts +0 -0
  58. package/src/core/providers/openaiNative.ts +0 -0
  59. package/src/core/providers/registry.ts +0 -0
  60. package/src/core/providers/types.ts +0 -0
  61. package/src/core/terminal/startupClear.ts +20 -0
  62. package/src/core/terminal/terminalCapabilities.ts +0 -0
  63. package/src/core/terminal/terminalControl.ts +0 -0
  64. package/src/core/terminal/terminalSanitize.ts +0 -0
  65. package/src/core/terminal/terminalSelection.ts +0 -0
  66. package/src/core/terminal/terminalTitle.ts +0 -0
  67. package/src/core/updateCheck.ts +188 -0
  68. package/src/core/workspaceActivity.ts +0 -0
  69. package/src/core/workspaceGuard.ts +0 -0
  70. package/src/core/workspaceRoot.ts +0 -0
  71. package/src/exec.ts +0 -0
  72. package/src/headless/execArgs.ts +1 -0
  73. package/src/headless/execRunner.ts +0 -0
  74. package/src/index.tsx +13 -9
  75. package/src/session/appSession.ts +0 -0
  76. package/src/session/chatLifecycle.ts +0 -0
  77. package/src/session/liveRenderScheduler.ts +0 -0
  78. package/src/session/planFlow.ts +0 -0
  79. package/src/session/planTranscript.ts +0 -0
  80. package/src/session/promptRunSchedule.ts +0 -0
  81. package/src/session/types.ts +2 -1
  82. package/src/test/runtimeTestUtils.ts +0 -0
  83. package/src/types/react-dom.d.ts +0 -0
  84. package/src/ui/ActionRequiredBlock.tsx +5 -5
  85. package/src/ui/ActivityBars.tsx +3 -3
  86. package/src/ui/ActivityIndicator.tsx +6 -6
  87. package/src/ui/AgentBlock.tsx +6 -6
  88. package/src/ui/AnimatedStatusText.tsx +1 -1
  89. package/src/ui/AppShell.tsx +13 -5
  90. package/src/ui/AttachmentImportPanel.tsx +8 -8
  91. package/src/ui/AuthPanel.tsx +20 -20
  92. package/src/ui/BackendPicker.tsx +0 -0
  93. package/src/ui/BottomComposer.tsx +117 -78
  94. package/src/ui/DashCard.tsx +3 -3
  95. package/src/ui/Markdown.tsx +17 -17
  96. package/src/ui/ModePicker.tsx +0 -0
  97. package/src/ui/ModelPicker.tsx +0 -0
  98. package/src/ui/ModelPickerScreen.tsx +10 -10
  99. package/src/ui/ModelReasoningPicker.tsx +14 -14
  100. package/src/ui/Panel.tsx +3 -3
  101. package/src/ui/PermissionsPanel.tsx +0 -0
  102. package/src/ui/PlanActionPicker.tsx +6 -6
  103. package/src/ui/PlanReviewPanel.tsx +8 -8
  104. package/src/ui/ProviderPicker.tsx +18 -18
  105. package/src/ui/ReasoningPicker.tsx +0 -0
  106. package/src/ui/RunFooter.tsx +3 -3
  107. package/src/ui/SelectionPanel.tsx +4 -4
  108. package/src/ui/SettingsPanel.tsx +8 -8
  109. package/src/ui/Spinner.tsx +1 -1
  110. package/src/ui/StaticIntroItem.tsx +0 -0
  111. package/src/ui/StaticTranscriptItem.tsx +0 -0
  112. package/src/ui/TextEntryPanel.tsx +10 -10
  113. package/src/ui/ThemePicker.tsx +0 -0
  114. package/src/ui/ThinkingBlock.tsx +8 -8
  115. package/src/ui/Timeline.tsx +21 -17
  116. package/src/ui/TopHeader.tsx +421 -257
  117. package/src/ui/TurnGroup.tsx +31 -31
  118. package/src/ui/UpdateAvailableCard.tsx +41 -0
  119. package/src/ui/UpdatePromptPanel.tsx +203 -0
  120. package/src/ui/busyStatusAnimation.ts +0 -0
  121. package/src/ui/commandNormalize.ts +0 -0
  122. package/src/ui/diffRenderer.ts +0 -0
  123. package/src/ui/focus.ts +3 -0
  124. package/src/ui/inputBuffer.ts +0 -0
  125. package/src/ui/layout.ts +1 -1
  126. package/src/ui/logoVariants.ts +107 -0
  127. package/src/ui/modeDisplay.ts +12 -12
  128. package/src/ui/outputPipeline.ts +0 -0
  129. package/src/ui/progressEntries.ts +0 -0
  130. package/src/ui/runActivityView.ts +0 -0
  131. package/src/ui/runtimeDisplay.ts +100 -0
  132. package/src/ui/slashCommands.ts +0 -0
  133. package/src/ui/terminalAnswerFormat.ts +0 -0
  134. package/src/ui/textLayout.ts +0 -0
  135. package/src/ui/theme.tsx +274 -395
  136. package/src/ui/themeFlow.ts +0 -0
  137. package/src/ui/timelineMeasure.ts +29 -19
  138. package/src/ui/useThrottledValue.ts +0 -0
  139. package/scripts/audit-codexa-capabilities.mjs +0 -466
  140. package/scripts/smoke-terminal-bench.mjs +0 -35
  141. package/src/appRenderStability.test.ts +0 -131
  142. package/src/commands/handler.test.ts +0 -643
  143. package/src/config/launchArgs.test.ts +0 -158
  144. package/src/config/layeredConfig.test.ts +0 -143
  145. package/src/config/persistence.test.ts +0 -110
  146. package/src/config/runtimeConfig.test.ts +0 -218
  147. package/src/config/settings.test.ts +0 -155
  148. package/src/config/trustStore.test.ts +0 -29
  149. package/src/core/attachments.test.ts +0 -155
  150. package/src/core/auth/codexAuth.test.ts +0 -68
  151. package/src/core/cleanupFastFail.test.ts +0 -76
  152. package/src/core/codexExecArgs.test.ts +0 -195
  153. package/src/core/codexLaunch.test.ts +0 -205
  154. package/src/core/codexPrompt.test.ts +0 -252
  155. package/src/core/executables/codexExecutable.test.ts +0 -212
  156. package/src/core/executables/executableResolver.test.ts +0 -129
  157. package/src/core/executables/geminiExecutable.test.ts +0 -116
  158. package/src/core/executables/pathSanityScan.test.ts +0 -47
  159. package/src/core/githubDiagnostics.test.ts +0 -92
  160. package/src/core/hollowResponseFormat.test.ts +0 -58
  161. package/src/core/launchContext.test.ts +0 -157
  162. package/src/core/models/codexCapabilities.test.ts +0 -45
  163. package/src/core/models/codexModelCapabilities.test.ts +0 -246
  164. package/src/core/models/modelSpecs.test.ts +0 -283
  165. package/src/core/perf/renderDebug.test.ts +0 -230
  166. package/src/core/planStorage.test.ts +0 -143
  167. package/src/core/process/CommandRunner.test.ts +0 -105
  168. package/src/core/projectInstructions.test.ts +0 -50
  169. package/src/core/providerLauncher/launcher.test.ts +0 -238
  170. package/src/core/providerLauncher/registry.test.ts +0 -324
  171. package/src/core/providerLauncher/workspaceConfig.test.ts +0 -638
  172. package/src/core/providerRuntime/anthropic.test.ts +0 -1120
  173. package/src/core/providerRuntime/capabilityProfile.test.ts +0 -311
  174. package/src/core/providerRuntime/contextMetadata.test.ts +0 -468
  175. package/src/core/providerRuntime/gemini.test.ts +0 -437
  176. package/src/core/providerRuntime/lmstudio.test.ts +0 -168
  177. package/src/core/providerRuntime/local.test.ts +0 -787
  178. package/src/core/providerRuntime/registry.test.ts +0 -233
  179. package/src/core/providers/codexJsonStream.test.ts +0 -148
  180. package/src/core/providers/codexSubprocess.test.ts +0 -68
  181. package/src/core/providers/codexTranscript.test.ts +0 -284
  182. package/src/core/terminal/terminalCapabilities.test.ts +0 -93
  183. package/src/core/terminal/terminalControl.test.ts +0 -75
  184. package/src/core/terminal/terminalSanitize.test.ts +0 -22
  185. package/src/core/terminal/terminalSelection.test.ts +0 -42
  186. package/src/core/terminal/terminalTitle.test.ts +0 -328
  187. package/src/core/workspaceActivity.test.ts +0 -163
  188. package/src/core/workspaceGuard.test.ts +0 -151
  189. package/src/core/workspaceRoot.test.ts +0 -23
  190. package/src/exec.test.ts +0 -13
  191. package/src/headless/execArgs.test.ts +0 -147
  192. package/src/headless/execRunner.test.ts +0 -434
  193. package/src/index.test.tsx +0 -618
  194. package/src/session/appSession.test.ts +0 -897
  195. package/src/session/chatLifecycle.test.ts +0 -64
  196. package/src/session/liveRenderScheduler.test.ts +0 -201
  197. package/src/session/planFlow.test.ts +0 -103
  198. package/src/session/planTranscript.test.ts +0 -65
  199. package/src/session/promptRunSchedule.test.ts +0 -36
  200. package/src/ui/ActivityIndicator.test.tsx +0 -58
  201. package/src/ui/AgentBlock.test.ts +0 -6
  202. package/src/ui/AnimatedStatusText.test.ts +0 -16
  203. package/src/ui/AppShell.test.tsx +0 -1739
  204. package/src/ui/AttachmentImportPanel.test.tsx +0 -204
  205. package/src/ui/BottomComposer.test.ts +0 -674
  206. package/src/ui/CodexLogo.tsx +0 -55
  207. package/src/ui/Markdown.test.ts +0 -157
  208. package/src/ui/ModelPickerProviderScope.test.tsx +0 -411
  209. package/src/ui/ModelPickerScreen.test.tsx +0 -99
  210. package/src/ui/ModelPickerState.test.tsx +0 -151
  211. package/src/ui/ModelReasoningPicker.test.tsx +0 -447
  212. package/src/ui/PlanReviewPanel.test.tsx +0 -267
  213. package/src/ui/PromptCardBorder.test.tsx +0 -161
  214. package/src/ui/ProviderPicker.test.tsx +0 -289
  215. package/src/ui/ProviderShortcut.test.tsx +0 -143
  216. package/src/ui/SettingsPanel.test.tsx +0 -233
  217. package/src/ui/Timeline.test.ts +0 -2067
  218. package/src/ui/TimelineNavigation.test.tsx +0 -201
  219. package/src/ui/TopHeader.test.tsx +0 -239
  220. package/src/ui/TurnGroup.test.tsx +0 -365
  221. package/src/ui/busyStatusAnimation.test.ts +0 -30
  222. package/src/ui/commandNormalize.test.ts +0 -142
  223. package/src/ui/diffRenderer.test.ts +0 -102
  224. package/src/ui/focusFlow.test.tsx +0 -1098
  225. package/src/ui/inputBuffer.test.ts +0 -151
  226. package/src/ui/layout.test.ts +0 -145
  227. package/src/ui/modeDisplay.test.ts +0 -42
  228. package/src/ui/runActivityView.test.ts +0 -89
  229. package/src/ui/runLifecycleView.test.tsx +0 -237
  230. package/src/ui/statusRenderIsolation.test.tsx +0 -654
  231. package/src/ui/terminalAnswerFormat.test.ts +0 -19
  232. package/src/ui/textLayout.test.ts +0 -18
  233. package/src/ui/themeFlow.test.ts +0 -53
  234. package/src/ui/timelineMeasureCache.test.ts +0 -986
@@ -1,643 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import test from "node:test";
3
- import type { LayeredConfigResult } from "../config/layeredConfig.js";
4
- import { normalizeRuntimeConfig, resolveRuntimeConfig } from "../config/runtimeConfig.js";
5
- import { normalizeCodexModelListResponses } from "../core/models/codexModelCapabilities.js";
6
- import { handleCommand, type CommandContext } from "./handler.js";
7
-
8
- const baseRuntime = normalizeRuntimeConfig({
9
- provider: "codex-subprocess",
10
- model: "gpt-5.4",
11
- mode: "suggest",
12
- reasoningLevel: "high",
13
- });
14
-
15
- const baseConfig: LayeredConfigResult = {
16
- runtime: baseRuntime,
17
- diagnostics: {
18
- projectRoot: "C:\\Workspace",
19
- projectTrusted: false,
20
- selectedProfile: null,
21
- selectedProfileSource: null,
22
- cliOverrides: [],
23
- layers: [
24
- { label: "Built-in defaults", status: "loaded" as const },
25
- { label: "User config", status: "missing" as const, path: "C:\\Users\\Test\\.codex\\config.toml" },
26
- ],
27
- ignoredEntries: [],
28
- fieldSources: {
29
- provider: "Built-in defaults",
30
- model: "Built-in defaults",
31
- reasoningLevel: "Built-in defaults",
32
- mode: "Built-in defaults",
33
- planMode: "Built-in defaults",
34
- geminiCommandPath: "Built-in defaults",
35
- "policy.approvalPolicy": "Built-in defaults",
36
- "policy.sandboxMode": "Built-in defaults",
37
- "policy.networkAccess": "Built-in defaults",
38
- "policy.writableRoots": "Built-in defaults",
39
- "policy.serviceTier": "Built-in defaults",
40
- "policy.personality": "Built-in defaults",
41
- },
42
- },
43
- };
44
-
45
- const baseContext: CommandContext = {
46
- config: baseConfig,
47
- runtime: baseRuntime,
48
- resolvedRuntime: resolveRuntimeConfig(baseRuntime),
49
- settings: {
50
- workspaceDisplayMode: "dir",
51
- terminalTitleMode: "dir",
52
- showBusyLoader: true,
53
- },
54
- workspace: {
55
- root: "C:\\Workspace",
56
- summaryMessage: [
57
- "Active workspace:",
58
- " C:\\Workspace",
59
- "",
60
- "Launch mode: installed codexa",
61
- ].join("\n"),
62
- },
63
- tokensUsed: 1200,
64
- };
65
-
66
- function runCommand(command: string, context: Partial<CommandContext> = {}) {
67
- return handleCommand(command, {
68
- ...baseContext,
69
- ...context,
70
- });
71
- }
72
-
73
- const dynamicCapabilities = normalizeCodexModelListResponses([
74
- {
75
- data: [
76
- {
77
- id: "dynamic-four",
78
- model: "dynamic-four",
79
- displayName: "Dynamic Four",
80
- hidden: false,
81
- isDefault: true,
82
- defaultReasoningEffort: "medium",
83
- supportedReasoningEfforts: [
84
- { reasoningEffort: "low", description: "Low" },
85
- { reasoningEffort: "medium", description: "Medium" },
86
- { reasoningEffort: "high", description: "High" },
87
- { reasoningEffort: "xhigh", description: "Extra" },
88
- ],
89
- },
90
- {
91
- id: "dynamic-two",
92
- model: "dynamic-two",
93
- displayName: "Dynamic Two",
94
- hidden: false,
95
- isDefault: false,
96
- defaultReasoningEffort: "medium",
97
- supportedReasoningEfforts: [
98
- { reasoningEffort: "medium", description: "Medium" },
99
- { reasoningEffort: "high", description: "High" },
100
- ],
101
- },
102
- ],
103
- },
104
- ]);
105
-
106
- test("parses /login command", () => {
107
- const result = runCommand("/login");
108
- assert.equal(result?.action, "login");
109
- });
110
-
111
- test("parses /logout command", () => {
112
- const result = runCommand("/logout");
113
- assert.equal(result?.action, "logout");
114
- });
115
-
116
- test("parses /auth status as auth_status action", () => {
117
- const result = runCommand("/auth status");
118
- assert.equal(result?.action, "auth_status");
119
- });
120
-
121
- test("keeps existing /model command behavior", () => {
122
- const result = runCommand("/model gpt-5.4-mini");
123
- assert.equal(result?.action, "model");
124
- assert.equal(result?.value, "gpt-5.4-mini");
125
- });
126
-
127
- test("accepts dynamically detected model ids", () => {
128
- const result = runCommand("/model dynamic-two", {
129
- modelCapabilities: dynamicCapabilities,
130
- });
131
-
132
- assert.equal(result?.action, "model");
133
- assert.equal(result?.value, "dynamic-two");
134
- });
135
-
136
- test("resolves canonical and aliased /mode commands", () => {
137
- const cases = [
138
- ["/mode suggest", "suggest"],
139
- ["/mode auto-edit", "auto-edit"],
140
- ["/mode full-auto", "full-auto"],
141
- ["/mode default", "full-auto"],
142
- ["/mode ask", "suggest"],
143
- ["/mode auto", "auto-edit"],
144
- ["/mode plan", "suggest"],
145
- ] as const;
146
-
147
- for (const [command, expectedValue] of cases) {
148
- const result = runCommand(command);
149
- assert.equal(result?.action, "mode", command);
150
- assert.equal(result?.value, expectedValue, command);
151
- }
152
- });
153
-
154
- test("opens the reasoning picker when /reasoning has no argument", () => {
155
- const result = runCommand("/reasoning");
156
- assert.equal(result?.action, "open_reasoning_picker");
157
- });
158
-
159
- test("accepts explicit reasoning levels", () => {
160
- const result = runCommand("/reasoning medium");
161
- assert.equal(result?.action, "reasoning");
162
- assert.equal(result?.value, "medium");
163
- });
164
-
165
- test("accepts extra high reasoning aliases", () => {
166
- const result = runCommand("/reasoning extra high");
167
- assert.equal(result?.action, "reasoning");
168
- assert.equal(result?.value, "xhigh");
169
- });
170
-
171
- test("validates reasoning against detected levels for the active model", () => {
172
- const runtime = normalizeRuntimeConfig({
173
- model: "dynamic-two",
174
- reasoningLevel: "medium",
175
- });
176
-
177
- const accepted = runCommand("/reasoning high", {
178
- runtime,
179
- resolvedRuntime: resolveRuntimeConfig(runtime),
180
- modelCapabilities: dynamicCapabilities,
181
- });
182
- assert.equal(accepted?.action, "reasoning");
183
- assert.equal(accepted?.value, "high");
184
-
185
- const rejected = runCommand("/reasoning xhigh", {
186
- runtime,
187
- resolvedRuntime: resolveRuntimeConfig(runtime),
188
- modelCapabilities: dynamicCapabilities,
189
- });
190
- assert.equal(rejected?.action, "unknown");
191
- assert.match(rejected?.message ?? "", /Valid: medium, high/i);
192
- });
193
-
194
- test("shows and toggles plan mode", () => {
195
- const statusResult = runCommand("/plan");
196
- assert.equal(statusResult?.action, "plan_mode");
197
- assert.equal(statusResult?.message, "Plan mode: Disabled.");
198
-
199
- const explicitStatusResult = runCommand("/plan status", {
200
- runtime: normalizeRuntimeConfig({ planMode: true }),
201
- });
202
- assert.equal(explicitStatusResult?.action, "plan_mode");
203
- assert.equal(explicitStatusResult?.message, "Plan mode: Enabled.");
204
-
205
- const enableResult = runCommand("/plan on");
206
- assert.equal(enableResult?.action, "plan_mode");
207
- assert.equal(enableResult?.value, "on");
208
-
209
- const disableResult = runCommand("/plan off");
210
- assert.equal(disableResult?.action, "plan_mode");
211
- assert.equal(disableResult?.value, "off");
212
- });
213
-
214
- test("rejects invalid /plan usage with a short hint", () => {
215
- const result = runCommand("/plan maybe");
216
- assert.equal(result?.action, "unknown");
217
- assert.equal(result?.message, "Usage: /plan [on|off]");
218
- });
219
-
220
- test("opens the settings panel and updates workspace and busy loader settings", () => {
221
- const statusResult = runCommand("/setting");
222
- assert.equal(statusResult?.action, "open_settings_panel");
223
- assert.equal(statusResult?.message, undefined);
224
-
225
- const pluralStatusResult = runCommand("/settings");
226
- assert.equal(pluralStatusResult?.action, "open_settings_panel");
227
- assert.equal(pluralStatusResult?.message, undefined);
228
-
229
- const workspaceResult = runCommand("/setting workspace", {
230
- settings: {
231
- workspaceDisplayMode: "simple",
232
- terminalTitleMode: "name",
233
- showBusyLoader: true,
234
- },
235
- });
236
- assert.equal(workspaceResult?.action, "setting_workspace_display");
237
- assert.match(workspaceResult?.message ?? "", /Simple \(simple\)/i);
238
- assert.match(workspaceResult?.message ?? "", /Allowed values: dir, name, simple/i);
239
-
240
- const setResult = runCommand("/setting workspace name");
241
- assert.equal(setResult?.action, "setting_workspace_display");
242
- assert.equal(setResult?.value, "name");
243
-
244
- const terminalResult = runCommand("/setting terminal-title", {
245
- settings: {
246
- workspaceDisplayMode: "dir",
247
- terminalTitleMode: "simple",
248
- showBusyLoader: true,
249
- },
250
- });
251
- assert.equal(terminalResult?.action, "setting_terminal_title");
252
- assert.match(terminalResult?.message ?? "", /Simple \(simple\)/i);
253
-
254
- const setTerminalResult = runCommand("/setting terminal-title name");
255
- assert.equal(setTerminalResult?.action, "setting_terminal_title");
256
- assert.equal(setTerminalResult?.value, "name");
257
-
258
- const legacyResult = runCommand("/setting directory normal");
259
- assert.equal(legacyResult?.action, "setting_workspace_display");
260
- assert.equal(legacyResult?.value, "dir");
261
-
262
- const busyResult = runCommand("/setting busy-loader false");
263
- assert.equal(busyResult?.action, "setting_busy_loader");
264
- assert.equal(busyResult?.value, "false");
265
- });
266
-
267
- test("recognizes /settings without falling through to unknown command handling", () => {
268
- const result = runCommand("/settings");
269
-
270
- assert.equal(result?.action, "open_settings_panel");
271
- assert.notEqual(result?.action, "unknown");
272
- assert.doesNotMatch(result?.message ?? "", /Unknown command/i);
273
- });
274
-
275
- test("shows busy loader setting status", () => {
276
- const result = runCommand("/setting busy-loader", {
277
- settings: {
278
- workspaceDisplayMode: "dir",
279
- terminalTitleMode: "dir",
280
- showBusyLoader: false,
281
- },
282
- });
283
- assert.equal(result?.action, "setting_busy_loader");
284
- assert.equal(result?.message, "Busy loader: false");
285
- });
286
-
287
- test("rejects invalid /setting usage with a short hint", () => {
288
- const invalidValue = runCommand("/setting directory compact");
289
- assert.equal(invalidValue?.action, "unknown");
290
- assert.equal(invalidValue?.message, "Usage: /setting workspace [dir|name|simple]");
291
-
292
- const invalidTerminalValue = runCommand("/setting terminal-title compact");
293
- assert.equal(invalidTerminalValue?.action, "unknown");
294
- assert.equal(invalidTerminalValue?.message, "Usage: /setting terminal-title [dir|name|simple]");
295
-
296
- const invalidBusyLoader = runCommand("/setting busy-loader maybe");
297
- assert.equal(invalidBusyLoader?.action, "unknown");
298
- assert.equal(invalidBusyLoader?.message, "Usage: /setting busy-loader [true|false]");
299
-
300
- const invalidSetting = runCommand("/setting theme");
301
- assert.equal(invalidSetting?.action, "unknown");
302
- assert.equal(invalidSetting?.message, "Usage: /setting, /setting workspace [dir|name|simple], /setting terminal-title [dir|name|simple], or /setting busy-loader [true|false]");
303
- });
304
-
305
- test("shows effective runtime status", () => {
306
- const result = runCommand("/status");
307
- assert.equal(result?.action, "status");
308
- assert.match(result?.message ?? "", /Plan mode: Disabled/i);
309
- assert.match(result?.message ?? "", /Approval policy: On request/i);
310
- assert.match(result?.message ?? "", /Sandbox mode: Read only/i);
311
- assert.match(result?.message ?? "", /Tokens used: ~1,200/i);
312
- });
313
-
314
- test("/status includes active provider route status when supplied", () => {
315
- const result = runCommand("/status", {
316
- routeStatusMessage: [
317
- "Route status:",
318
- " Active chat route: Google / gemini-3-flash-preview",
319
- " Backend kind: gemini-cli-auth",
320
- ].join("\n"),
321
- });
322
-
323
- assert.equal(result?.action, "status");
324
- assert.match(result?.message ?? "", /Active chat route: Google \/ gemini-3-flash-preview/);
325
- assert.match(result?.message ?? "", /Backend kind: gemini-cli-auth/);
326
- assert.doesNotMatch(result?.message ?? "", /Antigravity/);
327
- });
328
-
329
- test("/status includes project instructions status when supplied", () => {
330
- const result = runCommand("/status", {
331
- projectInstructions: {
332
- status: "loaded",
333
- instructions: { path: "C:\\Workspace\\AGENTS.md", content: "" },
334
- },
335
- });
336
-
337
- assert.equal(result?.action, "status");
338
- assert.match(result?.message ?? "", /Project instructions: loaded/);
339
- assert.match(result?.message ?? "", /AGENTS\.md/);
340
- });
341
-
342
- test("/models opens the model picker", () => {
343
- const result = runCommand("/models", {
344
- modelCapabilities: dynamicCapabilities,
345
- });
346
-
347
- assert.equal(result?.action, "open_model_picker");
348
- });
349
-
350
- test("shows layered config status", () => {
351
- const result = runCommand("/config");
352
- assert.equal(result?.action, "config_status");
353
- assert.match(result?.message ?? "", /Config status:/i);
354
- assert.match(result?.message ?? "", /Project trust: Untrusted/i);
355
- });
356
-
357
- test("parses config trust commands", () => {
358
- const statusResult = runCommand("/config trust");
359
- assert.equal(statusResult?.action, "config_trust_status");
360
- assert.match(statusResult?.message ?? "", /Status: Untrusted/i);
361
-
362
- const setResult = runCommand("/config trust on");
363
- assert.equal(setResult?.action, "config_trust_set");
364
- assert.equal(setResult?.value, "on");
365
- });
366
-
367
- test("opens the permissions panel when /permissions has no arguments", () => {
368
- const result = runCommand("/permissions");
369
- assert.equal(result?.action, "open_permissions_panel");
370
- });
371
-
372
- test("reports configured and effective permissions status", () => {
373
- const result = runCommand("/permissions status");
374
- assert.equal(result?.action, "permissions_status");
375
- assert.match(result?.message ?? "", /Permissions status:/i);
376
- assert.match(result?.message ?? "", /configured Inherit; effective On request/i);
377
- assert.match(result?.message ?? "", /Network access: configured Inherit; effective Disabled/i);
378
- });
379
-
380
- test("parses runtime approval policy setters and status", () => {
381
- const setResult = runCommand("/runtime approval-policy never");
382
- assert.equal(setResult?.action, "runtime_approval_policy");
383
- assert.equal(setResult?.value, "never");
384
-
385
- const statusResult = runCommand("/runtime approval-policy status");
386
- assert.equal(statusResult?.action, "runtime_approval_policy");
387
- assert.match(statusResult?.message ?? "", /configured Inherit; effective On request/i);
388
- });
389
-
390
- test("parses runtime sandbox setters and status", () => {
391
- const setResult = runCommand("/runtime sandbox danger-full-access");
392
- assert.equal(setResult?.action, "runtime_sandbox_mode");
393
- assert.equal(setResult?.value, "danger-full-access");
394
-
395
- const statusResult = runCommand("/runtime sandbox status");
396
- assert.equal(statusResult?.action, "runtime_sandbox_mode");
397
- assert.match(statusResult?.message ?? "", /configured Inherit; effective Read only/i);
398
- });
399
-
400
- test("parses runtime network setters including inherit resets", () => {
401
- const setResult = runCommand("/runtime network on");
402
- assert.equal(setResult?.action, "runtime_network_access");
403
- assert.equal(setResult?.value, "enabled");
404
-
405
- const inheritResult = runCommand("/runtime network inherit");
406
- assert.equal(inheritResult?.action, "runtime_network_access");
407
- assert.equal(inheritResult?.value, "inherit");
408
- });
409
-
410
- test("parses permissions command setters through the runtime action path", () => {
411
- const approvalResult = runCommand("/permissions approval-policy never");
412
- assert.equal(approvalResult?.action, "runtime_approval_policy");
413
- assert.equal(approvalResult?.value, "never");
414
-
415
- const sandboxResult = runCommand("/permissions sandbox workspace-write");
416
- assert.equal(sandboxResult?.action, "runtime_sandbox_mode");
417
- assert.equal(sandboxResult?.value, "workspace-write");
418
-
419
- const networkResult = runCommand("/permissions network on");
420
- assert.equal(networkResult?.action, "runtime_network_access");
421
- assert.equal(networkResult?.value, "enabled");
422
-
423
- const rootsResult = runCommand("/permissions writable-roots add .\\tmp");
424
- assert.equal(rootsResult?.action, "runtime_writable_roots_add");
425
- assert.equal(rootsResult?.value, ".\\tmp");
426
- });
427
-
428
- test("status reports resolved overrides rather than raw inherit values", () => {
429
- const runtime = normalizeRuntimeConfig({
430
- mode: "suggest",
431
- policy: {
432
- approvalPolicy: "never",
433
- sandboxMode: "workspace-write",
434
- networkAccess: "enabled",
435
- writableRoots: ["C:\\Extra\\"],
436
- },
437
- });
438
- const result = runCommand("/status", {
439
- runtime,
440
- resolvedRuntime: resolveRuntimeConfig(runtime),
441
- });
442
-
443
- assert.equal(result?.action, "status");
444
- assert.match(result?.message ?? "", /Approval policy: Never/i);
445
- assert.match(result?.message ?? "", /Sandbox mode: Workspace write/i);
446
- assert.match(result?.message ?? "", /Network access: Enabled/i);
447
- assert.match(result?.message ?? "", /C:\\Extra/i);
448
- });
449
-
450
- test("parses writable root list add remove and clear commands", () => {
451
- const listResult = runCommand("/runtime writable-roots list");
452
- assert.equal(listResult?.action, "runtime_writable_roots_list");
453
- assert.match(listResult?.message ?? "", /none/i);
454
-
455
- const addResult = runCommand("/runtime writable-roots add .\\tmp");
456
- assert.equal(addResult?.action, "runtime_writable_roots_add");
457
- assert.equal(addResult?.value, ".\\tmp");
458
-
459
- const removeResult = runCommand("/runtime writable-roots remove .\\tmp");
460
- assert.equal(removeResult?.action, "runtime_writable_roots_remove");
461
- assert.equal(removeResult?.value, ".\\tmp");
462
-
463
- const clearResult = runCommand("/runtime writable-roots clear");
464
- assert.equal(clearResult?.action, "runtime_writable_roots_clear");
465
- });
466
-
467
- test("parses runtime service tier and personality setters", () => {
468
- const tierResult = runCommand("/runtime service-tier fast");
469
- assert.equal(tierResult?.action, "runtime_service_tier");
470
- assert.equal(tierResult?.value, "fast");
471
-
472
- const personalityResult = runCommand("/runtime personality pragmatic");
473
- assert.equal(personalityResult?.action, "runtime_personality");
474
- assert.equal(personalityResult?.value, "pragmatic");
475
- });
476
-
477
- test("documents runtime commands in help", () => {
478
- const result = runCommand("/help");
479
- assert.equal(result?.action, "help");
480
- assert.match(result?.message ?? "", /\/status\s+Show the effective runtime configuration/i);
481
- assert.match(result?.message ?? "", /\/config\s+Show layered config sources/i);
482
- assert.match(result?.message ?? "", /\/permissions\s+Open or update permissions and sandbox controls/i);
483
- assert.match(result?.message ?? "", /\/permissions approval-policy/i);
484
- assert.match(result?.message ?? "", /\/runtime approval-policy/i);
485
- assert.match(result?.message ?? "", /\/runtime writable-roots/i);
486
- assert.match(result?.message ?? "", /\/plan \[on\|off\]\s+Show or toggle session plan mode/i);
487
- assert.match(result?.message ?? "", /\/setting, \/settings Open the settings picker/i);
488
- assert.match(result?.message ?? "", /\/setting workspace \[dir\|name\|simple\]/i);
489
- assert.match(result?.message ?? "", /\/setting busy-loader \[true\|false\]/i);
490
- assert.match(result?.message ?? "", /\/mouse\s+Toggle SGR mouse capture for in-app wheel scroll/i);
491
- assert.match(result?.message ?? "", /Current plan mode: Disabled/i);
492
- assert.match(result?.message ?? "", /Shift\+Tab\s+Toggle plan mode/i);
493
- assert.match(result?.message ?? "", /Ctrl\+Y\s+Cycle execution mode/i);
494
- assert.match(result?.message ?? "", /Ctrl\+Alt\+P\s+Open provider picker/i);
495
- });
496
-
497
- test("shows the active locked workspace", () => {
498
- const result = runCommand("/workspace");
499
- assert.equal(result?.action, "workspace");
500
- assert.equal(result?.message, baseContext.workspace.summaryMessage);
501
- });
502
-
503
- test("parses workspace relaunch commands", () => {
504
- const result = runCommand("/workspace relaunch C:\\Next Workspace");
505
- assert.equal(result?.action, "workspace_relaunch");
506
- assert.equal(result?.value, "C:\\Next Workspace");
507
- });
508
-
509
- test("requires a target path for workspace relaunch", () => {
510
- const result = runCommand("/workspace relaunch");
511
- assert.equal(result?.action, "unknown");
512
- assert.match(result?.message ?? "", /Usage: \/workspace relaunch <path>/i);
513
- });
514
-
515
- test("parses /theme and /themes commands", () => {
516
- const themeResult = runCommand("/theme mono");
517
- assert.equal(themeResult?.action, "theme");
518
- assert.equal(themeResult?.value, "mono");
519
-
520
- const themesResult = runCommand("/themes");
521
- assert.equal(themesResult?.action, "open_theme_picker");
522
- });
523
-
524
- test("parses /clear command", () => {
525
- const result = runCommand("/clear");
526
- assert.equal(result?.action, "clear");
527
- });
528
-
529
- test("parses /model without args as open_model_picker", () => {
530
- const result = runCommand("/model");
531
- assert.equal(result?.action, "open_model_picker");
532
- });
533
-
534
- test("parses /providers as open_provider_picker", () => {
535
- const result = runCommand("/providers");
536
- assert.equal(result?.action, "open_provider_picker");
537
- });
538
-
539
- test("parses /provider alias as open_provider_picker", () => {
540
- const result = runCommand("/provider");
541
- assert.equal(result?.action, "open_provider_picker");
542
- });
543
-
544
- test("parses /route as distinct route status", () => {
545
- const result = runCommand("/route", {
546
- routeStatusMessage: [
547
- "Route status:",
548
- " Workspace default: Anthropic",
549
- " Active chat route: OpenAI / gpt-5.4",
550
- " Active model: gpt-5.4",
551
- " Active provider mode: Usable inside Codexa",
552
- " Anthropic in-Codexa routing: not configured yet",
553
- ].join("\n"),
554
- });
555
-
556
- assert.equal(result?.action, "route_status");
557
- assert.match(result?.message ?? "", /Workspace default: Anthropic/);
558
- assert.match(result?.message ?? "", /Active chat route: OpenAI \/ gpt-5\.4/);
559
- });
560
-
561
- test("/models is a model picker alias", () => {
562
- const result = runCommand("/models", {
563
- modelCapabilities: dynamicCapabilities,
564
- activeRouteProviderLabel: "OpenAI",
565
- });
566
-
567
- assert.equal(result?.action, "open_model_picker");
568
- });
569
-
570
- test("parses /reasoning without args as open_reasoning_picker", () => {
571
- const result = runCommand("/reasoning");
572
- assert.equal(result?.action, "open_reasoning_picker");
573
- });
574
-
575
- test("parses /diagnose github command", () => {
576
- const result = runCommand("/diagnose github");
577
- assert.equal(result?.action, "diagnose_github");
578
- });
579
-
580
- test("parses /diagnose providers command", () => {
581
- const result = runCommand("/diagnose providers");
582
- assert.equal(result?.action, "diagnose_providers");
583
- });
584
-
585
- test("unknown commands show /help suggestion", () => {
586
- const result = runCommand("/unknown-cmd-123");
587
- assert.equal(result?.action, "unknown");
588
- assert.match(result?.message ?? "", /Type \/help for available commands/i);
589
- });
590
-
591
- test("every command documented in help is recognized by the parser", () => {
592
- const helpResult = runCommand("/help");
593
- assert.equal(helpResult?.action, "help");
594
- const helpText = helpResult?.message ?? "";
595
-
596
- // Capture commands from line-start entries (" /cmd ...") and comma-listed aliases (", /alias")
597
- // so that lines like " /exit, /quit Quit..." contribute both "exit" and "quit"
598
- const fromLineStart = [...helpText.matchAll(/^\s{2}\/([a-z][a-z0-9-]+)/gm)].map((m) => m[1]!);
599
- const fromCommaAlias = [...helpText.matchAll(/,\s*\/([a-z][a-z0-9-]+)/g)].map((m) => m[1]!);
600
- const documentedCommands = new Set([...fromLineStart, ...fromCommaAlias]);
601
-
602
- // /diagnose requires the "github" subcommand; bare /diagnose returns unknown by design
603
- const requiresSubcommand = new Set(["diagnose"]);
604
-
605
- for (const cmd of documentedCommands) {
606
- if (requiresSubcommand.has(cmd)) continue;
607
- const result = runCommand(`/${cmd}`);
608
- assert.notEqual(result?.action, "unknown", `Command /${cmd} documented in /help but handler returned 'unknown'`);
609
- }
610
-
611
- // Verify specific multi-word commands mentioned in help
612
- const multiWord = [
613
- ["diagnose github", "diagnose_github"],
614
- ["diagnose providers", "diagnose_providers"],
615
- ["config trust", "config_trust_status"],
616
- ["auth status", "auth_status"],
617
- ["workspace relaunch .", "workspace_relaunch"],
618
- ["providers", "open_provider_picker"],
619
- ["route", "route_status"],
620
- ] as const;
621
- for (const [cmd, expectedAction] of multiWord) {
622
- const result = runCommand(`/${cmd}`);
623
- assert.equal(result?.action, expectedAction, `Multi-word command /${cmd} mentioned in help but returned action ${result?.action} instead of ${expectedAction}`);
624
- }
625
- });
626
-
627
- test("?-prefixed command-like input is treated as invalid command error", () => {
628
- const result = runCommand("?clear");
629
- assert.equal(result?.action, "unknown");
630
- assert.match(result?.message ?? "", /Invalid command syntax.*\?clear/i);
631
- assert.match(result?.message ?? "", /Did you mean \/clear/i);
632
- });
633
-
634
- test("?model shows command error not model picker", () => {
635
- const result = runCommand("?model");
636
- assert.equal(result?.action, "unknown");
637
- assert.match(result?.message ?? "", /Invalid command syntax/i);
638
- });
639
-
640
- test("non-command text without / or ? prefix returns null", () => {
641
- const result = runCommand("hello world");
642
- assert.equal(result, null);
643
- });