@golba98/codexa 1.0.2 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (206) hide show
  1. package/README.md +396 -100
  2. package/bin/codexa.js +62 -144
  3. package/package.json +14 -8
  4. package/src/app.tsx +596 -306
  5. package/src/commands/handler.ts +6 -6
  6. package/src/config/buildInfo.ts +2 -2
  7. package/src/config/layeredConfig.ts +1 -1
  8. package/src/config/persistence.ts +10 -0
  9. package/src/config/runtimeConfig.ts +1 -1
  10. package/src/config/settings.ts +8 -16
  11. package/src/config/trustStore.ts +1 -1
  12. package/src/config/updateCheckCache.ts +19 -1
  13. package/src/core/README.md +52 -0
  14. package/src/core/agent/loop.ts +282 -0
  15. package/src/core/agent/protocol.ts +211 -0
  16. package/src/core/agent/tools.ts +414 -0
  17. package/src/core/{codexExecArgs.ts → codex/codexExecArgs.ts} +2 -2
  18. package/src/core/{codexLaunch.ts → codex/codexLaunch.ts} +3 -3
  19. package/src/core/{codexPrompt.ts → codex/codexPrompt.ts} +3 -3
  20. package/src/core/debug/modelStateDebug.ts +34 -0
  21. package/src/core/executables/antigravityExecutable.ts +48 -0
  22. package/src/core/executables/codexExecutable.ts +1 -0
  23. package/src/core/executables/executableResolver.ts +65 -43
  24. package/src/core/perf/renderDebug.ts +10 -6
  25. package/src/core/process/processValidation.ts +9 -5
  26. package/src/core/providerLauncher/launcher.ts +59 -42
  27. package/src/core/providerLauncher/registry.ts +30 -14
  28. package/src/core/providerLauncher/types.ts +11 -9
  29. package/src/core/providerLauncher/workspaceConfig.ts +41 -26
  30. package/src/core/providerRuntime/anthropic.ts +7 -1
  31. package/src/core/providerRuntime/antigravity.ts +305 -0
  32. package/src/core/providerRuntime/claudeCodeDiscovery.ts +268 -22
  33. package/src/core/providerRuntime/claudeCodeDiscoveryDebug.ts +55 -0
  34. package/src/core/providerRuntime/contextMetadata.ts +12 -24
  35. package/src/core/providerRuntime/local.ts +129 -51
  36. package/src/core/providerRuntime/models.ts +22 -11
  37. package/src/core/providerRuntime/registry.ts +58 -31
  38. package/src/core/providerRuntime/types.ts +19 -14
  39. package/src/core/providers/codexSubprocess.ts +2 -2
  40. package/src/core/providers/types.ts +1 -1
  41. package/src/core/{attachments.ts → shared/attachments.ts} +27 -4
  42. package/src/core/{cleanupFastFail.ts → shared/cleanupFastFail.ts} +1 -1
  43. package/src/core/{hollowResponseFormat.ts → shared/hollowResponseFormat.ts} +1 -1
  44. package/src/core/terminal/clearFrameBoundary.ts +814 -0
  45. package/src/core/terminal/frameLock.ts +109 -0
  46. package/src/core/terminal/inkRenderReset.ts +123 -0
  47. package/src/core/terminal/terminalControl.ts +22 -0
  48. package/src/core/terminal/terminalTitle.ts +16 -102
  49. package/src/core/version/channel.ts +23 -0
  50. package/src/core/version/updateCheck.ts +193 -0
  51. package/src/core/{launchContext.ts → workspace/launchContext.ts} +32 -39
  52. package/src/core/{planStorage.ts → workspace/planStorage.ts} +2 -2
  53. package/src/core/{workspaceGuard.ts → workspace/workspaceGuard.ts} +97 -13
  54. package/src/headless/execRunner.ts +2 -2
  55. package/src/index.tsx +43 -89
  56. package/src/session/appSession.ts +10 -7
  57. package/src/session/chatLifecycle.ts +1 -1
  58. package/src/session/liveRenderScheduler.ts +1 -1
  59. package/src/session/types.ts +3 -2
  60. package/src/ui/ActionRequiredBlock.tsx +5 -5
  61. package/src/ui/ActivityBars.tsx +3 -3
  62. package/src/ui/ActivityIndicator.tsx +6 -6
  63. package/src/ui/AgentBlock.tsx +6 -6
  64. package/src/ui/AnimatedStatusText.tsx +1 -1
  65. package/src/ui/AppShell.tsx +670 -719
  66. package/src/ui/AttachmentImportPanel.tsx +8 -8
  67. package/src/ui/AuthPanel.tsx +20 -20
  68. package/src/ui/BottomComposer.tsx +158 -118
  69. package/src/ui/DashCard.tsx +3 -3
  70. package/src/ui/Markdown.tsx +17 -17
  71. package/src/ui/ModelPickerScreen.tsx +222 -42
  72. package/src/ui/ModelReasoningPicker.tsx +15 -15
  73. package/src/ui/Panel.tsx +3 -3
  74. package/src/ui/PlanActionPicker.tsx +6 -6
  75. package/src/ui/PlanReviewPanel.tsx +9 -9
  76. package/src/ui/ProviderPicker.tsx +735 -321
  77. package/src/ui/RunFooter.tsx +3 -3
  78. package/src/ui/RuntimeStatusBar.tsx +108 -0
  79. package/src/ui/SelectionPanel.tsx +8 -4
  80. package/src/ui/SettingsPanel.tsx +9 -9
  81. package/src/ui/Spinner.tsx +1 -1
  82. package/src/ui/TextEntryPanel.tsx +11 -11
  83. package/src/ui/ThinkingBlock.tsx +8 -8
  84. package/src/ui/Timeline.tsx +1625 -1472
  85. package/src/ui/TopHeader.tsx +437 -293
  86. package/src/ui/TranscriptShell.tsx +322 -0
  87. package/src/ui/TurnGroup.tsx +33 -33
  88. package/src/ui/UpdateAvailableCard.tsx +41 -0
  89. package/src/ui/UpdatePromptPanel.tsx +197 -0
  90. package/src/ui/focus.ts +3 -0
  91. package/src/ui/layout.ts +299 -25
  92. package/src/ui/layoutListWindow.ts +145 -0
  93. package/src/ui/logoVariants.ts +103 -0
  94. package/src/ui/modeDisplay.ts +12 -12
  95. package/src/ui/runtimeDisplay.ts +112 -0
  96. package/src/ui/textLayout.ts +15 -4
  97. package/src/ui/theme.tsx +274 -395
  98. package/src/ui/timelineMeasure.ts +218 -136
  99. package/scripts/audit-codexa-capabilities.mjs +0 -466
  100. package/scripts/gen-build-info.mjs +0 -33
  101. package/scripts/smoke-terminal-bench.mjs +0 -35
  102. package/src/appRenderStability.test.ts +0 -131
  103. package/src/commands/handler.test.ts +0 -655
  104. package/src/config/launchArgs.test.ts +0 -189
  105. package/src/config/layeredConfig.test.ts +0 -143
  106. package/src/config/persistence.test.ts +0 -114
  107. package/src/config/runtimeConfig.test.ts +0 -218
  108. package/src/config/settings.test.ts +0 -155
  109. package/src/config/trustStore.test.ts +0 -29
  110. package/src/core/attachments.test.ts +0 -155
  111. package/src/core/auth/codexAuth.test.ts +0 -68
  112. package/src/core/cleanupFastFail.test.ts +0 -76
  113. package/src/core/codex.ts +0 -124
  114. package/src/core/codexExecArgs.test.ts +0 -195
  115. package/src/core/codexLaunch.test.ts +0 -205
  116. package/src/core/codexPrompt.test.ts +0 -252
  117. package/src/core/executables/codexExecutable.test.ts +0 -212
  118. package/src/core/executables/executableResolver.test.ts +0 -129
  119. package/src/core/executables/geminiExecutable.test.ts +0 -116
  120. package/src/core/executables/pathSanityScan.test.ts +0 -47
  121. package/src/core/githubDiagnostics.test.ts +0 -92
  122. package/src/core/hollowResponseFormat.test.ts +0 -58
  123. package/src/core/launchContext.test.ts +0 -157
  124. package/src/core/models/codexCapabilities.test.ts +0 -45
  125. package/src/core/models/codexModelCapabilities.test.ts +0 -246
  126. package/src/core/models/modelSpecs.test.ts +0 -283
  127. package/src/core/perf/renderDebug.test.ts +0 -230
  128. package/src/core/planStorage.test.ts +0 -143
  129. package/src/core/process/CommandRunner.test.ts +0 -105
  130. package/src/core/projectInstructions.test.ts +0 -50
  131. package/src/core/providerLauncher/launcher.test.ts +0 -238
  132. package/src/core/providerLauncher/registry.test.ts +0 -324
  133. package/src/core/providerLauncher/workspaceConfig.test.ts +0 -638
  134. package/src/core/providerRuntime/anthropic.test.ts +0 -1120
  135. package/src/core/providerRuntime/capabilityProfile.test.ts +0 -311
  136. package/src/core/providerRuntime/contextMetadata.test.ts +0 -468
  137. package/src/core/providerRuntime/gemini.test.ts +0 -437
  138. package/src/core/providerRuntime/lmstudio.test.ts +0 -168
  139. package/src/core/providerRuntime/local.test.ts +0 -787
  140. package/src/core/providerRuntime/registry.test.ts +0 -233
  141. package/src/core/providers/codexJsonStream.test.ts +0 -148
  142. package/src/core/providers/codexSubprocess.test.ts +0 -68
  143. package/src/core/providers/codexTranscript.test.ts +0 -284
  144. package/src/core/terminal/startupClear.test.ts +0 -55
  145. package/src/core/terminal/terminalCapabilities.test.ts +0 -93
  146. package/src/core/terminal/terminalControl.test.ts +0 -75
  147. package/src/core/terminal/terminalSanitize.test.ts +0 -22
  148. package/src/core/terminal/terminalSelection.test.ts +0 -42
  149. package/src/core/terminal/terminalTitle.test.ts +0 -328
  150. package/src/core/updateCheck.test.ts +0 -194
  151. package/src/core/updateCheck.ts +0 -172
  152. package/src/core/workspaceActivity.test.ts +0 -163
  153. package/src/core/workspaceGuard.test.ts +0 -151
  154. package/src/core/workspaceRoot.test.ts +0 -23
  155. package/src/exec.test.ts +0 -13
  156. package/src/headless/execArgs.test.ts +0 -147
  157. package/src/headless/execRunner.test.ts +0 -436
  158. package/src/index.test.tsx +0 -620
  159. package/src/session/appSession.test.ts +0 -897
  160. package/src/session/chatLifecycle.test.ts +0 -64
  161. package/src/session/liveRenderScheduler.test.ts +0 -201
  162. package/src/session/planFlow.test.ts +0 -103
  163. package/src/session/planTranscript.test.ts +0 -65
  164. package/src/session/promptRunSchedule.test.ts +0 -36
  165. package/src/ui/ActivityIndicator.test.tsx +0 -58
  166. package/src/ui/AgentBlock.test.ts +0 -6
  167. package/src/ui/AnimatedStatusText.test.ts +0 -16
  168. package/src/ui/AppShell.test.tsx +0 -1776
  169. package/src/ui/AttachmentImportPanel.test.tsx +0 -204
  170. package/src/ui/BottomComposer.test.ts +0 -674
  171. package/src/ui/CodexLogo.tsx +0 -55
  172. package/src/ui/Markdown.test.ts +0 -157
  173. package/src/ui/ModelPickerProviderScope.test.tsx +0 -411
  174. package/src/ui/ModelPickerScreen.test.tsx +0 -99
  175. package/src/ui/ModelPickerState.test.tsx +0 -151
  176. package/src/ui/ModelReasoningPicker.test.tsx +0 -447
  177. package/src/ui/PlanReviewPanel.test.tsx +0 -267
  178. package/src/ui/PromptCardBorder.test.tsx +0 -161
  179. package/src/ui/ProviderPicker.test.tsx +0 -289
  180. package/src/ui/ProviderShortcut.test.tsx +0 -143
  181. package/src/ui/SettingsPanel.test.tsx +0 -233
  182. package/src/ui/StaticTranscriptItem.tsx +0 -56
  183. package/src/ui/Timeline.test.ts +0 -2067
  184. package/src/ui/TimelineNavigation.test.tsx +0 -201
  185. package/src/ui/TopHeader.test.tsx +0 -254
  186. package/src/ui/TurnGroup.test.tsx +0 -365
  187. package/src/ui/busyStatusAnimation.test.ts +0 -30
  188. package/src/ui/commandNormalize.test.ts +0 -142
  189. package/src/ui/diffRenderer.test.ts +0 -102
  190. package/src/ui/focusFlow.test.tsx +0 -1098
  191. package/src/ui/inputBuffer.test.ts +0 -151
  192. package/src/ui/layout.test.ts +0 -146
  193. package/src/ui/modeDisplay.test.ts +0 -42
  194. package/src/ui/runActivityView.test.ts +0 -89
  195. package/src/ui/runLifecycleView.test.tsx +0 -237
  196. package/src/ui/statusRenderIsolation.test.tsx +0 -654
  197. package/src/ui/terminalAnswerFormat.test.ts +0 -19
  198. package/src/ui/textLayout.test.ts +0 -18
  199. package/src/ui/themeFlow.test.ts +0 -53
  200. package/src/ui/timelineMeasureCache.test.ts +0 -986
  201. /package/src/core/{inputDebug.ts → debug/inputDebug.ts} +0 -0
  202. /package/src/core/{clipboard.ts → shared/clipboard.ts} +0 -0
  203. /package/src/core/{githubDiagnostics.ts → shared/githubDiagnostics.ts} +0 -0
  204. /package/src/core/{projectInstructions.ts → workspace/projectInstructions.ts} +0 -0
  205. /package/src/core/{workspaceActivity.ts → workspace/workspaceActivity.ts} +0 -0
  206. /package/src/core/{workspaceRoot.ts → workspace/workspaceRoot.ts} +0 -0
