@golba98/codexa 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +320 -0
- package/bin/codexa.js +445 -0
- package/package.json +45 -0
- package/scripts/audit-codexa-capabilities.mjs +466 -0
- package/scripts/smoke-terminal-bench.mjs +35 -0
- package/src/app.tsx +4561 -0
- package/src/appRenderStability.test.ts +131 -0
- package/src/commands/handler.test.ts +643 -0
- package/src/commands/handler.ts +875 -0
- package/src/config/launchArgs.test.ts +158 -0
- package/src/config/launchArgs.ts +186 -0
- package/src/config/layeredConfig.test.ts +143 -0
- package/src/config/layeredConfig.ts +836 -0
- package/src/config/persistence.test.ts +110 -0
- package/src/config/persistence.ts +311 -0
- package/src/config/runtimeConfig.test.ts +218 -0
- package/src/config/runtimeConfig.ts +554 -0
- package/src/config/settings.test.ts +155 -0
- package/src/config/settings.ts +401 -0
- package/src/config/toml-serialize.ts +98 -0
- package/src/config/trustStore.test.ts +29 -0
- package/src/config/trustStore.ts +68 -0
- package/src/core/attachments.test.ts +155 -0
- package/src/core/attachments.ts +71 -0
- package/src/core/auth/codexAuth.test.ts +68 -0
- package/src/core/auth/codexAuth.ts +359 -0
- package/src/core/cleanupFastFail.test.ts +76 -0
- package/src/core/cleanupFastFail.ts +67 -0
- package/src/core/clipboard.ts +24 -0
- package/src/core/codex.ts +124 -0
- package/src/core/codexExecArgs.test.ts +195 -0
- package/src/core/codexExecArgs.ts +152 -0
- package/src/core/codexLaunch.test.ts +205 -0
- package/src/core/codexLaunch.ts +162 -0
- package/src/core/codexPrompt.test.ts +252 -0
- package/src/core/codexPrompt.ts +428 -0
- package/src/core/executables/claudeExecutable.ts +63 -0
- package/src/core/executables/codexExecutable.test.ts +212 -0
- package/src/core/executables/codexExecutable.ts +159 -0
- package/src/core/executables/executableResolver.test.ts +129 -0
- package/src/core/executables/executableResolver.ts +138 -0
- package/src/core/executables/geminiExecutable.test.ts +116 -0
- package/src/core/executables/geminiExecutable.ts +78 -0
- package/src/core/executables/pathSanityScan.test.ts +47 -0
- package/src/core/githubDiagnostics.test.ts +92 -0
- package/src/core/githubDiagnostics.ts +222 -0
- package/src/core/hollowResponseFormat.test.ts +58 -0
- package/src/core/hollowResponseFormat.ts +39 -0
- package/src/core/inputDebug.ts +51 -0
- package/src/core/launchContext.test.ts +157 -0
- package/src/core/launchContext.ts +266 -0
- package/src/core/models/codexCapabilities.test.ts +45 -0
- package/src/core/models/codexCapabilities.ts +95 -0
- package/src/core/models/codexModelCapabilities.test.ts +246 -0
- package/src/core/models/codexModelCapabilities.ts +571 -0
- package/src/core/models/modelSpecs.test.ts +283 -0
- package/src/core/models/modelSpecs.ts +300 -0
- package/src/core/perf/profiler.ts +125 -0
- package/src/core/perf/renderDebug.test.ts +230 -0
- package/src/core/perf/renderDebug.ts +373 -0
- package/src/core/planStorage.test.ts +143 -0
- package/src/core/planStorage.ts +141 -0
- package/src/core/process/CommandRunner.test.ts +105 -0
- package/src/core/process/CommandRunner.ts +269 -0
- package/src/core/process/processValidation.ts +101 -0
- package/src/core/projectInstructions.test.ts +50 -0
- package/src/core/projectInstructions.ts +54 -0
- package/src/core/providerLauncher/launcher.test.ts +238 -0
- package/src/core/providerLauncher/launcher.ts +203 -0
- package/src/core/providerLauncher/registry.test.ts +324 -0
- package/src/core/providerLauncher/registry.ts +253 -0
- package/src/core/providerLauncher/types.ts +84 -0
- package/src/core/providerLauncher/workspaceConfig.test.ts +638 -0
- package/src/core/providerLauncher/workspaceConfig.ts +407 -0
- package/src/core/providerRuntime/anthropic.test.ts +1120 -0
- package/src/core/providerRuntime/anthropic.ts +576 -0
- package/src/core/providerRuntime/capabilityProfile.test.ts +311 -0
- package/src/core/providerRuntime/capabilityProfile.ts +288 -0
- package/src/core/providerRuntime/claudeCodeDiscovery.ts +446 -0
- package/src/core/providerRuntime/contextMetadata.test.ts +468 -0
- package/src/core/providerRuntime/contextMetadata.ts +409 -0
- package/src/core/providerRuntime/gemini.test.ts +437 -0
- package/src/core/providerRuntime/gemini.ts +784 -0
- package/src/core/providerRuntime/lmstudio.test.ts +168 -0
- package/src/core/providerRuntime/lmstudio.ts +118 -0
- package/src/core/providerRuntime/local.test.ts +787 -0
- package/src/core/providerRuntime/local.ts +754 -0
- package/src/core/providerRuntime/models.ts +150 -0
- package/src/core/providerRuntime/reasoning.ts +17 -0
- package/src/core/providerRuntime/registry.test.ts +233 -0
- package/src/core/providerRuntime/registry.ts +203 -0
- package/src/core/providerRuntime/types.ts +103 -0
- package/src/core/providers/codexJsonStream.test.ts +148 -0
- package/src/core/providers/codexJsonStream.ts +305 -0
- package/src/core/providers/codexSubprocess.test.ts +68 -0
- package/src/core/providers/codexSubprocess.ts +372 -0
- package/src/core/providers/codexTranscript.test.ts +284 -0
- package/src/core/providers/codexTranscript.ts +695 -0
- package/src/core/providers/openaiNative.ts +13 -0
- package/src/core/providers/registry.ts +21 -0
- package/src/core/providers/types.ts +59 -0
- package/src/core/terminal/terminalCapabilities.test.ts +93 -0
- package/src/core/terminal/terminalCapabilities.ts +100 -0
- package/src/core/terminal/terminalControl.test.ts +75 -0
- package/src/core/terminal/terminalControl.ts +147 -0
- package/src/core/terminal/terminalSanitize.test.ts +22 -0
- package/src/core/terminal/terminalSanitize.ts +147 -0
- package/src/core/terminal/terminalSelection.test.ts +42 -0
- package/src/core/terminal/terminalSelection.ts +66 -0
- package/src/core/terminal/terminalTitle.test.ts +328 -0
- package/src/core/terminal/terminalTitle.ts +483 -0
- package/src/core/workspaceActivity.test.ts +163 -0
- package/src/core/workspaceActivity.ts +380 -0
- package/src/core/workspaceGuard.test.ts +151 -0
- package/src/core/workspaceGuard.ts +288 -0
- package/src/core/workspaceRoot.test.ts +23 -0
- package/src/core/workspaceRoot.ts +47 -0
- package/src/exec.test.ts +13 -0
- package/src/exec.ts +72 -0
- package/src/headless/execArgs.test.ts +147 -0
- package/src/headless/execArgs.ts +294 -0
- package/src/headless/execRunner.test.ts +434 -0
- package/src/headless/execRunner.ts +304 -0
- package/src/index.test.tsx +618 -0
- package/src/index.tsx +296 -0
- package/src/session/appSession.test.ts +897 -0
- package/src/session/appSession.ts +761 -0
- package/src/session/chatLifecycle.test.ts +64 -0
- package/src/session/chatLifecycle.ts +951 -0
- package/src/session/liveRenderScheduler.test.ts +201 -0
- package/src/session/liveRenderScheduler.ts +214 -0
- package/src/session/planFlow.test.ts +103 -0
- package/src/session/planFlow.ts +149 -0
- package/src/session/planTranscript.test.ts +65 -0
- package/src/session/planTranscript.ts +15 -0
- package/src/session/promptRunSchedule.test.ts +36 -0
- package/src/session/promptRunSchedule.ts +26 -0
- package/src/session/types.ts +228 -0
- package/src/test/runtimeTestUtils.ts +14 -0
- package/src/types/react-dom.d.ts +3 -0
- package/src/ui/ActionRequiredBlock.tsx +38 -0
- package/src/ui/ActivityBars.tsx +68 -0
- package/src/ui/ActivityIndicator.test.tsx +58 -0
- package/src/ui/ActivityIndicator.tsx +58 -0
- package/src/ui/AgentBlock.test.ts +6 -0
- package/src/ui/AgentBlock.tsx +130 -0
- package/src/ui/AnimatedStatusText.test.ts +16 -0
- package/src/ui/AnimatedStatusText.tsx +69 -0
- package/src/ui/AppShell.test.tsx +1739 -0
- package/src/ui/AppShell.tsx +698 -0
- package/src/ui/AttachmentImportPanel.test.tsx +204 -0
- package/src/ui/AttachmentImportPanel.tsx +98 -0
- package/src/ui/AuthPanel.tsx +113 -0
- package/src/ui/BackendPicker.tsx +28 -0
- package/src/ui/BottomComposer.test.ts +674 -0
- package/src/ui/BottomComposer.tsx +1028 -0
- package/src/ui/CodexLogo.tsx +55 -0
- package/src/ui/DashCard.tsx +82 -0
- package/src/ui/Markdown.test.ts +157 -0
- package/src/ui/Markdown.tsx +310 -0
- package/src/ui/ModePicker.tsx +27 -0
- package/src/ui/ModelPicker.tsx +31 -0
- package/src/ui/ModelPickerProviderScope.test.tsx +411 -0
- package/src/ui/ModelPickerScreen.test.tsx +99 -0
- package/src/ui/ModelPickerScreen.tsx +416 -0
- package/src/ui/ModelPickerState.test.tsx +151 -0
- package/src/ui/ModelReasoningPicker.test.tsx +447 -0
- package/src/ui/ModelReasoningPicker.tsx +458 -0
- package/src/ui/Panel.tsx +51 -0
- package/src/ui/PermissionsPanel.tsx +78 -0
- package/src/ui/PlanActionPicker.tsx +119 -0
- package/src/ui/PlanReviewPanel.test.tsx +267 -0
- package/src/ui/PlanReviewPanel.tsx +212 -0
- package/src/ui/PromptCardBorder.test.tsx +161 -0
- package/src/ui/ProviderPicker.test.tsx +289 -0
- package/src/ui/ProviderPicker.tsx +321 -0
- package/src/ui/ProviderShortcut.test.tsx +143 -0
- package/src/ui/ReasoningPicker.tsx +46 -0
- package/src/ui/RunFooter.tsx +65 -0
- package/src/ui/SelectionPanel.tsx +67 -0
- package/src/ui/SettingsPanel.test.tsx +233 -0
- package/src/ui/SettingsPanel.tsx +156 -0
- package/src/ui/Spinner.tsx +25 -0
- package/src/ui/StaticIntroItem.tsx +54 -0
- package/src/ui/StaticTranscriptItem.tsx +56 -0
- package/src/ui/TextEntryPanel.tsx +139 -0
- package/src/ui/ThemePicker.tsx +31 -0
- package/src/ui/ThinkingBlock.tsx +100 -0
- package/src/ui/Timeline.test.ts +2067 -0
- package/src/ui/Timeline.tsx +1472 -0
- package/src/ui/TimelineNavigation.test.tsx +201 -0
- package/src/ui/TopHeader.test.tsx +239 -0
- package/src/ui/TopHeader.tsx +257 -0
- package/src/ui/TurnGroup.test.tsx +365 -0
- package/src/ui/TurnGroup.tsx +657 -0
- package/src/ui/busyStatusAnimation.test.ts +30 -0
- package/src/ui/busyStatusAnimation.ts +11 -0
- package/src/ui/commandNormalize.test.ts +142 -0
- package/src/ui/commandNormalize.ts +66 -0
- package/src/ui/diffRenderer.test.ts +102 -0
- package/src/ui/diffRenderer.ts +116 -0
- package/src/ui/focus.ts +61 -0
- package/src/ui/focusFlow.test.tsx +1098 -0
- package/src/ui/inputBuffer.test.ts +151 -0
- package/src/ui/inputBuffer.ts +203 -0
- package/src/ui/layout.test.ts +145 -0
- package/src/ui/layout.ts +287 -0
- package/src/ui/modeDisplay.test.ts +42 -0
- package/src/ui/modeDisplay.ts +52 -0
- package/src/ui/outputPipeline.ts +64 -0
- package/src/ui/progressEntries.ts +156 -0
- package/src/ui/runActivityView.test.ts +89 -0
- package/src/ui/runActivityView.ts +37 -0
- package/src/ui/runLifecycleView.test.tsx +237 -0
- package/src/ui/slashCommands.ts +41 -0
- package/src/ui/statusRenderIsolation.test.tsx +654 -0
- package/src/ui/terminalAnswerFormat.test.ts +19 -0
- package/src/ui/terminalAnswerFormat.ts +128 -0
- package/src/ui/textLayout.test.ts +18 -0
- package/src/ui/textLayout.ts +338 -0
- package/src/ui/theme.tsx +395 -0
- package/src/ui/themeFlow.test.ts +53 -0
- package/src/ui/themeFlow.ts +41 -0
- package/src/ui/timelineMeasure.ts +3088 -0
- package/src/ui/timelineMeasureCache.test.ts +986 -0
- package/src/ui/useThrottledValue.ts +31 -0
|
@@ -0,0 +1,110 @@
|
|
|
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: false,
|
|
43
|
+
showModel: false,
|
|
44
|
+
showReasoning: false,
|
|
45
|
+
showContext: false,
|
|
46
|
+
showAuthStatus: false,
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const serialized = serializeSettings(initial);
|
|
51
|
+
const parsed = parseSettingsData(serialized);
|
|
52
|
+
|
|
53
|
+
assert.equal("runtime" in serialized, false);
|
|
54
|
+
assert.equal(parsed.ui.theme, "purple");
|
|
55
|
+
assert.equal(parsed.ui.workspaceDisplayMode, "simple");
|
|
56
|
+
assert.equal(parsed.ui.terminalTitleMode, "name");
|
|
57
|
+
assert.equal(parsed.ui.showBusyLoader, false);
|
|
58
|
+
assert.equal(parsed.ui.terminalMouseMode, "selection");
|
|
59
|
+
assert.equal(parsed.auth.preference, "runner-managed");
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test("falls back to defaults for missing UI or auth preferences", () => {
|
|
63
|
+
const parsed = parseSettingsData({});
|
|
64
|
+
const defaults = getDefaultSettings();
|
|
65
|
+
|
|
66
|
+
assert.equal(parsed.ui.layoutStyle, defaults.ui.layoutStyle);
|
|
67
|
+
assert.equal(parsed.ui.theme, defaults.ui.theme);
|
|
68
|
+
assert.equal(parsed.ui.workspaceDisplayMode, defaults.ui.workspaceDisplayMode);
|
|
69
|
+
assert.equal(parsed.ui.terminalTitleMode, defaults.ui.terminalTitleMode);
|
|
70
|
+
assert.equal(parsed.ui.showBusyLoader, defaults.ui.showBusyLoader);
|
|
71
|
+
assert.equal(parsed.auth.preference, defaults.auth.preference);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test("maps legacy directory display settings into workspace display mode", () => {
|
|
75
|
+
assert.equal(parseSettingsData({ directory_display_mode: "normal" }).ui.workspaceDisplayMode, "dir");
|
|
76
|
+
assert.equal(parseSettingsData({ directoryDisplayMode: "simple" }).ui.workspaceDisplayMode, "simple");
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test("parses workspace display and busy loader from camel and snake case", () => {
|
|
80
|
+
assert.equal(parseSettingsData({ workspace_display_mode: "name", terminal_title_mode: "simple", show_busy_loader: false }).ui.workspaceDisplayMode, "name");
|
|
81
|
+
assert.equal(parseSettingsData({ workspaceDisplayMode: "dir", terminalTitleMode: "name", showBusyLoader: true }).ui.showBusyLoader, true);
|
|
82
|
+
assert.equal(parseSettingsData({ terminalTitleMode: "name" }).ui.terminalTitleMode, "name");
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test("merges legacy runtime fields into TOML without overwriting existing values", () => {
|
|
86
|
+
const merged = mergeRuntimeIntoTomlConfig({
|
|
87
|
+
model: "gpt-5.4",
|
|
88
|
+
codexa: {
|
|
89
|
+
mode: "suggest",
|
|
90
|
+
},
|
|
91
|
+
}, {
|
|
92
|
+
...DEFAULT_RUNTIME_CONFIG,
|
|
93
|
+
model: "gpt-5.4-mini",
|
|
94
|
+
mode: "full-auto",
|
|
95
|
+
policy: {
|
|
96
|
+
...DEFAULT_RUNTIME_CONFIG.policy,
|
|
97
|
+
networkAccess: "enabled",
|
|
98
|
+
writableRoots: ["C:\\safe"],
|
|
99
|
+
personality: "pragmatic",
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
assert.equal(merged.model, "gpt-5.4");
|
|
104
|
+
assert.deepEqual(merged.codexa, { mode: "suggest" });
|
|
105
|
+
assert.deepEqual(merged.sandbox_workspace_write, {
|
|
106
|
+
network_access: true,
|
|
107
|
+
writable_roots: ["C:\\safe"],
|
|
108
|
+
});
|
|
109
|
+
assert.equal(merged.personality, "pragmatic");
|
|
110
|
+
});
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "fs";
|
|
2
|
+
import { dirname } from "path";
|
|
3
|
+
import { Theme } from "../ui/theme.js";
|
|
4
|
+
import {
|
|
5
|
+
AUTH_PREFERENCES,
|
|
6
|
+
DEFAULT_AUTH_PREFERENCE,
|
|
7
|
+
DEFAULT_LAYOUT_STYLE,
|
|
8
|
+
DEFAULT_SHOW_BUSY_LOADER,
|
|
9
|
+
DEFAULT_TERMINAL_MOUSE_MODE,
|
|
10
|
+
DEFAULT_TERMINAL_TITLE_MODE,
|
|
11
|
+
DEFAULT_THEME,
|
|
12
|
+
DEFAULT_WORKSPACE_DISPLAY_MODE,
|
|
13
|
+
HEADER_CONFIG_DEFAULTS,
|
|
14
|
+
LEGACY_DIRECTORY_DISPLAY_MODES,
|
|
15
|
+
TERMINAL_MOUSE_MODES,
|
|
16
|
+
WORKSPACE_DISPLAY_MODES,
|
|
17
|
+
getCodexConfigFile,
|
|
18
|
+
normalizeLegacyDirectoryDisplayMode,
|
|
19
|
+
SETTINGS_FILE,
|
|
20
|
+
type AuthPreference,
|
|
21
|
+
type HeaderConfig,
|
|
22
|
+
type TerminalMouseMode,
|
|
23
|
+
type TerminalTitleMode,
|
|
24
|
+
type WorkspaceDisplayMode,
|
|
25
|
+
} from "./settings.js";
|
|
26
|
+
import {
|
|
27
|
+
mergeRuntimeIntoTomlConfig,
|
|
28
|
+
parseTomlDocument,
|
|
29
|
+
} from "./layeredConfig.js";
|
|
30
|
+
import { serializeTomlDocument } from "./toml-serialize.js";
|
|
31
|
+
import {
|
|
32
|
+
normalizeRuntimeConfig,
|
|
33
|
+
type RuntimeConfig,
|
|
34
|
+
} from "./runtimeConfig.js";
|
|
35
|
+
|
|
36
|
+
export interface UiSettings {
|
|
37
|
+
layoutStyle: string;
|
|
38
|
+
theme: string;
|
|
39
|
+
workspaceDisplayMode: WorkspaceDisplayMode;
|
|
40
|
+
terminalTitleMode: TerminalTitleMode;
|
|
41
|
+
showBusyLoader: boolean;
|
|
42
|
+
terminalMouseMode: TerminalMouseMode;
|
|
43
|
+
customTheme?: Partial<Theme>;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface AuthSettings {
|
|
47
|
+
preference: AuthPreference;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface AppSettings {
|
|
51
|
+
ui: UiSettings;
|
|
52
|
+
auth: AuthSettings;
|
|
53
|
+
header: HeaderConfig;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Pick the first string value found under the camelCase or snake_case key.
|
|
57
|
+
// Falls through to the snake_case key only when the camelCase value is absent or not a string.
|
|
58
|
+
function pickStr(src: Record<string, unknown>, camel: string, snake: string): string | undefined {
|
|
59
|
+
const camelVal = src[camel];
|
|
60
|
+
if (typeof camelVal === "string") return camelVal;
|
|
61
|
+
const snakeVal = src[snake];
|
|
62
|
+
if (typeof snakeVal === "string") return snakeVal;
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Same as pickStr but for boolean values.
|
|
67
|
+
function pickBool(src: Record<string, unknown>, camel: string, snake: string): boolean | undefined {
|
|
68
|
+
const camelVal = src[camel];
|
|
69
|
+
if (typeof camelVal === "boolean") return camelVal;
|
|
70
|
+
const snakeVal = src[snake];
|
|
71
|
+
if (typeof snakeVal === "boolean") return snakeVal;
|
|
72
|
+
return undefined;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function normalizeAuthPreference(value: unknown): AuthPreference {
|
|
76
|
+
return typeof value === "string" && AUTH_PREFERENCES.some((item) => item.id === value)
|
|
77
|
+
? (value as AuthPreference)
|
|
78
|
+
: DEFAULT_AUTH_PREFERENCE;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function normalizeUiSettings(input: Partial<UiSettings> | null | undefined): UiSettings {
|
|
82
|
+
return {
|
|
83
|
+
layoutStyle: input?.layoutStyle ?? DEFAULT_LAYOUT_STYLE,
|
|
84
|
+
theme: input?.theme ?? DEFAULT_THEME,
|
|
85
|
+
workspaceDisplayMode: input?.workspaceDisplayMode ?? DEFAULT_WORKSPACE_DISPLAY_MODE,
|
|
86
|
+
terminalTitleMode: input?.terminalTitleMode ?? DEFAULT_TERMINAL_TITLE_MODE,
|
|
87
|
+
showBusyLoader: typeof input?.showBusyLoader === "boolean"
|
|
88
|
+
? input.showBusyLoader
|
|
89
|
+
: DEFAULT_SHOW_BUSY_LOADER,
|
|
90
|
+
terminalMouseMode: TERMINAL_MOUSE_MODES.includes(input?.terminalMouseMode as TerminalMouseMode)
|
|
91
|
+
? (input!.terminalMouseMode as TerminalMouseMode)
|
|
92
|
+
: DEFAULT_TERMINAL_MOUSE_MODE,
|
|
93
|
+
customTheme: input?.customTheme,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function normalizeHeaderConfig(input: Partial<HeaderConfig> | null | undefined): HeaderConfig {
|
|
98
|
+
return {
|
|
99
|
+
showBrand: typeof input?.showBrand === "boolean" ? input.showBrand : HEADER_CONFIG_DEFAULTS.showBrand,
|
|
100
|
+
showWorkspace: typeof input?.showWorkspace === "boolean" ? input.showWorkspace : HEADER_CONFIG_DEFAULTS.showWorkspace,
|
|
101
|
+
showProvider: typeof input?.showProvider === "boolean" ? input.showProvider : HEADER_CONFIG_DEFAULTS.showProvider,
|
|
102
|
+
showModel: typeof input?.showModel === "boolean" ? input.showModel : HEADER_CONFIG_DEFAULTS.showModel,
|
|
103
|
+
showReasoning: typeof input?.showReasoning === "boolean" ? input.showReasoning : HEADER_CONFIG_DEFAULTS.showReasoning,
|
|
104
|
+
showContext: typeof input?.showContext === "boolean" ? input.showContext : HEADER_CONFIG_DEFAULTS.showContext,
|
|
105
|
+
showAuthStatus: typeof input?.showAuthStatus === "boolean" ? input.showAuthStatus : HEADER_CONFIG_DEFAULTS.showAuthStatus,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function getDefaultSettings(): AppSettings {
|
|
110
|
+
return {
|
|
111
|
+
ui: normalizeUiSettings(null),
|
|
112
|
+
auth: {
|
|
113
|
+
preference: DEFAULT_AUTH_PREFERENCE,
|
|
114
|
+
},
|
|
115
|
+
header: normalizeHeaderConfig(null),
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function parseLegacyRuntime(data: Record<string, unknown>): RuntimeConfig {
|
|
120
|
+
return normalizeRuntimeConfig({
|
|
121
|
+
provider: typeof data.backend === "string" ? data.backend as RuntimeConfig["provider"] : undefined,
|
|
122
|
+
model: typeof data.model === "string" ? data.model as RuntimeConfig["model"] : undefined,
|
|
123
|
+
mode: typeof data.mode === "string" ? data.mode as RuntimeConfig["mode"] : undefined,
|
|
124
|
+
reasoningLevel: typeof data.reasoning_level === "string" ? data.reasoning_level as RuntimeConfig["reasoningLevel"] : undefined,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export function extractLegacyRuntime(data: unknown): RuntimeConfig | null {
|
|
129
|
+
if (!data || typeof data !== "object") {
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const record = data as Record<string, unknown>;
|
|
134
|
+
if (typeof record.runtime === "object" && record.runtime !== null) {
|
|
135
|
+
return normalizeRuntimeConfig(record.runtime as Partial<RuntimeConfig>);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const hasFlatRuntimeKeys = ["backend", "model", "mode", "reasoning_level"].some((key) => key in record);
|
|
139
|
+
return hasFlatRuntimeKeys ? parseLegacyRuntime(record) : null;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function stripLegacyRuntime(data: unknown): Record<string, unknown> {
|
|
143
|
+
if (!data || typeof data !== "object") {
|
|
144
|
+
return {};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const record = { ...(data as Record<string, unknown>) };
|
|
148
|
+
delete record.runtime;
|
|
149
|
+
delete record.backend;
|
|
150
|
+
delete record.model;
|
|
151
|
+
delete record.mode;
|
|
152
|
+
delete record.reasoning_level;
|
|
153
|
+
return record;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function parseTerminalTitleMode(uiSource: Record<string, unknown>, fallback: TerminalTitleMode): TerminalTitleMode {
|
|
157
|
+
const direct = uiSource.terminalTitleMode ?? uiSource.terminal_title_mode;
|
|
158
|
+
if (typeof direct === "string" && WORKSPACE_DISPLAY_MODES.includes(direct as WorkspaceDisplayMode)) {
|
|
159
|
+
return direct as TerminalTitleMode;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return fallback;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function parseTerminalMouseMode(uiSource: Record<string, unknown>, fallback: TerminalMouseMode): TerminalMouseMode {
|
|
166
|
+
const camel = uiSource.terminalMouseMode;
|
|
167
|
+
if (typeof camel === "string" && TERMINAL_MOUSE_MODES.includes(camel as TerminalMouseMode)) {
|
|
168
|
+
return camel as TerminalMouseMode;
|
|
169
|
+
}
|
|
170
|
+
const snake = uiSource.terminal_mouse_mode;
|
|
171
|
+
if (typeof snake === "string" && TERMINAL_MOUSE_MODES.includes(snake as TerminalMouseMode)) {
|
|
172
|
+
return snake as TerminalMouseMode;
|
|
173
|
+
}
|
|
174
|
+
return fallback;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function parseWorkspaceDisplayMode(uiSource: Record<string, unknown>, fallback: WorkspaceDisplayMode): WorkspaceDisplayMode {
|
|
178
|
+
const direct = uiSource.workspaceDisplayMode ?? uiSource.workspace_display_mode;
|
|
179
|
+
if (typeof direct === "string" && WORKSPACE_DISPLAY_MODES.includes(direct as WorkspaceDisplayMode)) {
|
|
180
|
+
return direct as WorkspaceDisplayMode;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const legacy = uiSource.directoryDisplayMode ?? uiSource.directory_display_mode;
|
|
184
|
+
if (typeof legacy === "string") {
|
|
185
|
+
if (WORKSPACE_DISPLAY_MODES.includes(legacy as WorkspaceDisplayMode)) {
|
|
186
|
+
return legacy as WorkspaceDisplayMode;
|
|
187
|
+
}
|
|
188
|
+
if (LEGACY_DIRECTORY_DISPLAY_MODES.includes(legacy as typeof LEGACY_DIRECTORY_DISPLAY_MODES[number])) {
|
|
189
|
+
return normalizeLegacyDirectoryDisplayMode(legacy as typeof LEGACY_DIRECTORY_DISPLAY_MODES[number]);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return fallback;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export function parseSettingsData(data: unknown): AppSettings {
|
|
197
|
+
const defaults = getDefaultSettings();
|
|
198
|
+
if (!data || typeof data !== "object") {
|
|
199
|
+
return defaults;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const record = data as Record<string, unknown>;
|
|
203
|
+
const uiSource = typeof record.ui === "object" && record.ui !== null
|
|
204
|
+
? record.ui as Record<string, unknown>
|
|
205
|
+
: record;
|
|
206
|
+
const authSource = typeof record.auth === "object" && record.auth !== null
|
|
207
|
+
? record.auth as Record<string, unknown>
|
|
208
|
+
: record;
|
|
209
|
+
|
|
210
|
+
const headerSource = typeof record.header === "object" && record.header !== null
|
|
211
|
+
? record.header as Record<string, unknown>
|
|
212
|
+
: {};
|
|
213
|
+
|
|
214
|
+
return {
|
|
215
|
+
ui: normalizeUiSettings({
|
|
216
|
+
layoutStyle: pickStr(uiSource, "layoutStyle", "layout_style") ?? defaults.ui.layoutStyle,
|
|
217
|
+
theme: pickStr(uiSource, "theme", "theme") ?? defaults.ui.theme,
|
|
218
|
+
workspaceDisplayMode: parseWorkspaceDisplayMode(uiSource, defaults.ui.workspaceDisplayMode),
|
|
219
|
+
terminalTitleMode: parseTerminalTitleMode(uiSource, defaults.ui.terminalTitleMode),
|
|
220
|
+
showBusyLoader: pickBool(uiSource, "showBusyLoader", "show_busy_loader") ?? defaults.ui.showBusyLoader,
|
|
221
|
+
terminalMouseMode: parseTerminalMouseMode(uiSource, defaults.ui.terminalMouseMode),
|
|
222
|
+
customTheme: (uiSource.customTheme ?? uiSource.custom_theme) as Partial<Theme> | undefined,
|
|
223
|
+
}),
|
|
224
|
+
auth: {
|
|
225
|
+
preference: normalizeAuthPreference(authSource.preference ?? authSource.auth_preference),
|
|
226
|
+
},
|
|
227
|
+
header: normalizeHeaderConfig({
|
|
228
|
+
showBrand: pickBool(headerSource, "showBrand", "show_brand"),
|
|
229
|
+
showWorkspace: pickBool(headerSource, "showWorkspace", "show_workspace"),
|
|
230
|
+
showProvider: pickBool(headerSource, "showProvider", "show_provider"),
|
|
231
|
+
showModel: pickBool(headerSource, "showModel", "show_model"),
|
|
232
|
+
showReasoning: pickBool(headerSource, "showReasoning", "show_reasoning"),
|
|
233
|
+
showContext: pickBool(headerSource, "showContext", "show_context"),
|
|
234
|
+
showAuthStatus: pickBool(headerSource, "showAuthStatus", "show_auth_status"),
|
|
235
|
+
}),
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export function serializeSettings(settings: AppSettings): Record<string, unknown> {
|
|
240
|
+
return {
|
|
241
|
+
ui: {
|
|
242
|
+
layout_style: settings.ui.layoutStyle,
|
|
243
|
+
theme: settings.ui.theme,
|
|
244
|
+
workspace_display_mode: settings.ui.workspaceDisplayMode,
|
|
245
|
+
terminal_title_mode: settings.ui.terminalTitleMode,
|
|
246
|
+
show_busy_loader: settings.ui.showBusyLoader,
|
|
247
|
+
terminal_mouse_mode: settings.ui.terminalMouseMode,
|
|
248
|
+
custom_theme: settings.ui.customTheme,
|
|
249
|
+
},
|
|
250
|
+
auth: {
|
|
251
|
+
preference: settings.auth.preference,
|
|
252
|
+
},
|
|
253
|
+
header: {
|
|
254
|
+
show_brand: settings.header.showBrand,
|
|
255
|
+
show_workspace: settings.header.showWorkspace,
|
|
256
|
+
show_provider: settings.header.showProvider,
|
|
257
|
+
show_model: settings.header.showModel,
|
|
258
|
+
show_reasoning: settings.header.showReasoning,
|
|
259
|
+
show_context: settings.header.showContext,
|
|
260
|
+
show_auth_status: settings.header.showAuthStatus,
|
|
261
|
+
},
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function writeJsonFile(filePath: string, data: Record<string, unknown>): void {
|
|
266
|
+
mkdirSync(dirname(filePath), { recursive: true });
|
|
267
|
+
const tmpFile = `${filePath}.tmp`;
|
|
268
|
+
writeFileSync(tmpFile, JSON.stringify(data, null, 2), "utf-8");
|
|
269
|
+
renameSync(tmpFile, filePath);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function maybeMigrateLegacyRuntime(rawData: unknown): void {
|
|
273
|
+
const legacyRuntime = extractLegacyRuntime(rawData);
|
|
274
|
+
if (!legacyRuntime) {
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
try {
|
|
279
|
+
const codexConfigFile = getCodexConfigFile();
|
|
280
|
+
const existingConfig = existsSync(codexConfigFile)
|
|
281
|
+
? parseTomlDocument(readFileSync(codexConfigFile, "utf-8"))
|
|
282
|
+
: {};
|
|
283
|
+
const mergedConfig = mergeRuntimeIntoTomlConfig(existingConfig, legacyRuntime);
|
|
284
|
+
mkdirSync(dirname(codexConfigFile), { recursive: true });
|
|
285
|
+
const tmpTomlFile = `${codexConfigFile}.tmp`;
|
|
286
|
+
writeFileSync(tmpTomlFile, serializeTomlDocument(mergedConfig), "utf-8");
|
|
287
|
+
renameSync(tmpTomlFile, codexConfigFile);
|
|
288
|
+
writeJsonFile(SETTINGS_FILE, stripLegacyRuntime(rawData));
|
|
289
|
+
} catch {
|
|
290
|
+
// Best-effort migration only; keep legacy JSON intact if anything fails.
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
export function loadSettings(): AppSettings {
|
|
295
|
+
try {
|
|
296
|
+
const text = readFileSync(SETTINGS_FILE, "utf-8");
|
|
297
|
+
const rawData = JSON.parse(text);
|
|
298
|
+
maybeMigrateLegacyRuntime(rawData);
|
|
299
|
+
return parseSettingsData(rawData);
|
|
300
|
+
} catch {
|
|
301
|
+
return getDefaultSettings();
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
export function saveSettings(settings: AppSettings): void {
|
|
306
|
+
try {
|
|
307
|
+
writeJsonFile(SETTINGS_FILE, serializeSettings(settings));
|
|
308
|
+
} catch {
|
|
309
|
+
// Silently ignore — settings are best-effort
|
|
310
|
+
}
|
|
311
|
+
}
|
|
@@ -0,0 +1,218 @@
|
|
|
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
|
+
});
|