ubume 0.1.0
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 +88 -0
- package/bin/codexa-local-harness-bridge.js +3 -0
- package/bin/codexa.js +28 -0
- package/bin/ubume-local-harness-bridge.js +325 -0
- package/bin/ubume.js +398 -0
- package/package.json +66 -0
- package/src/app.tsx +5759 -0
- package/src/commands/handler.ts +889 -0
- package/src/config/appVersion.ts +69 -0
- package/src/config/buildInfo.ts +3 -0
- package/src/config/launchArgs.ts +196 -0
- package/src/config/layeredConfig.ts +853 -0
- package/src/config/legacyEnv.ts +16 -0
- package/src/config/persistence.ts +377 -0
- package/src/config/runtimeConfig.ts +558 -0
- package/src/config/settings.ts +405 -0
- package/src/config/toml-serialize.ts +109 -0
- package/src/config/trustStore.ts +84 -0
- package/src/config/updateCheckCache.ts +85 -0
- package/src/core/README.md +55 -0
- package/src/core/agent/loop.ts +464 -0
- package/src/core/agent/protocol.ts +345 -0
- package/src/core/agent/tools.ts +423 -0
- package/src/core/auth/codexAuth.ts +359 -0
- package/src/core/codex/codexExecArgs.ts +166 -0
- package/src/core/codex/codexLaunch.ts +163 -0
- package/src/core/codex/codexPrompt.ts +429 -0
- package/src/core/debug/inputDebug.ts +51 -0
- package/src/core/debug/localStreamDebug.ts +50 -0
- package/src/core/debug/modelStateDebug.ts +35 -0
- package/src/core/executables/antigravityExecutable.ts +48 -0
- package/src/core/executables/claudeExecutable.ts +63 -0
- package/src/core/executables/codexExecutable.ts +160 -0
- package/src/core/executables/executableResolver.ts +164 -0
- package/src/core/executables/geminiExecutable.ts +78 -0
- package/src/core/models/codexCapabilities.ts +97 -0
- package/src/core/models/codexModelCapabilities.ts +624 -0
- package/src/core/models/codexModelsCacheSeed.ts +153 -0
- package/src/core/models/modelSpecs.ts +303 -0
- package/src/core/models/providerModelCache.ts +94 -0
- package/src/core/perf/profiler.ts +125 -0
- package/src/core/perf/renderDebug.ts +398 -0
- package/src/core/process/CommandRunner.ts +280 -0
- package/src/core/process/processValidation.ts +111 -0
- package/src/core/providerLauncher/launcher.ts +220 -0
- package/src/core/providerLauncher/registry.ts +354 -0
- package/src/core/providerLauncher/types.ts +95 -0
- package/src/core/providerLauncher/workspaceConfig.ts +487 -0
- package/src/core/providerRuntime/anthropic.ts +580 -0
- package/src/core/providerRuntime/antigravity.ts +500 -0
- package/src/core/providerRuntime/capabilityProfile.ts +383 -0
- package/src/core/providerRuntime/claudeCodeDiscovery.ts +724 -0
- package/src/core/providerRuntime/claudeCodeDiscoveryDebug.ts +55 -0
- package/src/core/providerRuntime/codexaCupy.ts +97 -0
- package/src/core/providerRuntime/codexaNative.ts +425 -0
- package/src/core/providerRuntime/contextMetadata.ts +397 -0
- package/src/core/providerRuntime/gemini.ts +789 -0
- package/src/core/providerRuntime/lmstudio.ts +118 -0
- package/src/core/providerRuntime/local.ts +770 -0
- package/src/core/providerRuntime/localHarness/runtime.ts +1090 -0
- package/src/core/providerRuntime/localOutputBudget.ts +17 -0
- package/src/core/providerRuntime/mistralVibe.ts +667 -0
- package/src/core/providerRuntime/models.ts +175 -0
- package/src/core/providerRuntime/reasoning.ts +20 -0
- package/src/core/providerRuntime/registry.ts +286 -0
- package/src/core/providerRuntime/types.ts +142 -0
- package/src/core/providerRuntime/unsloth.ts +216 -0
- package/src/core/providers/codexJsonStream.ts +305 -0
- package/src/core/providers/codexSubprocess.ts +378 -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 +91 -0
- package/src/core/shared/attachments.ts +101 -0
- package/src/core/shared/cleanupFastFail.ts +67 -0
- package/src/core/shared/clipboard.ts +24 -0
- package/src/core/shared/clipboardImage.ts +111 -0
- package/src/core/shared/githubDiagnostics.ts +222 -0
- package/src/core/shared/hollowResponseFormat.ts +39 -0
- package/src/core/terminal/clearFrameBoundary.ts +852 -0
- package/src/core/terminal/frameLock.ts +110 -0
- package/src/core/terminal/inkRenderReset.ts +123 -0
- package/src/core/terminal/startupClear.ts +20 -0
- package/src/core/terminal/terminalCapabilities.ts +100 -0
- package/src/core/terminal/terminalControl.ts +169 -0
- package/src/core/terminal/terminalSanitize.ts +147 -0
- package/src/core/terminal/terminalTitle.ts +400 -0
- package/src/core/version/channel.ts +27 -0
- package/src/core/version/packageManager.ts +119 -0
- package/src/core/version/updateCheck.ts +203 -0
- package/src/core/workspace/appData.ts +107 -0
- package/src/core/workspace/conversationStore.ts +335 -0
- package/src/core/workspace/launchContext.ts +259 -0
- package/src/core/workspace/planStorage.ts +135 -0
- package/src/core/workspace/projectInstructions.ts +54 -0
- package/src/core/workspace/scratchDir.ts +64 -0
- package/src/core/workspace/workspaceActivity.ts +384 -0
- package/src/core/workspace/workspaceGuard.ts +377 -0
- package/src/core/workspace/workspaceRoot.ts +47 -0
- package/src/exec.ts +73 -0
- package/src/headless/execArgs.ts +296 -0
- package/src/headless/execRunner.ts +304 -0
- package/src/index.tsx +270 -0
- package/src/legacyEnvBootstrap.ts +5 -0
- package/src/session/appSession.ts +771 -0
- package/src/session/chatLifecycle.ts +994 -0
- package/src/session/conversation.ts +107 -0
- package/src/session/liveRenderScheduler.ts +214 -0
- package/src/session/persistedResponse.ts +93 -0
- package/src/session/planFlow.ts +159 -0
- package/src/session/planTranscript.ts +19 -0
- package/src/session/promptRunSchedule.ts +26 -0
- package/src/session/types.ts +234 -0
- package/src/test/runtimeTestUtils.ts +14 -0
- package/src/types/react-dom.d.ts +3 -0
- package/src/ui/chrome/ActivityBars.tsx +68 -0
- package/src/ui/chrome/ActivityIndicator.tsx +58 -0
- package/src/ui/chrome/AnimatedStatusText.tsx +69 -0
- package/src/ui/chrome/AppShell.tsx +474 -0
- package/src/ui/chrome/BottomComposer.tsx +1135 -0
- package/src/ui/chrome/DashCard.tsx +82 -0
- package/src/ui/chrome/RunFooter.tsx +65 -0
- package/src/ui/chrome/RuntimeStatusBar.tsx +108 -0
- package/src/ui/chrome/Spinner.tsx +25 -0
- package/src/ui/chrome/TopHeader.tsx +439 -0
- package/src/ui/chrome/UpdateAvailableCard.tsx +42 -0
- package/src/ui/chrome/busyStatusAnimation.ts +11 -0
- package/src/ui/input/commandNormalize.ts +66 -0
- package/src/ui/input/focus.ts +73 -0
- package/src/ui/input/imageAttachments.ts +18 -0
- package/src/ui/input/inputBuffer.ts +203 -0
- package/src/ui/input/pastedContent.ts +75 -0
- package/src/ui/input/rawArrowKeys.ts +28 -0
- package/src/ui/input/slashCommands.ts +43 -0
- package/src/ui/input/useStdinRawModeLease.ts +23 -0
- package/src/ui/layout.ts +560 -0
- package/src/ui/panels/AttachmentImportPanel.tsx +131 -0
- package/src/ui/panels/AuthPanel.tsx +149 -0
- package/src/ui/panels/BackendPicker.tsx +28 -0
- package/src/ui/panels/ModePicker.tsx +31 -0
- package/src/ui/panels/ModelPicker.tsx +31 -0
- package/src/ui/panels/ModelPickerScreen.tsx +761 -0
- package/src/ui/panels/ModelReasoningPicker.tsx +458 -0
- package/src/ui/panels/Panel.tsx +51 -0
- package/src/ui/panels/PermissionsPanel.tsx +78 -0
- package/src/ui/panels/PlanActionPicker.tsx +187 -0
- package/src/ui/panels/ProviderPicker.tsx +753 -0
- package/src/ui/panels/ProviderSetupPrompt.tsx +52 -0
- package/src/ui/panels/ReasoningPicker.tsx +46 -0
- package/src/ui/panels/ResumePicker.tsx +90 -0
- package/src/ui/panels/SelectionPanel.tsx +138 -0
- package/src/ui/panels/SettingsPanel.tsx +156 -0
- package/src/ui/panels/TextEntryPanel.tsx +139 -0
- package/src/ui/panels/ThemePicker.tsx +32 -0
- package/src/ui/panels/ToolApprovalPanel.tsx +46 -0
- package/src/ui/panels/UpdatePromptPanel.tsx +236 -0
- package/src/ui/panels/responsivePickerViewport.ts +64 -0
- package/src/ui/render/Markdown.tsx +331 -0
- package/src/ui/render/diffRenderer.ts +116 -0
- package/src/ui/render/logoVariants.ts +113 -0
- package/src/ui/render/modeDisplay.ts +52 -0
- package/src/ui/render/outputPipeline.ts +64 -0
- package/src/ui/render/runtimeDisplay.ts +128 -0
- package/src/ui/render/terminalAnswerFormat.ts +128 -0
- package/src/ui/render/textLayout.ts +392 -0
- package/src/ui/theme.tsx +274 -0
- package/src/ui/themeFlow.ts +41 -0
- package/src/ui/timeline/ActionRequiredBlock.tsx +38 -0
- package/src/ui/timeline/AgentBlock.tsx +130 -0
- package/src/ui/timeline/StaticIntroItem.tsx +54 -0
- package/src/ui/timeline/ThinkingBlock.tsx +100 -0
- package/src/ui/timeline/Timeline.tsx +1410 -0
- package/src/ui/timeline/TranscriptShell.tsx +302 -0
- package/src/ui/timeline/TurnGroup.tsx +673 -0
- package/src/ui/timeline/layoutListWindow.ts +145 -0
- package/src/ui/timeline/liveViewportWindow.ts +68 -0
- package/src/ui/timeline/progressEntries.ts +156 -0
- package/src/ui/timeline/runActivityView.ts +37 -0
- package/src/ui/timeline/staticTranscriptCache.ts +174 -0
- package/src/ui/timeline/streamCoalesce.ts +53 -0
- package/src/ui/timeline/timelineMeasure.ts +3273 -0
- package/src/ui/useThrottledValue.ts +31 -0
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
import { posix, win32 } from "path";
|
|
2
|
+
import { normalizeWorkspaceRoot } from "./workspaceRoot.js";
|
|
3
|
+
|
|
4
|
+
type PathStyle = "windows" | "posix";
|
|
5
|
+
|
|
6
|
+
export function normalizeDiagnosticPath(filePath: string): string {
|
|
7
|
+
let pathPart = filePath;
|
|
8
|
+
let drivePrefix = "";
|
|
9
|
+
|
|
10
|
+
if (/^[A-Za-z]:[\\/]/.test(filePath)) {
|
|
11
|
+
drivePrefix = filePath.slice(0, 2);
|
|
12
|
+
pathPart = filePath.slice(2);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Robustly strip trailing colon-separated line/column numbers.
|
|
16
|
+
// Handles:
|
|
17
|
+
// path/to/file.rs:109:12
|
|
18
|
+
// path/to/file.rs:109
|
|
19
|
+
// C:\path\to\file.rs:12:5
|
|
20
|
+
const diagnosticSuffixRegex = /:(\d+)(?::(\d+))?$/;
|
|
21
|
+
pathPart = pathPart.replace(diagnosticSuffixRegex, "");
|
|
22
|
+
|
|
23
|
+
return drivePrefix + pathPart;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface WorkspacePathViolation {
|
|
27
|
+
rawPath: string;
|
|
28
|
+
normalizedPath: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const QUOTED_ABSOLUTE_PATH_PATTERN =
|
|
32
|
+
/(["'`])((?:[A-Za-z]:[\\/]|\\\\[^\\/\r\n]+[\\/][^\\/\r\n]+[\\/]|\/|~\/)[^"'`\r\n]+?)\1/g;
|
|
33
|
+
const QUOTED_RELATIVE_PATH_PATTERN = /(["'`])((?:\.\.?[\\/])[^"'`\r\n]+?)\1/g;
|
|
34
|
+
const WINDOWS_DRIVE_PATH_PATTERN = /(?:^|[\s([{\],;=])([A-Za-z]:[\\/][^\s"'`<>|]+)/g;
|
|
35
|
+
const WINDOWS_UNC_PATH_PATTERN =
|
|
36
|
+
/(?:^|[\s([{\],;=])(\\\\[^\\/\s"'`<>|]+[\\/][^\\/\s"'`<>|]+(?:[\\/][^\s"'`<>|]+)*)/g;
|
|
37
|
+
const POSIX_ABSOLUTE_PATH_PATTERN = /(?:^|[\s([{\],;=])((?:\/|~\/)(?:[^\/\s"'`<>|]+\/)+[^\s"'`<>|]+)/g;
|
|
38
|
+
const RELATIVE_PATH_PATTERN = /(?:^|[\s([{\],;=])((?:\.\.?[\\/])(?:[^\s"'`<>|]+(?:[\\/][^\s"'`<>|]+)*))/g;
|
|
39
|
+
const RUST_RELATIVE_DIAGNOSTIC_PATH_PATTERN =
|
|
40
|
+
/(?:^|[\s([{\],;=])((?:[A-Za-z0-9_.-]+[\\/])+[A-Za-z0-9_.-]+\.rs(?::\d+(?::\d+)?)?)/g;
|
|
41
|
+
const TRAILING_PUNCTUATION_PATTERN = /[),.;:\]}]+$/;
|
|
42
|
+
const DIRECTORY_NAVIGATION_PATTERN = /(?:^|[;&\n]|&&|\|\|)\s*(cd|pushd|set-location)\b/i;
|
|
43
|
+
const CARGO_REGISTRY_INDEX_DIRECTORY_PATTERN = /^index\.crates\.io-[A-Za-z0-9-]+$/;
|
|
44
|
+
|
|
45
|
+
function detectPathStyle(pathValue: string): PathStyle | null {
|
|
46
|
+
if (/^[A-Za-z]:[\\/]/.test(pathValue) || /^\\\\[^\\\/]+[\\\/][^\\\/]+/.test(pathValue)) {
|
|
47
|
+
return "windows";
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (pathValue.startsWith("/") || pathValue.startsWith("~/")) {
|
|
51
|
+
return "posix";
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function getPathApi(style: PathStyle) {
|
|
58
|
+
return style === "windows" ? win32 : posix;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function stripWrappingQuotes(text: string): string {
|
|
62
|
+
return text.replace(/^["'`]|["'`]$/g, "");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function stripTrailingPunctuation(text: string): string {
|
|
66
|
+
return text.replace(TRAILING_PUNCTUATION_PATTERN, "");
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function isExplicitRelativePath(pathValue: string): boolean {
|
|
70
|
+
return /^(?:\.\.?[\\/])/.test(pathValue);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function hasRustRelativeDiagnosticShape(pathValue: string): boolean {
|
|
74
|
+
return /^(?:[A-Za-z0-9_.-]+[\\/])+[A-Za-z0-9_.-]+\.rs(?::\d+(?::\d+)?)?$/.test(pathValue);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function normalizedPathSegments(pathValue: string): string[] {
|
|
78
|
+
return normalizeDiagnosticPath(pathValue)
|
|
79
|
+
.replace(/\\/g, "/")
|
|
80
|
+
.split("/")
|
|
81
|
+
.filter(Boolean)
|
|
82
|
+
.map((segment) => segment.toLowerCase());
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function isSkippedExternalDependencyPath(pathValue: string): boolean {
|
|
86
|
+
const segments = normalizedPathSegments(pathValue);
|
|
87
|
+
|
|
88
|
+
for (let i = 0; i < segments.length; i++) {
|
|
89
|
+
const segment = segments[i];
|
|
90
|
+
const next = segments[i + 1];
|
|
91
|
+
const afterNext = segments[i + 2];
|
|
92
|
+
|
|
93
|
+
if (segment === ".cargo" && next === "registry") return true;
|
|
94
|
+
if (segment === ".cargo" && next === "git" && afterNext === "checkouts") return true;
|
|
95
|
+
if (
|
|
96
|
+
segment === "target" ||
|
|
97
|
+
segment === "node_modules" ||
|
|
98
|
+
segment === ".pnpm-store" ||
|
|
99
|
+
segment === ".npm" ||
|
|
100
|
+
segment === ".cache"
|
|
101
|
+
) {
|
|
102
|
+
return true;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function normalizeAbsolutePath(pathValue: string, style: PathStyle): string {
|
|
110
|
+
const pathApi = getPathApi(style);
|
|
111
|
+
const resolved = pathApi.normalize(pathApi.resolve(pathValue));
|
|
112
|
+
const { root } = pathApi.parse(resolved);
|
|
113
|
+
|
|
114
|
+
let normalized = resolved;
|
|
115
|
+
while (normalized.length > root.length && /[\\/]+$/.test(normalized)) {
|
|
116
|
+
normalized = normalized.slice(0, -1);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return normalized;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function normalizeComparisonPath(pathValue: string, style: PathStyle): string {
|
|
123
|
+
const normalized = normalizeAbsolutePath(pathValue, style);
|
|
124
|
+
return style === "windows" ? normalized.toLowerCase() : normalized;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function normalizeAllowedRoots(workspaceRoot: string, allowedRoots: readonly string[] = []): string[] {
|
|
128
|
+
const normalizedWorkspace = normalizeWorkspaceRoot(workspaceRoot);
|
|
129
|
+
const roots = [normalizedWorkspace, ...allowedRoots];
|
|
130
|
+
const seen = new Set<string>();
|
|
131
|
+
const normalizedRoots: string[] = [];
|
|
132
|
+
|
|
133
|
+
for (const root of roots) {
|
|
134
|
+
const style = detectPathStyle(root);
|
|
135
|
+
if (!style) continue;
|
|
136
|
+
const normalizedRoot = normalizeAbsolutePath(root, style);
|
|
137
|
+
const comparisonKey = normalizeComparisonPath(normalizedRoot, style);
|
|
138
|
+
if (seen.has(comparisonKey)) continue;
|
|
139
|
+
seen.add(comparisonKey);
|
|
140
|
+
normalizedRoots.push(normalizedRoot);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return normalizedRoots;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export function resolveWorkspacePath(pathValue: string, workspaceRoot: string): string {
|
|
147
|
+
const trimmed = stripWrappingQuotes(pathValue.trim());
|
|
148
|
+
const workspaceStyle = detectPathStyle(workspaceRoot) ?? "windows";
|
|
149
|
+
const explicitStyle = detectPathStyle(trimmed);
|
|
150
|
+
|
|
151
|
+
if (explicitStyle) {
|
|
152
|
+
return normalizeAbsolutePath(trimmed, explicitStyle);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const pathApi = getPathApi(workspaceStyle);
|
|
156
|
+
// Normalize separators in relative paths to match the workspace style so
|
|
157
|
+
// that Windows-style backslashes work correctly on POSIX systems and vice versa.
|
|
158
|
+
const separatorNormalized = workspaceStyle === "windows"
|
|
159
|
+
? trimmed.replace(/\//g, "\\")
|
|
160
|
+
: trimmed.replace(/\\/g, "/");
|
|
161
|
+
return normalizeAbsolutePath(pathApi.resolve(workspaceRoot, separatorNormalized), workspaceStyle);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export function isPathInsideWorkspace(pathValue: string, workspaceRoot: string): boolean {
|
|
165
|
+
const normalizedWorkspace = normalizeWorkspaceRoot(workspaceRoot);
|
|
166
|
+
return isPathInsideAllowedRoots(pathValue, normalizedWorkspace);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export function isPathInsideAllowedRoots(
|
|
170
|
+
pathValue: string,
|
|
171
|
+
workspaceRoot: string,
|
|
172
|
+
allowedRoots: readonly string[] = [],
|
|
173
|
+
): boolean {
|
|
174
|
+
const normalizedWorkspace = normalizeWorkspaceRoot(workspaceRoot);
|
|
175
|
+
const normalizedRoots = normalizeAllowedRoots(normalizedWorkspace, allowedRoots);
|
|
176
|
+
const resolvedTarget = resolveWorkspacePath(pathValue, normalizedWorkspace);
|
|
177
|
+
const targetStyle = detectPathStyle(resolvedTarget);
|
|
178
|
+
|
|
179
|
+
if (!targetStyle) {
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
for (const root of normalizedRoots) {
|
|
184
|
+
const rootStyle = detectPathStyle(root);
|
|
185
|
+
if (!rootStyle || rootStyle !== targetStyle) {
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const pathApi = getPathApi(rootStyle);
|
|
190
|
+
const rootForComparison = normalizeComparisonPath(root, rootStyle);
|
|
191
|
+
const targetForComparison = normalizeComparisonPath(resolvedTarget, rootStyle);
|
|
192
|
+
const relativePath = pathApi.relative(rootForComparison, targetForComparison);
|
|
193
|
+
|
|
194
|
+
if (
|
|
195
|
+
relativePath === "" ||
|
|
196
|
+
(!relativePath.startsWith("..") && !pathApi.isAbsolute(relativePath))
|
|
197
|
+
) {
|
|
198
|
+
return true;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return false;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export function extractExplicitPathReferences(text: string): string[] {
|
|
206
|
+
const matches: string[] = [];
|
|
207
|
+
const seen = new Set<string>();
|
|
208
|
+
|
|
209
|
+
const addMatch = (candidate: string) => {
|
|
210
|
+
const cleaned = stripTrailingPunctuation(stripWrappingQuotes(candidate.trim()));
|
|
211
|
+
if (!cleaned || (!detectPathStyle(cleaned) && !isExplicitRelativePath(cleaned) && !hasRustRelativeDiagnosticShape(cleaned))) {
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const comparisonKey = detectPathStyle(cleaned) === "posix" ? cleaned : cleaned.toLowerCase();
|
|
216
|
+
if (seen.has(comparisonKey)) {
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
seen.add(comparisonKey);
|
|
221
|
+
matches.push(cleaned);
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
for (const pattern of [
|
|
225
|
+
QUOTED_ABSOLUTE_PATH_PATTERN,
|
|
226
|
+
QUOTED_RELATIVE_PATH_PATTERN,
|
|
227
|
+
WINDOWS_DRIVE_PATH_PATTERN,
|
|
228
|
+
WINDOWS_UNC_PATH_PATTERN,
|
|
229
|
+
POSIX_ABSOLUTE_PATH_PATTERN,
|
|
230
|
+
RELATIVE_PATH_PATTERN,
|
|
231
|
+
RUST_RELATIVE_DIAGNOSTIC_PATH_PATTERN,
|
|
232
|
+
]) {
|
|
233
|
+
pattern.lastIndex = 0;
|
|
234
|
+
let match = pattern.exec(text);
|
|
235
|
+
while (match) {
|
|
236
|
+
addMatch(match[2] ?? match[1] ?? "");
|
|
237
|
+
match = pattern.exec(text);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return matches;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
export function formatSkippedDependencyPath(pathValue: string): string {
|
|
245
|
+
const parts = pathValue.replace(/\\/g, "/").split("/");
|
|
246
|
+
const registryIndex = parts.indexOf("registry");
|
|
247
|
+
if (
|
|
248
|
+
registryIndex !== -1
|
|
249
|
+
&& parts[registryIndex + 1] === "src"
|
|
250
|
+
&& CARGO_REGISTRY_INDEX_DIRECTORY_PATTERN.test(parts[registryIndex + 2] ?? "")
|
|
251
|
+
) {
|
|
252
|
+
return parts.slice(registryIndex + 3).join("/");
|
|
253
|
+
}
|
|
254
|
+
const gitIndex = parts.indexOf("git");
|
|
255
|
+
if (gitIndex !== -1 && parts[gitIndex + 1] === "checkouts") {
|
|
256
|
+
return parts.slice(gitIndex + 2).join("/");
|
|
257
|
+
}
|
|
258
|
+
return parts[parts.length - 1] ?? pathValue;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
export function findOutsideWorkspacePaths(
|
|
262
|
+
text: string,
|
|
263
|
+
workspaceRoot: string,
|
|
264
|
+
allowedRoots: readonly string[] = [],
|
|
265
|
+
): { violations: WorkspacePathViolation[]; skippedExternalPaths: string[] } {
|
|
266
|
+
const violations: WorkspacePathViolation[] = [];
|
|
267
|
+
const skippedExternalPaths: string[] = [];
|
|
268
|
+
const seen = new Set<string>();
|
|
269
|
+
const workspaceStyle = detectPathStyle(workspaceRoot) ?? "windows";
|
|
270
|
+
|
|
271
|
+
for (const rawPath of extractExplicitPathReferences(text)) {
|
|
272
|
+
const cleanPath = normalizeDiagnosticPath(rawPath);
|
|
273
|
+
if (isSkippedExternalDependencyPath(cleanPath)) {
|
|
274
|
+
skippedExternalPaths.push(cleanPath);
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (isPathInsideAllowedRoots(cleanPath, workspaceRoot, allowedRoots)) {
|
|
279
|
+
continue;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const explicitStyle = detectPathStyle(cleanPath) ?? workspaceStyle;
|
|
283
|
+
const normalizedPath = detectPathStyle(cleanPath)
|
|
284
|
+
? normalizeAbsolutePath(cleanPath, explicitStyle)
|
|
285
|
+
: resolveWorkspacePath(cleanPath, workspaceRoot);
|
|
286
|
+
if (isSkippedExternalDependencyPath(normalizedPath)) {
|
|
287
|
+
skippedExternalPaths.push(normalizedPath);
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const comparisonKey = explicitStyle === "windows" ? normalizedPath.toLowerCase() : normalizedPath;
|
|
292
|
+
|
|
293
|
+
if (seen.has(comparisonKey)) {
|
|
294
|
+
continue;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
seen.add(comparisonKey);
|
|
298
|
+
violations.push({ rawPath, normalizedPath });
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
return { violations, skippedExternalPaths };
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
export function containsDirectoryNavigationCommand(command: string): boolean {
|
|
305
|
+
return DIRECTORY_NAVIGATION_PATTERN.test(command);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function formatOutsidePathBlockMessage(
|
|
309
|
+
heading: string,
|
|
310
|
+
workspaceRoot: string,
|
|
311
|
+
violations: WorkspacePathViolation[],
|
|
312
|
+
allowedRoots: readonly string[] = [],
|
|
313
|
+
): string {
|
|
314
|
+
const normalizedWorkspace = normalizeWorkspaceRoot(workspaceRoot);
|
|
315
|
+
const normalizedAllowedRoots = normalizeAllowedRoots(normalizedWorkspace, allowedRoots)
|
|
316
|
+
.filter((root) => normalizeComparisonPath(root, detectPathStyle(root) ?? "windows") !== normalizeComparisonPath(normalizedWorkspace, detectPathStyle(normalizedWorkspace) ?? "windows"));
|
|
317
|
+
const paths = violations.map((item) => ` - ${item.normalizedPath}`).join("\n");
|
|
318
|
+
const extraRootsBlock = normalizedAllowedRoots.length > 0
|
|
319
|
+
? [
|
|
320
|
+
"Allowed writable roots:",
|
|
321
|
+
...normalizedAllowedRoots.map((root) => ` - ${root}`),
|
|
322
|
+
].join("\n")
|
|
323
|
+
: null;
|
|
324
|
+
|
|
325
|
+
return [
|
|
326
|
+
heading,
|
|
327
|
+
`Locked workspace: ${normalizedWorkspace}`,
|
|
328
|
+
extraRootsBlock,
|
|
329
|
+
"Outside path references:",
|
|
330
|
+
paths,
|
|
331
|
+
"Use relative paths inside this workspace, or relaunch the CLI from the folder you want to edit.",
|
|
332
|
+
].filter(Boolean).join("\n");
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
export function getPromptWorkspaceGuardMessage(
|
|
336
|
+
prompt: string,
|
|
337
|
+
workspaceRoot: string,
|
|
338
|
+
allowedRoots: readonly string[] = [],
|
|
339
|
+
): string | null {
|
|
340
|
+
const { violations } = findOutsideWorkspacePaths(prompt, workspaceRoot, allowedRoots);
|
|
341
|
+
if (violations.length === 0) {
|
|
342
|
+
return null;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
return formatOutsidePathBlockMessage(
|
|
346
|
+
"Run blocked: this session can only work inside the locked workspace.",
|
|
347
|
+
workspaceRoot,
|
|
348
|
+
violations,
|
|
349
|
+
allowedRoots,
|
|
350
|
+
);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
export function getShellWorkspaceGuardMessage(
|
|
354
|
+
command: string,
|
|
355
|
+
workspaceRoot: string,
|
|
356
|
+
allowedRoots: readonly string[] = [],
|
|
357
|
+
): string | null {
|
|
358
|
+
if (containsDirectoryNavigationCommand(command)) {
|
|
359
|
+
return [
|
|
360
|
+
"Shell command blocked: this session is locked to the launch folder.",
|
|
361
|
+
`Locked workspace: ${normalizeWorkspaceRoot(workspaceRoot)}`,
|
|
362
|
+
"Directory-changing commands like cd, Set-Location, and pushd are disabled here.",
|
|
363
|
+
].join("\n");
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
const { violations } = findOutsideWorkspacePaths(command, workspaceRoot, allowedRoots);
|
|
367
|
+
if (violations.length === 0) {
|
|
368
|
+
return null;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
return formatOutsidePathBlockMessage(
|
|
372
|
+
"Shell command blocked: it references paths outside the locked workspace.",
|
|
373
|
+
workspaceRoot,
|
|
374
|
+
violations,
|
|
375
|
+
allowedRoots,
|
|
376
|
+
);
|
|
377
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { parse, resolve, win32 } from "path";
|
|
2
|
+
|
|
3
|
+
function isWindowsStylePath(p: string): boolean {
|
|
4
|
+
return /^[A-Za-z]:[\\/]/.test(p) || /^\\\\/.test(p);
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function normalizeWorkspaceRoot(pathValue: string): string {
|
|
8
|
+
let candidate = pathValue.trim();
|
|
9
|
+
// Guard against obviously unsafe values that could break command-line parsing.
|
|
10
|
+
if (candidate.includes("\n") || candidate.includes("\r") || candidate.includes("\0")) {
|
|
11
|
+
candidate = process.cwd();
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if (isWindowsStylePath(candidate)) {
|
|
15
|
+
const resolved = win32.normalize(candidate);
|
|
16
|
+
const { root } = win32.parse(resolved);
|
|
17
|
+
let normalized = resolved;
|
|
18
|
+
while (normalized.length > root.length && /[\\/]+$/.test(normalized)) {
|
|
19
|
+
normalized = normalized.slice(0, -1);
|
|
20
|
+
}
|
|
21
|
+
return normalized;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const resolved = resolve(candidate);
|
|
25
|
+
const { root } = parse(resolved);
|
|
26
|
+
|
|
27
|
+
let normalized = resolved;
|
|
28
|
+
while (normalized.length > root.length && /[\\/]+$/.test(normalized)) {
|
|
29
|
+
normalized = normalized.slice(0, -1);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return normalized;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function resolveWorkspaceRoot(): string {
|
|
36
|
+
const candidates = [
|
|
37
|
+
process.env.CODEX_WORKSPACE_ROOT,
|
|
38
|
+
process.cwd(),
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
for (const candidate of candidates) {
|
|
42
|
+
const value = candidate?.trim();
|
|
43
|
+
if (value) return normalizeWorkspaceRoot(value);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return normalizeWorkspaceRoot(process.cwd());
|
|
47
|
+
}
|
package/src/exec.ts
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import "./legacyEnvBootstrap.js";
|
|
2
|
+
import { parseHeadlessExecArgs } from "./headless/execArgs.js";
|
|
3
|
+
import {
|
|
4
|
+
createHeadlessExecTiming,
|
|
5
|
+
HEADLESS_EXEC_PARSE_ERROR,
|
|
6
|
+
runHeadlessExec,
|
|
7
|
+
} from "./headless/execRunner.js";
|
|
8
|
+
|
|
9
|
+
export function printExecHelp(): void {
|
|
10
|
+
console.log(`Usage:
|
|
11
|
+
ubume exec "prompt"
|
|
12
|
+
ubume exec --prompt "prompt"
|
|
13
|
+
ubume exec [--profile <name>] [-c key=value] "prompt"
|
|
14
|
+
ubume exec --model gpt-5.4-mini --reasoning medium "Reply with exactly: UBUME_READY"
|
|
15
|
+
|
|
16
|
+
Options:
|
|
17
|
+
--prompt <text> Prompt to submit in headless mode.
|
|
18
|
+
--reasoning <effort>
|
|
19
|
+
Reasoning effort: none, minimal, low, medium, high, xhigh.
|
|
20
|
+
--benchmark-diagnostics
|
|
21
|
+
Alias for --timing.
|
|
22
|
+
--timing Print optional ubume exec phase timing to stderr.
|
|
23
|
+
--ubume-prompt-policy <raw|wrapped>
|
|
24
|
+
Prompt policy for exec mode. Defaults to raw.
|
|
25
|
+
--skip-git-repo-check
|
|
26
|
+
Forward --skip-git-repo-check to codex exec.
|
|
27
|
+
--profile <name> Load a Codex profile from config.
|
|
28
|
+
-m, --model <name> Select the Codex model for this launch.
|
|
29
|
+
-c, --config <key=val> Override a runtime config value.
|
|
30
|
+
-h, --help Show this help text and exit.
|
|
31
|
+
`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const isMainModule = Boolean((import.meta as ImportMeta & { main?: boolean }).main);
|
|
35
|
+
|
|
36
|
+
if (isMainModule) {
|
|
37
|
+
const argv = process.argv.slice(2);
|
|
38
|
+
const timing = createHeadlessExecTiming({
|
|
39
|
+
enabled: process.env.UBUME_EXEC_TIMING === "1"
|
|
40
|
+
|| argv.includes("--timing")
|
|
41
|
+
|| argv.includes("--benchmark-diagnostics"),
|
|
42
|
+
stderr: process.stderr,
|
|
43
|
+
startTimeMs: Number(process.env.UBUME_EXEC_TIMING_EPOCH_MS) || undefined,
|
|
44
|
+
});
|
|
45
|
+
timing.mark("ubume_exec_process_start", { pid: process.pid });
|
|
46
|
+
|
|
47
|
+
const parsed = parseHeadlessExecArgs(argv);
|
|
48
|
+
if (!parsed.ok) {
|
|
49
|
+
console.error(`[ubume exec] parse: ${parsed.error}`);
|
|
50
|
+
timing.mark("ubume_exec_process_exit", { exit_code: HEADLESS_EXEC_PARSE_ERROR });
|
|
51
|
+
process.exit(HEADLESS_EXEC_PARSE_ERROR);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
timing.mark("args_parsed", {
|
|
55
|
+
prompt_character_count: parsed.value.prompt.length,
|
|
56
|
+
prompt_policy: parsed.value.promptPolicy,
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
if (parsed.value.help) {
|
|
60
|
+
printExecHelp();
|
|
61
|
+
timing.mark("ubume_exec_process_exit", { exit_code: 0 });
|
|
62
|
+
process.exit(0);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const result = await runHeadlessExec({
|
|
66
|
+
prompt: parsed.value.prompt,
|
|
67
|
+
launchArgs: parsed.value.launchArgs,
|
|
68
|
+
promptPolicy: parsed.value.promptPolicy,
|
|
69
|
+
benchmarkDiagnostics: (parsed.value.timing || process.env.UBUME_EXEC_TIMING === "1") ? timing : undefined,
|
|
70
|
+
});
|
|
71
|
+
timing.mark("ubume_exec_process_exit", { exit_code: result.exitCode });
|
|
72
|
+
process.exit(result.exitCode);
|
|
73
|
+
}
|