@@ -1,655 +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
- ["update check", "update"],
621
- ] as const;
622
- for (const [cmd, expectedAction] of multiWord) {
623
- const result = runCommand(`/${cmd}`);
624
- assert.equal(result?.action, expectedAction, `Multi-word command /${cmd} mentioned in help but returned action ${result?.action} instead of ${expectedAction}`);
625
- }
626
- });
627
-
628
- test("?-prefixed command-like input is treated as invalid command error", () => {
629
- const result = runCommand("?clear");
630
- assert.equal(result?.action, "unknown");
631
- assert.match(result?.message ?? "", /Invalid command syntax.*\?clear/i);
632
- assert.match(result?.message ?? "", /Did you mean \/clear/i);
633
- });
634
-
635
- test("?model shows command error not model picker", () => {
636
- const result = runCommand("?model");
637
- assert.equal(result?.action, "unknown");
638
- assert.match(result?.message ?? "", /Invalid command syntax/i);
639
- });
640
-
641
- test("non-command text without / or ? prefix returns null", () => {
642
- const result = runCommand("hello world");
643
- assert.equal(result, null);
644
- });
645
-
646
- test("parses /update and /update check commands", () => {
647
- const resultStatus = runCommand("/update");
648
- assert.equal(resultStatus?.action, "update");
649
- assert.equal(resultStatus?.value, "status");
650
-
651
- const resultCheck = runCommand("/update check");
652
- assert.equal(resultCheck?.action, "update");
653
- assert.equal(resultCheck?.value, "check");
654
- });
655
-