@mcoda/codali 0.1.134 → 0.1.135-codali.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.
- package/CHANGELOG.md +42 -0
- package/ORCHESTRATION.md +387 -0
- package/README.md +103 -1
- package/config/alternate-local.example.json +49 -0
- package/config/docdex-runtime.json +21 -0
- package/config/orchestration.schema.json +548 -0
- package/config/raijin-suku.example.json +172 -0
- package/config/terminal-long-scenarios.v1.json +70 -0
- package/dist/agents/RoleResolution.d.ts.map +1 -1
- package/dist/agents/RoleResolution.js +4 -4
- package/dist/api/CodaliApi.d.ts +16 -0
- package/dist/api/CodaliApi.d.ts.map +1 -1
- package/dist/api/CodaliApi.js +39 -10
- package/dist/cli/AskCommand.d.ts +3 -0
- package/dist/cli/AskCommand.d.ts.map +1 -1
- package/dist/cli/AskCommand.js +76 -13
- package/dist/cli/RunCommand.d.ts +4 -0
- package/dist/cli/RunCommand.d.ts.map +1 -1
- package/dist/cli/RunCommand.js +110 -24
- package/dist/cli/TerminalCommand.d.ts +35 -0
- package/dist/cli/TerminalCommand.d.ts.map +1 -0
- package/dist/cli/TerminalCommand.js +616 -0
- package/dist/cli/TerminalCredentials.d.ts +3 -0
- package/dist/cli/TerminalCredentials.d.ts.map +1 -0
- package/dist/cli/TerminalCredentials.js +89 -0
- package/dist/cli/TerminalInput.d.ts +24 -0
- package/dist/cli/TerminalInput.d.ts.map +1 -0
- package/dist/cli/TerminalInput.js +98 -0
- package/dist/cli/TerminalText.d.ts +10 -0
- package/dist/cli/TerminalText.d.ts.map +1 -0
- package/dist/cli/TerminalText.js +53 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +33 -4
- package/dist/cognitive/ConfiguredPipeline.d.ts +34 -0
- package/dist/cognitive/ConfiguredPipeline.d.ts.map +1 -0
- package/dist/cognitive/ConfiguredPipeline.js +50 -0
- package/dist/cognitive/ValidationRunner.d.ts +4 -0
- package/dist/cognitive/ValidationRunner.d.ts.map +1 -1
- package/dist/cognitive/ValidationRunner.js +18 -41
- package/dist/config/NodeOrchestration.d.ts +21 -0
- package/dist/config/NodeOrchestration.d.ts.map +1 -0
- package/dist/config/NodeOrchestration.js +54 -0
- package/dist/config/OrchestrationConfig.d.ts +100 -0
- package/dist/config/OrchestrationConfig.d.ts.map +1 -0
- package/dist/config/OrchestrationConfig.js +245 -0
- package/dist/connectors/mcp/McpClient.d.ts +3 -1
- package/dist/connectors/mcp/McpClient.d.ts.map +1 -1
- package/dist/connectors/mcp/McpClient.js +3 -2
- package/dist/connectors/mcp/McpServerRegistry.d.ts +2 -2
- package/dist/connectors/mcp/McpServerRegistry.d.ts.map +1 -1
- package/dist/connectors/mcp/McpServerRegistry.js +39 -16
- package/dist/connectors/mcp/McpToolAdapter.js +2 -2
- package/dist/connectors/media/ConfiguredMediaTools.d.ts +6 -0
- package/dist/connectors/media/ConfiguredMediaTools.d.ts.map +1 -0
- package/dist/connectors/media/ConfiguredMediaTools.js +41 -0
- package/dist/connectors/media/ImageGenerationTool.js +3 -3
- package/dist/connectors/media/MediaArtifactReceipt.d.ts +5 -0
- package/dist/connectors/media/MediaArtifactReceipt.d.ts.map +1 -0
- package/dist/connectors/media/MediaArtifactReceipt.js +65 -0
- package/dist/connectors/media/MediaJobStore.d.ts +68 -0
- package/dist/connectors/media/MediaJobStore.d.ts.map +1 -0
- package/dist/connectors/media/MediaJobStore.js +199 -0
- package/dist/connectors/media/MediaJobWorker.d.ts +2 -0
- package/dist/connectors/media/MediaJobWorker.d.ts.map +1 -0
- package/dist/connectors/media/MediaJobWorker.js +65 -0
- package/dist/connectors/media/RemoteMediaJob.d.ts +6 -0
- package/dist/connectors/media/RemoteMediaJob.d.ts.map +1 -0
- package/dist/connectors/media/RemoteMediaJob.js +80 -0
- package/dist/docdex/DocdexClient.d.ts +18 -1
- package/dist/docdex/DocdexClient.d.ts.map +1 -1
- package/dist/docdex/DocdexClient.js +135 -19
- package/dist/docdex/ManagedDocdex.d.ts +29 -0
- package/dist/docdex/ManagedDocdex.d.ts.map +1 -0
- package/dist/docdex/ManagedDocdex.js +215 -0
- package/dist/docdex/ManagedRuntime.d.ts +28 -0
- package/dist/docdex/ManagedRuntime.d.ts.map +1 -0
- package/dist/docdex/ManagedRuntime.js +212 -0
- package/dist/eval/EvaluationProcess.d.ts +13 -0
- package/dist/eval/EvaluationProcess.d.ts.map +1 -0
- package/dist/eval/EvaluationProcess.js +76 -0
- package/dist/eval/LongWorkflowJourney.d.ts +34 -0
- package/dist/eval/LongWorkflowJourney.d.ts.map +1 -0
- package/dist/eval/LongWorkflowJourney.js +243 -0
- package/dist/eval/PlanningCorpus.d.ts +45 -0
- package/dist/eval/PlanningCorpus.d.ts.map +1 -0
- package/dist/eval/PlanningCorpus.js +189 -0
- package/dist/eval/PlanningCorpusRunner.d.ts +13 -0
- package/dist/eval/PlanningCorpusRunner.d.ts.map +1 -0
- package/dist/eval/PlanningCorpusRunner.js +89 -0
- package/dist/eval/TerminalCorpusRunner.d.ts +36 -0
- package/dist/eval/TerminalCorpusRunner.d.ts.map +1 -0
- package/dist/eval/TerminalCorpusRunner.js +222 -0
- package/dist/eval/TerminalTaskCorpus.d.ts +26 -0
- package/dist/eval/TerminalTaskCorpus.d.ts.map +1 -0
- package/dist/eval/TerminalTaskCorpus.js +105 -0
- package/dist/eval/TerminalWorkflowCorpus.d.ts +11 -0
- package/dist/eval/TerminalWorkflowCorpus.d.ts.map +1 -0
- package/dist/eval/TerminalWorkflowCorpus.js +116 -0
- package/dist/eval/fixtures/TerminalRecoveryJourney.d.ts +4 -0
- package/dist/eval/fixtures/TerminalRecoveryJourney.d.ts.map +1 -0
- package/dist/eval/fixtures/TerminalRecoveryJourney.js +132 -0
- package/dist/gateway/CodaliGateway.d.ts +2 -0
- package/dist/gateway/CodaliGateway.d.ts.map +1 -1
- package/dist/gateway/CodaliGateway.js +11 -1
- package/dist/gateway/ConfiguredGateway.d.ts +23 -0
- package/dist/gateway/ConfiguredGateway.d.ts.map +1 -0
- package/dist/gateway/ConfiguredGateway.js +44 -0
- package/dist/gateway/GatewayTracer.d.ts +1 -1
- package/dist/gateway/GatewayTracer.d.ts.map +1 -1
- package/dist/gateway/GatewayTracer.js +2 -27
- package/dist/index.d.ts +23 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -0
- package/dist/permissions/PermissionGate.d.ts +44 -0
- package/dist/permissions/PermissionGate.d.ts.map +1 -0
- package/dist/permissions/PermissionGate.js +97 -0
- package/dist/providers/ConfiguredEmbeddings.d.ts +43 -0
- package/dist/providers/ConfiguredEmbeddings.d.ts.map +1 -0
- package/dist/providers/ConfiguredEmbeddings.js +87 -0
- package/dist/providers/ConfiguredModelScope.d.ts +69 -0
- package/dist/providers/ConfiguredModelScope.d.ts.map +1 -0
- package/dist/providers/ConfiguredModelScope.js +113 -0
- package/dist/providers/ConfiguredProvider.d.ts +79 -0
- package/dist/providers/ConfiguredProvider.d.ts.map +1 -0
- package/dist/providers/ConfiguredProvider.js +238 -0
- package/dist/providers/ModelServiceError.d.ts +10 -0
- package/dist/providers/ModelServiceError.d.ts.map +1 -0
- package/dist/providers/ModelServiceError.js +45 -0
- package/dist/providers/MswarmWorkerProvider.d.ts.map +1 -1
- package/dist/providers/MswarmWorkerProvider.js +4 -0
- package/dist/providers/OpenAiCompatibleProvider.d.ts.map +1 -1
- package/dist/providers/OpenAiCompatibleProvider.js +91 -50
- package/dist/providers/ProviderTypes.d.ts +12 -0
- package/dist/providers/ProviderTypes.d.ts.map +1 -1
- package/dist/runtime/CodaliJobRuntime.d.ts +6 -0
- package/dist/runtime/CodaliJobRuntime.d.ts.map +1 -1
- package/dist/runtime/CodaliJobRuntime.js +36 -1
- package/dist/runtime/CodaliRuntime.d.ts +28 -0
- package/dist/runtime/CodaliRuntime.d.ts.map +1 -1
- package/dist/runtime/CodaliRuntime.js +69 -1
- package/dist/runtime/CompletionReport.d.ts +5 -0
- package/dist/runtime/CompletionReport.d.ts.map +1 -0
- package/dist/runtime/CompletionReport.js +12 -0
- package/dist/runtime/ProgressTracker.d.ts +17 -0
- package/dist/runtime/ProgressTracker.d.ts.map +1 -0
- package/dist/runtime/ProgressTracker.js +27 -0
- package/dist/runtime/RequestedTestEvidence.d.ts +12 -0
- package/dist/runtime/RequestedTestEvidence.d.ts.map +1 -0
- package/dist/runtime/RequestedTestEvidence.js +16 -0
- package/dist/runtime/RequiredRetrieval.d.ts +13 -0
- package/dist/runtime/RequiredRetrieval.d.ts.map +1 -0
- package/dist/runtime/RequiredRetrieval.js +25 -0
- package/dist/runtime/ReviewSource.d.ts +6 -0
- package/dist/runtime/ReviewSource.d.ts.map +1 -0
- package/dist/runtime/ReviewSource.js +38 -0
- package/dist/runtime/Runner.d.ts +7 -0
- package/dist/runtime/Runner.d.ts.map +1 -1
- package/dist/runtime/Runner.js +215 -171
- package/dist/runtime/SecretRedaction.d.ts +11 -0
- package/dist/runtime/SecretRedaction.d.ts.map +1 -0
- package/dist/runtime/SecretRedaction.js +85 -0
- package/dist/runtime/SessionController.d.ts +148 -0
- package/dist/runtime/SessionController.d.ts.map +1 -0
- package/dist/runtime/SessionController.js +1357 -0
- package/dist/runtime/StructuralEvidence.d.ts +22 -0
- package/dist/runtime/StructuralEvidence.d.ts.map +1 -0
- package/dist/runtime/StructuralEvidence.js +87 -0
- package/dist/runtime/TaskReviewContext.d.ts +34 -0
- package/dist/runtime/TaskReviewContext.d.ts.map +1 -0
- package/dist/runtime/TaskReviewContext.js +34 -0
- package/dist/runtime/ValidationOutput.d.ts +5 -0
- package/dist/runtime/ValidationOutput.d.ts.map +1 -0
- package/dist/runtime/ValidationOutput.js +32 -0
- package/dist/runtime/VerificationCommand.d.ts +5 -0
- package/dist/runtime/VerificationCommand.d.ts.map +1 -0
- package/dist/runtime/VerificationCommand.js +76 -0
- package/dist/runtime/WorkStatusPage.d.ts +15 -0
- package/dist/runtime/WorkStatusPage.d.ts.map +1 -0
- package/dist/runtime/WorkStatusPage.js +58 -0
- package/dist/runtime/WorkspaceLedger.d.ts +13 -0
- package/dist/runtime/WorkspaceLedger.d.ts.map +1 -0
- package/dist/runtime/WorkspaceLedger.js +145 -0
- package/dist/runtime/WorkspaceLock.d.ts.map +1 -1
- package/dist/runtime/WorkspaceLock.js +20 -17
- package/dist/runtime/WorkspaceSnapshot.d.ts +13 -0
- package/dist/runtime/WorkspaceSnapshot.d.ts.map +1 -0
- package/dist/runtime/WorkspaceSnapshot.js +88 -0
- package/dist/session/AtomicState.d.ts +13 -0
- package/dist/session/AtomicState.d.ts.map +1 -0
- package/dist/session/AtomicState.js +123 -0
- package/dist/session/SessionStoragePath.d.ts +15 -0
- package/dist/session/SessionStoragePath.d.ts.map +1 -0
- package/dist/session/SessionStoragePath.js +89 -0
- package/dist/session/SessionStore.d.ts +9 -0
- package/dist/session/SessionStore.d.ts.map +1 -1
- package/dist/session/SessionStore.js +181 -47
- package/dist/session/WorkState.d.ts +23 -0
- package/dist/session/WorkState.d.ts.map +1 -0
- package/dist/session/WorkState.js +367 -0
- package/dist/session/WorkTypes.d.ts +94 -0
- package/dist/session/WorkTypes.d.ts.map +1 -0
- package/dist/session/WorkTypes.js +1 -0
- package/dist/tools/ToolRegistry.d.ts +2 -0
- package/dist/tools/ToolRegistry.d.ts.map +1 -1
- package/dist/tools/ToolRegistry.js +12 -2
- package/dist/tools/ToolTypes.d.ts +16 -0
- package/dist/tools/ToolTypes.d.ts.map +1 -1
- package/dist/tools/docdex/DocdexTools.d.ts +2 -0
- package/dist/tools/docdex/DocdexTools.d.ts.map +1 -1
- package/dist/tools/docdex/DocdexTools.js +17 -2
- package/dist/tools/filesystem/FileTools.d.ts.map +1 -1
- package/dist/tools/filesystem/FileTools.js +30 -10
- package/dist/tools/shell/AsyncProcess.d.ts +19 -0
- package/dist/tools/shell/AsyncProcess.d.ts.map +1 -0
- package/dist/tools/shell/AsyncProcess.js +94 -0
- package/dist/tools/shell/DarwinRuntimeLibraries.d.ts +13 -0
- package/dist/tools/shell/DarwinRuntimeLibraries.d.ts.map +1 -0
- package/dist/tools/shell/DarwinRuntimeLibraries.js +130 -0
- package/dist/tools/shell/HostToolchain.d.ts +19 -0
- package/dist/tools/shell/HostToolchain.d.ts.map +1 -0
- package/dist/tools/shell/HostToolchain.js +266 -0
- package/dist/tools/shell/IsolatedExecutor.d.ts +15 -0
- package/dist/tools/shell/IsolatedExecutor.d.ts.map +1 -0
- package/dist/tools/shell/IsolatedExecutor.js +138 -0
- package/dist/tools/shell/ShellTool.d.ts.map +1 -1
- package/dist/tools/shell/ShellTool.js +9 -29
- package/dist/tools/shell/ShellWorkingDirectory.d.ts +3 -0
- package/dist/tools/shell/ShellWorkingDirectory.d.ts.map +1 -0
- package/dist/tools/shell/ShellWorkingDirectory.js +30 -0
- package/package.json +27 -24
|
@@ -1,18 +1,36 @@
|
|
|
1
|
-
import { promises as fs } from "node:fs";
|
|
1
|
+
import { promises as fs, realpathSync } from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
const resolveWorkspacePath = (context, targetPath) => {
|
|
3
|
+
const resolveWorkspacePath = async (context, targetPath) => {
|
|
4
4
|
const resolved = path.resolve(context.workspaceRoot, targetPath);
|
|
5
5
|
if (context.allowOutsideWorkspace) {
|
|
6
6
|
return resolved;
|
|
7
7
|
}
|
|
8
8
|
const relative = path.relative(context.workspaceRoot, resolved);
|
|
9
|
-
if (relative.startsWith(
|
|
9
|
+
if (relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {
|
|
10
10
|
throw new Error("Path is outside the workspace root");
|
|
11
11
|
}
|
|
12
|
-
|
|
12
|
+
const root = await fs.realpath(context.workspaceRoot);
|
|
13
|
+
let existing = resolved;
|
|
14
|
+
const suffix = [];
|
|
15
|
+
while (true) {
|
|
16
|
+
try {
|
|
17
|
+
const real = await fs.realpath(existing);
|
|
18
|
+
const actual = path.join(real, ...suffix.reverse());
|
|
19
|
+
const rel = path.relative(root, actual);
|
|
20
|
+
if (rel === ".." || rel.startsWith(`..${path.sep}`) || path.isAbsolute(rel))
|
|
21
|
+
throw new Error("Symlink path is outside the workspace root");
|
|
22
|
+
return actual;
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
if (error.code !== "ENOENT" || existing === path.dirname(existing))
|
|
26
|
+
throw error;
|
|
27
|
+
suffix.push(path.basename(existing));
|
|
28
|
+
existing = path.dirname(existing);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
13
31
|
};
|
|
14
32
|
const toRelative = (context, targetPath) => {
|
|
15
|
-
return path.relative(context.workspaceRoot, targetPath) || ".";
|
|
33
|
+
return path.relative(realpathSync.native(context.workspaceRoot), targetPath) || ".";
|
|
16
34
|
};
|
|
17
35
|
const normalizeScope = (value) => {
|
|
18
36
|
const normalized = path.posix.normalize(value.replace(/\\/g, "/")).replace(/^\/+/, "");
|
|
@@ -66,10 +84,11 @@ export const createFileTools = () => {
|
|
|
66
84
|
},
|
|
67
85
|
handler: async (args, context) => {
|
|
68
86
|
const { path: target } = args;
|
|
69
|
-
const resolved = resolveWorkspacePath(context, target);
|
|
87
|
+
const resolved = await resolveWorkspacePath(context, target);
|
|
70
88
|
assertPathScope(context, resolved, context.allowedReadPaths, "read");
|
|
89
|
+
await context.checkPath?.(resolved, "read");
|
|
71
90
|
const content = await fs.readFile(resolved, "utf8");
|
|
72
|
-
return { output: content };
|
|
91
|
+
return { output: content, data: { path: toRelative(context, resolved) } };
|
|
73
92
|
},
|
|
74
93
|
},
|
|
75
94
|
{
|
|
@@ -87,8 +106,9 @@ export const createFileTools = () => {
|
|
|
87
106
|
},
|
|
88
107
|
handler: async (args, context) => {
|
|
89
108
|
const { path: target, content } = args;
|
|
90
|
-
const resolved = resolveWorkspacePath(context, target);
|
|
109
|
+
const resolved = await resolveWorkspacePath(context, target);
|
|
91
110
|
assertPathScope(context, resolved, context.allowedWritePaths, "write");
|
|
111
|
+
await context.checkPath?.(resolved, "write");
|
|
92
112
|
await fs.mkdir(path.dirname(resolved), { recursive: true });
|
|
93
113
|
await fs.writeFile(resolved, content, "utf8");
|
|
94
114
|
if (context.recordTouchedFile) {
|
|
@@ -111,7 +131,7 @@ export const createFileTools = () => {
|
|
|
111
131
|
},
|
|
112
132
|
handler: async (args, context) => {
|
|
113
133
|
const { path: target = ".", maxDepth = 2 } = args ?? {};
|
|
114
|
-
const resolved = resolveWorkspacePath(context, target);
|
|
134
|
+
const resolved = await resolveWorkspacePath(context, target);
|
|
115
135
|
assertPathScope(context, resolved, context.allowedReadPaths, "read");
|
|
116
136
|
const entries = await listFilesRecursive(resolved, maxDepth);
|
|
117
137
|
const relativeEntries = entries.map((entry) => toRelative(context, entry));
|
|
@@ -132,7 +152,7 @@ export const createFileTools = () => {
|
|
|
132
152
|
},
|
|
133
153
|
handler: async (args, context) => {
|
|
134
154
|
const { path: target } = args;
|
|
135
|
-
const resolved = resolveWorkspacePath(context, target);
|
|
155
|
+
const resolved = await resolveWorkspacePath(context, target);
|
|
136
156
|
assertPathScope(context, resolved, context.allowedReadPaths, "read");
|
|
137
157
|
const stats = await fs.stat(resolved);
|
|
138
158
|
const info = {
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type ToolContext, type ToolHandlerResult } from "../ToolTypes.js";
|
|
2
|
+
export declare const safeProcessEnvironment: () => NodeJS.ProcessEnv;
|
|
3
|
+
/** Host executor settings, never populated from model-supplied tool arguments. */
|
|
4
|
+
export interface ProcessEnvironment {
|
|
5
|
+
HOME?: string;
|
|
6
|
+
TMPDIR?: string;
|
|
7
|
+
PATH?: string;
|
|
8
|
+
CARGO_HOME?: string;
|
|
9
|
+
XDG_CACHE_HOME?: string;
|
|
10
|
+
PYTHONPYCACHEPREFIX?: string;
|
|
11
|
+
PYTHONUSERBASE?: string;
|
|
12
|
+
SDKROOT?: string;
|
|
13
|
+
DEVELOPER_DIR?: string;
|
|
14
|
+
GIT_CONFIG_NOSYSTEM?: string;
|
|
15
|
+
GIT_CONFIG_GLOBAL?: string;
|
|
16
|
+
GIT_ATTR_NOSYSTEM?: string;
|
|
17
|
+
}
|
|
18
|
+
export declare const runProcess: (command: string, args: string[], context: ToolContext, environment?: ProcessEnvironment) => Promise<ToolHandlerResult>;
|
|
19
|
+
//# sourceMappingURL=AsyncProcess.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AsyncProcess.d.ts","sourceRoot":"","sources":["../../../src/tools/shell/AsyncProcess.ts"],"names":[],"mappings":"AAEA,OAAO,EAAsB,KAAK,WAAW,EAAE,KAAK,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAG/F,eAAO,MAAM,sBAAsB,QAAO,MAAM,CAAC,UAGhD,CAAC;AAEF,kFAAkF;AAClF,MAAM,WAAW,kBAAkB;IACjC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAC9C,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IAAC,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACpG,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IACzC,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;CACtF;AAED,eAAO,MAAM,UAAU,GAAI,SAAS,MAAM,EAAE,MAAM,MAAM,EAAE,EAAE,SAAS,WAAW,EAAE,cAAa,kBAAuB,KAAG,OAAO,CAAC,iBAAiB,CAgDjJ,CAAC"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { StringDecoder } from "node:string_decoder";
|
|
3
|
+
import { ToolExecutionError } from "../ToolTypes.js";
|
|
4
|
+
import { resolveShellWorkingDirectory } from "./ShellWorkingDirectory.js";
|
|
5
|
+
export const safeProcessEnvironment = () => Object.fromEntries(["PATH", "LANG", "LC_ALL", "LC_CTYPE", "TERM", "TZ", "TMPDIR", "SYSTEMROOT", "WINDIR", "COMSPEC", "PATHEXT"]
|
|
6
|
+
.filter(key => process.env[key] !== undefined).map(key => [key, process.env[key]]));
|
|
7
|
+
export const runProcess = (command, args, context, environment = {}) => {
|
|
8
|
+
context.signal?.throwIfAborted();
|
|
9
|
+
const timeoutMs = context.shellTimeoutMs ?? 120000;
|
|
10
|
+
const maxBytes = context.shellMaxOutputBytes ?? 1048576;
|
|
11
|
+
if (!Number.isSafeInteger(timeoutMs) || timeoutMs <= 0 || !Number.isSafeInteger(maxBytes) || maxBytes <= 0)
|
|
12
|
+
throw new Error("Invalid process limits");
|
|
13
|
+
const cwd = resolveShellWorkingDirectory(context.workspaceRoot, context.shellWorkingDirectory);
|
|
14
|
+
return new Promise((resolve, reject) => {
|
|
15
|
+
const group = process.platform !== "win32";
|
|
16
|
+
const env = safeProcessEnvironment();
|
|
17
|
+
for (const key of ["HOME", "TMPDIR", "PATH", "CARGO_HOME", "XDG_CACHE_HOME", "PYTHONPYCACHEPREFIX", "PYTHONUSERBASE", "SDKROOT", "DEVELOPER_DIR", "GIT_CONFIG_NOSYSTEM", "GIT_CONFIG_GLOBAL", "GIT_ATTR_NOSYSTEM"])
|
|
18
|
+
if (typeof environment[key] === "string")
|
|
19
|
+
env[key] = environment[key];
|
|
20
|
+
const child = spawn(command, args, { cwd, env, detached: group, stdio: ["ignore", "pipe", "pipe"], windowsHide: true });
|
|
21
|
+
let stdout = "";
|
|
22
|
+
let stderr = "";
|
|
23
|
+
let captured = 0;
|
|
24
|
+
let truncated = false;
|
|
25
|
+
let timedOut = false;
|
|
26
|
+
const decoders = { stdout: new StringDecoder("utf8"), stderr: new StringDecoder("utf8") };
|
|
27
|
+
let escalation;
|
|
28
|
+
const kill = (signal) => {
|
|
29
|
+
if (!child.pid)
|
|
30
|
+
return;
|
|
31
|
+
try {
|
|
32
|
+
if (group)
|
|
33
|
+
process.kill(-child.pid, signal);
|
|
34
|
+
else
|
|
35
|
+
child.kill(signal);
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
if (error.code !== "ESRCH")
|
|
39
|
+
child.kill(signal);
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
const stop = () => { kill("SIGTERM"); escalation ?? (escalation = setTimeout(() => kill("SIGKILL"), 1000)); escalation.unref(); };
|
|
43
|
+
const timeout = setTimeout(() => { timedOut = true; stop(); }, timeoutMs);
|
|
44
|
+
const append = (stream, text) => {
|
|
45
|
+
if (stream === "stdout")
|
|
46
|
+
stdout += text;
|
|
47
|
+
else
|
|
48
|
+
stderr += text;
|
|
49
|
+
if (text) {
|
|
50
|
+
try {
|
|
51
|
+
context.onOutput?.(stream, text);
|
|
52
|
+
}
|
|
53
|
+
catch { /* Rendering cannot abandon process cleanup. */ }
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
const collect = (stream, chunk) => {
|
|
57
|
+
const remaining = Math.max(0, maxBytes - captured);
|
|
58
|
+
if (chunk.length > remaining)
|
|
59
|
+
truncated = true;
|
|
60
|
+
const text = decoders[stream].write(chunk.subarray(0, remaining));
|
|
61
|
+
captured += Math.min(chunk.length, remaining);
|
|
62
|
+
append(stream, text);
|
|
63
|
+
};
|
|
64
|
+
child.stdout.on("data", (chunk) => collect("stdout", chunk));
|
|
65
|
+
child.stderr.on("data", (chunk) => collect("stderr", chunk));
|
|
66
|
+
context.signal?.addEventListener("abort", stop, { once: true });
|
|
67
|
+
if (context.signal?.aborted)
|
|
68
|
+
stop();
|
|
69
|
+
const cleanup = () => { clearTimeout(timeout); if (escalation)
|
|
70
|
+
clearTimeout(escalation); context.signal?.removeEventListener("abort", stop); };
|
|
71
|
+
child.once("error", error => { cleanup(); reject(error); });
|
|
72
|
+
child.once("close", (exitCode, signal) => {
|
|
73
|
+
cleanup();
|
|
74
|
+
append("stdout", decoders.stdout.end());
|
|
75
|
+
append("stderr", decoders.stderr.end());
|
|
76
|
+
if (group)
|
|
77
|
+
kill("SIGKILL");
|
|
78
|
+
const details = { command, args, cwd, exitCode, signal, stdout, stderr, truncated, timedOut };
|
|
79
|
+
if (context.signal?.aborted) {
|
|
80
|
+
reject(context.signal.reason ?? new Error("Process cancelled"));
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
if (timedOut) {
|
|
84
|
+
reject(new ToolExecutionError("tool_timeout", "Process deadline exceeded", { retryable: false, details }));
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
if (exitCode !== 0 || signal) {
|
|
88
|
+
reject(new ToolExecutionError("tool_execution_failed", stderr || stdout || `Process terminated (${signal ?? exitCode})`, { retryable: false, details }));
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
resolve({ output: (stdout || stderr) + (truncated ? "\n[output truncated]" : ""), data: details });
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { HostRuntimeMount } from "./HostToolchain.js";
|
|
2
|
+
interface LoadCommands {
|
|
3
|
+
libraries: string[];
|
|
4
|
+
rpaths: string[];
|
|
5
|
+
}
|
|
6
|
+
/** Inspect installed binaries, never execute a runtime or import repository code. */
|
|
7
|
+
export declare const inspectMachOLibraries: (files: string[]) => Map<string, LoadCommands>;
|
|
8
|
+
export declare const parseMachOLoadCommands: (output: string, files: string[]) => Map<string, LoadCommands>;
|
|
9
|
+
/** Admit only concrete native dependencies, including symlink spellings used by
|
|
10
|
+
* dyld. Never grant a package-manager prefix, home, or arbitrary library folder. */
|
|
11
|
+
export declare const discoverDarwinRuntimeLibraries: (executable: string, extensionFiles: string[], workspace: string, home: string, inspect?: (files: string[]) => Map<string, LoadCommands>) => HostRuntimeMount[];
|
|
12
|
+
export {};
|
|
13
|
+
//# sourceMappingURL=DarwinRuntimeLibraries.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DarwinRuntimeLibraries.d.ts","sourceRoot":"","sources":["../../../src/tools/shell/DarwinRuntimeLibraries.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAE3D,UAAU,YAAY;IAAG,SAAS,EAAE,MAAM,EAAE,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAC;CAAE;AAIjE,qFAAqF;AACrF,eAAO,MAAM,qBAAqB,GAAI,OAAO,MAAM,EAAE,KAAG,GAAG,CAAC,MAAM,EAAE,YAAY,CAM/E,CAAC;AAEF,eAAO,MAAM,sBAAsB,GAAI,QAAQ,MAAM,EAAE,OAAO,MAAM,EAAE,KAAG,GAAG,CAAC,MAAM,EAAE,YAAY,CAoBhG,CAAC;AAUF;oFACoF;AACpF,eAAO,MAAM,8BAA8B,GACzC,YAAY,MAAM,EAAE,gBAAgB,MAAM,EAAE,EAAE,WAAW,MAAM,EAAE,MAAM,MAAM,EAC7E,UAAS,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,GAAG,CAAC,MAAM,EAAE,YAAY,CAAyB,KAC9E,gBAAgB,EAiDlB,CAAC"}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { closeSync, openSync, readSync, realpathSync, statSync } from "node:fs";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { spawnSync } from "node:child_process";
|
|
5
|
+
const within = (file, root) => file === root || file.startsWith(`${root}${path.sep}`);
|
|
6
|
+
const systemLibrary = (file) => within(file, "/usr/lib") || within(file, "/System");
|
|
7
|
+
/** Inspect installed binaries, never execute a runtime or import repository code. */
|
|
8
|
+
export const inspectMachOLibraries = (files) => {
|
|
9
|
+
const result = spawnSync("/usr/bin/otool", ["-l", ...files], {
|
|
10
|
+
cwd: os.tmpdir(), env: { PATH: "/usr/bin:/bin" }, timeout: 3000, maxBuffer: 4 * 1024 * 1024, encoding: "utf8",
|
|
11
|
+
});
|
|
12
|
+
if (result.status !== 0)
|
|
13
|
+
throw new Error("Cannot inspect installed runtime libraries");
|
|
14
|
+
return parseMachOLoadCommands(result.stdout, files);
|
|
15
|
+
};
|
|
16
|
+
export const parseMachOLoadCommands = (output, files) => {
|
|
17
|
+
const allowed = new Set(files);
|
|
18
|
+
const result = new Map();
|
|
19
|
+
let current;
|
|
20
|
+
let command = "";
|
|
21
|
+
for (const line of output.split("\n")) {
|
|
22
|
+
if (!/^\s/.test(line) && line.endsWith(":")) {
|
|
23
|
+
const file = line.slice(0, -1).replace(/ \(architecture [^)]+\)$/, "");
|
|
24
|
+
current = allowed.has(file) ? result.get(file) ?? { libraries: [], rpaths: [] } : undefined;
|
|
25
|
+
if (current)
|
|
26
|
+
result.set(file, current);
|
|
27
|
+
command = "";
|
|
28
|
+
}
|
|
29
|
+
else if (/^Load command \d+$/.test(line))
|
|
30
|
+
command = "";
|
|
31
|
+
else if (current) {
|
|
32
|
+
const kind = line.match(/^\s+cmd (LC_[A-Z_]+)\s*$/);
|
|
33
|
+
if (kind)
|
|
34
|
+
command = kind[1];
|
|
35
|
+
const value = line.match(/^\s+(?:name|path) (.+) \(offset \d+\)\s*$/)?.[1];
|
|
36
|
+
if (value && command === "LC_RPATH")
|
|
37
|
+
current.rpaths.push(value);
|
|
38
|
+
else if (value && ["LC_LOAD_DYLIB", "LC_LOAD_WEAK_DYLIB", "LC_REEXPORT_DYLIB", "LC_LOAD_UPWARD_DYLIB"].includes(command))
|
|
39
|
+
current.libraries.push(value);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
if (files.some(file => !result.has(file)))
|
|
43
|
+
throw new Error("Incomplete runtime library inspection");
|
|
44
|
+
return result;
|
|
45
|
+
};
|
|
46
|
+
const nativeFile = (file) => {
|
|
47
|
+
if (!statSync(file).isFile())
|
|
48
|
+
return false;
|
|
49
|
+
const fd = openSync(file, "r");
|
|
50
|
+
const header = Buffer.alloc(4);
|
|
51
|
+
try {
|
|
52
|
+
if (readSync(fd, header, 0, 4, 0) !== 4)
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
finally {
|
|
56
|
+
closeSync(fd);
|
|
57
|
+
}
|
|
58
|
+
return ["feedface", "feedfacf", "cefaedfe", "cffaedfe", "cafebabe", "bebafeca", "cafebabf", "bfbafeca"].includes(header.toString("hex"));
|
|
59
|
+
};
|
|
60
|
+
/** Admit only concrete native dependencies, including symlink spellings used by
|
|
61
|
+
* dyld. Never grant a package-manager prefix, home, or arbitrary library folder. */
|
|
62
|
+
export const discoverDarwinRuntimeLibraries = (executable, extensionFiles, workspace, home, inspect = inspectMachOLibraries) => {
|
|
63
|
+
const mounts = new Map();
|
|
64
|
+
const visited = new Set();
|
|
65
|
+
const initial = [...new Set([executable, ...extensionFiles])];
|
|
66
|
+
const safe = (file) => {
|
|
67
|
+
if (!path.isAbsolute(file) || /[\r\n\0]/.test(file))
|
|
68
|
+
throw new Error("Invalid runtime library path");
|
|
69
|
+
const resolved = realpathSync(file);
|
|
70
|
+
if (within(file, workspace) || within(resolved, workspace) || resolved === home || !nativeFile(resolved)) {
|
|
71
|
+
throw new Error("Runtime library is not an installed native file");
|
|
72
|
+
}
|
|
73
|
+
return resolved;
|
|
74
|
+
};
|
|
75
|
+
let pending = initial.map(file => ({ file: safe(file), inherited: [] }));
|
|
76
|
+
let executableRpaths = [];
|
|
77
|
+
const expand = (value, loader) => {
|
|
78
|
+
if (value === "@loader_path")
|
|
79
|
+
return path.dirname(loader);
|
|
80
|
+
if (value === "@executable_path")
|
|
81
|
+
return path.dirname(executable);
|
|
82
|
+
if (value.startsWith("@loader_path/"))
|
|
83
|
+
return path.resolve(path.dirname(loader), value.slice(13));
|
|
84
|
+
if (value.startsWith("@executable_path/"))
|
|
85
|
+
return path.resolve(path.dirname(executable), value.slice(17));
|
|
86
|
+
return path.isAbsolute(value) ? path.normalize(value) : undefined;
|
|
87
|
+
};
|
|
88
|
+
for (let depth = 0; pending.length; depth++) {
|
|
89
|
+
if (depth >= 12 || visited.size + pending.length > 256)
|
|
90
|
+
throw new Error("Runtime library graph exceeds discovery limit");
|
|
91
|
+
const files = [...new Set(pending.map(item => item.file))];
|
|
92
|
+
const inspected = inspect(files);
|
|
93
|
+
const next = [];
|
|
94
|
+
for (const { file, inherited } of pending) {
|
|
95
|
+
if (visited.has(file))
|
|
96
|
+
continue;
|
|
97
|
+
visited.add(file);
|
|
98
|
+
const info = inspected.get(file);
|
|
99
|
+
if (!info)
|
|
100
|
+
throw new Error("Missing runtime library metadata");
|
|
101
|
+
const rpaths = info.rpaths.map(value => expand(value, file)).filter((value) => value !== undefined);
|
|
102
|
+
if (file === executable)
|
|
103
|
+
executableRpaths = rpaths;
|
|
104
|
+
const searchRoots = [...rpaths, ...inherited, ...executableRpaths];
|
|
105
|
+
for (const library of info.libraries) {
|
|
106
|
+
const candidates = library.startsWith("@rpath/") ? searchRoots.map(root => path.resolve(root, library.slice(7))) : [expand(library, file)];
|
|
107
|
+
for (const candidate of candidates) {
|
|
108
|
+
if (!candidate)
|
|
109
|
+
continue;
|
|
110
|
+
if (systemLibrary(candidate))
|
|
111
|
+
break; // May exist only in the system dyld cache.
|
|
112
|
+
let resolved;
|
|
113
|
+
try {
|
|
114
|
+
resolved = safe(candidate);
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
continue;
|
|
118
|
+
} // Missing optional libraries stay inaccessible.
|
|
119
|
+
for (const alias of [candidate, resolved])
|
|
120
|
+
mounts.set(alias, { readOnlyRoot: alias, directory: false });
|
|
121
|
+
if (!visited.has(resolved) && !next.some(item => item.file === resolved))
|
|
122
|
+
next.push({ file: resolved, inherited: searchRoots });
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
pending = next;
|
|
128
|
+
}
|
|
129
|
+
return [...mounts.values()];
|
|
130
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface HostRuntimeMount {
|
|
2
|
+
readOnlyRoot: string;
|
|
3
|
+
directory: boolean;
|
|
4
|
+
}
|
|
5
|
+
export interface HostRuntime extends HostRuntimeMount {
|
|
6
|
+
name: string;
|
|
7
|
+
executable: string;
|
|
8
|
+
support?: HostRuntimeMount[];
|
|
9
|
+
environment?: {
|
|
10
|
+
SDKROOT: string;
|
|
11
|
+
DEVELOPER_DIR: string;
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
/** Discover known user-installed language tools. The workspace can neither
|
|
15
|
+
* supply this PATH nor turn its own package.json into a trusted runtime mount.
|
|
16
|
+
* Mounts are single binaries or matching package roots, never the user's home. */
|
|
17
|
+
export declare const discoverHostToolchain: (workspaceRoot: string, searchPath?: string) => HostRuntime[];
|
|
18
|
+
export declare const extraRuntimeMounts: (runtimes: HostRuntime[]) => HostRuntimeMount[];
|
|
19
|
+
//# sourceMappingURL=HostToolchain.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"HostToolchain.d.ts","sourceRoot":"","sources":["../../../src/tools/shell/HostToolchain.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,gBAAgB;IAAG,YAAY,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,OAAO,CAAC;CAAE;AAC/E,MAAM,WAAW,WAAY,SAAQ,gBAAgB;IACnD,IAAI,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,gBAAgB,EAAE,CAAC;IAC/D,WAAW,CAAC,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,aAAa,EAAE,MAAM,CAAA;KAAE,CAAC;CAC1D;AAkID;;kFAEkF;AAClF,eAAO,MAAM,qBAAqB,GAAI,eAAe,MAAM,EAAE,mBAAmC,KAAG,WAAW,EA0D7G,CAAC;AAEF,eAAO,MAAM,kBAAkB,GAAI,UAAU,WAAW,EAAE,KAAG,gBAAgB,EAM5E,CAAC"}
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
import { accessSync, closeSync, constants, existsSync, openSync, readFileSync, readdirSync, readSync, realpathSync, statSync } from "node:fs";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { createHash } from "node:crypto";
|
|
5
|
+
import { spawnSync } from "node:child_process";
|
|
6
|
+
import { discoverDarwinRuntimeLibraries } from "./DarwinRuntimeLibraries.js";
|
|
7
|
+
const packages = { npm: ["npm"], pnpm: ["pnpm", "@pnpm/exe"], yarn: ["yarn", "@yarnpkg/cli", "@yarnpkg/cli-dist"] };
|
|
8
|
+
const within = (file, root) => file === root || file.startsWith(`${root}${path.sep}`);
|
|
9
|
+
const systemPath = (file) => ["/usr", "/bin", "/sbin", "/System"].some(root => within(file, root));
|
|
10
|
+
const executableBinary = (file) => {
|
|
11
|
+
const fd = openSync(file, "r");
|
|
12
|
+
const bytes = Buffer.alloc(4);
|
|
13
|
+
try {
|
|
14
|
+
if (readSync(fd, bytes, 0, 4, 0) !== 4)
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
finally {
|
|
18
|
+
closeSync(fd);
|
|
19
|
+
}
|
|
20
|
+
return ["7f454c46", "feedface", "feedfacf", "cefaedfe", "cffaedfe", "cafebabe", "bebafeca", "cafebabf", "bfbafeca"].includes(bytes.toString("hex"));
|
|
21
|
+
};
|
|
22
|
+
const rustTools = new Set(["cargo", "rustc", "rustdoc"]);
|
|
23
|
+
/** Resolve a rustup proxy without executing it or consulting repository overrides.
|
|
24
|
+
* Only an already-installed, host-selected toolchain can become a runtime mount. */
|
|
25
|
+
const resolveRustProxy = (name, executable, home) => {
|
|
26
|
+
if (!rustTools.has(name))
|
|
27
|
+
return executable;
|
|
28
|
+
let proxy = path.basename(executable) === "rustup";
|
|
29
|
+
if (!proxy && existsSync(path.join(path.dirname(executable), "rustup"))) {
|
|
30
|
+
const actual = statSync(executable);
|
|
31
|
+
const rustup = statSync(path.join(path.dirname(executable), "rustup"));
|
|
32
|
+
proxy = actual.dev === rustup.dev && actual.ino === rustup.ino;
|
|
33
|
+
// Some installers copy proxies instead of linking them. Compare all bytes;
|
|
34
|
+
// a filename or matching binary header alone does not identify rustup.
|
|
35
|
+
if (!proxy && actual.size === rustup.size && actual.size <= 64 * 1024 * 1024) {
|
|
36
|
+
const digest = (file) => createHash("sha256").update(readFileSync(file)).digest("hex");
|
|
37
|
+
proxy = digest(executable) === digest(path.join(path.dirname(executable), "rustup"));
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
if (!proxy && statSync(executable).size <= 16384 && !executableBinary(executable)) {
|
|
41
|
+
// Package managers may wrap rustup in a small shell script. Do not execute
|
|
42
|
+
// it: identify the adjacent native libexec proxies, then select host state.
|
|
43
|
+
const packageRoot = path.dirname(path.dirname(executable));
|
|
44
|
+
const nativeProxy = path.join(packageRoot, "libexec", "bin", name);
|
|
45
|
+
const nativeRustup = path.join(packageRoot, "libexec", "bin", "rustup");
|
|
46
|
+
if (existsSync(nativeProxy) && existsSync(nativeRustup)
|
|
47
|
+
&& within(realpathSync.native(nativeProxy), packageRoot) && within(realpathSync.native(nativeRustup), packageRoot)
|
|
48
|
+
&& executableBinary(nativeRustup)) {
|
|
49
|
+
const target = statSync(nativeProxy);
|
|
50
|
+
const manager = statSync(nativeRustup);
|
|
51
|
+
proxy = target.dev === manager.dev && target.ino === manager.ino;
|
|
52
|
+
if (!proxy && target.size === manager.size && target.size <= 64 * 1024 * 1024) {
|
|
53
|
+
const digest = (file) => createHash("sha256").update(readFileSync(file)).digest("hex");
|
|
54
|
+
proxy = digest(nativeProxy) === digest(nativeRustup);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (!proxy)
|
|
59
|
+
return executable;
|
|
60
|
+
const rustup = process.env.RUSTUP_HOME ?? path.join(home, ".rustup");
|
|
61
|
+
if (!path.isAbsolute(rustup))
|
|
62
|
+
throw new Error("Rustup home must be absolute");
|
|
63
|
+
const settings = path.join(rustup, "settings.toml");
|
|
64
|
+
const configured = process.env.RUSTUP_TOOLCHAIN || (statSync(settings).size <= 16384
|
|
65
|
+
? readFileSync(settings, "utf8").match(/^default_toolchain\s*=\s*"([A-Za-z0-9_.-]+)"\s*$/m)?.[1] : undefined);
|
|
66
|
+
if (!configured || !/^[A-Za-z0-9][A-Za-z0-9_.-]*$/.test(configured))
|
|
67
|
+
throw new Error("No installed host Rust toolchain selected");
|
|
68
|
+
const toolchains = path.join(rustup, "toolchains");
|
|
69
|
+
const matches = existsSync(path.join(toolchains, configured)) ? [configured]
|
|
70
|
+
: readdirSync(toolchains).filter(entry => entry.startsWith(`${configured}-`));
|
|
71
|
+
if (matches.length !== 1)
|
|
72
|
+
throw new Error("Rust toolchain selection is unavailable or ambiguous");
|
|
73
|
+
const root = realpathSync.native(path.join(toolchains, matches[0]));
|
|
74
|
+
const target = realpathSync.native(path.join(root, "bin", name));
|
|
75
|
+
if (!within(target, root))
|
|
76
|
+
throw new Error("Rust tool executable escapes selected toolchain");
|
|
77
|
+
return target;
|
|
78
|
+
};
|
|
79
|
+
const languageRuntime = (name, executable, workspace, home) => {
|
|
80
|
+
if (!executableBinary(executable) || path.basename(path.dirname(executable)) !== "bin")
|
|
81
|
+
return undefined;
|
|
82
|
+
const prefix = path.dirname(path.dirname(executable));
|
|
83
|
+
if (prefix === home || within(workspace, prefix) || within(prefix, workspace) || prefix === path.parse(prefix).root)
|
|
84
|
+
return undefined;
|
|
85
|
+
const runtime = { name, executable, readOnlyRoot: executable, directory: false, support: [] };
|
|
86
|
+
const add = (file) => {
|
|
87
|
+
const resolved = realpathSync.native(file);
|
|
88
|
+
if (!within(resolved, prefix))
|
|
89
|
+
throw new Error("Runtime support path escapes installation");
|
|
90
|
+
runtime.support.push({ readOnlyRoot: resolved, directory: statSync(resolved).isDirectory() });
|
|
91
|
+
};
|
|
92
|
+
if (rustTools.has(name)) {
|
|
93
|
+
const library = path.join(prefix, "lib");
|
|
94
|
+
if (!readdirSync(path.join(library, "rustlib")).some(file => /^manifest-rustc(?:-|$)/.test(file)))
|
|
95
|
+
return undefined;
|
|
96
|
+
add(library);
|
|
97
|
+
return runtime;
|
|
98
|
+
}
|
|
99
|
+
if (name === "python" || name === "python3") {
|
|
100
|
+
let standardLibrary = false;
|
|
101
|
+
const extensions = [];
|
|
102
|
+
for (const lib of ["lib", "lib64"]) {
|
|
103
|
+
const directory = path.join(prefix, lib);
|
|
104
|
+
if (!existsSync(directory))
|
|
105
|
+
continue;
|
|
106
|
+
for (const entry of readdirSync(directory)) {
|
|
107
|
+
const file = path.join(directory, entry);
|
|
108
|
+
if (/^python3\.\d+t?$/.test(entry) && existsSync(path.join(file, "os.py")) && existsSync(path.join(file, "encodings", "__init__.py"))) {
|
|
109
|
+
add(file);
|
|
110
|
+
standardLibrary = true;
|
|
111
|
+
const dynamic = path.join(file, "lib-dynload");
|
|
112
|
+
if (process.platform === "darwin" && existsSync(dynamic)) {
|
|
113
|
+
for (const entry of readdirSync(dynamic).filter(entry => /\.(?:so|dylib)$/.test(entry))) {
|
|
114
|
+
const extension = realpathSync.native(path.join(dynamic, entry));
|
|
115
|
+
if (!within(extension, prefix))
|
|
116
|
+
throw new Error("Python extension escapes installation");
|
|
117
|
+
extensions.push(extension);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
else if (/^libpython3\.\d+t?(?:\.so(?:\.[\d.]+)?|(?:\.\d+)*\.dylib)$/.test(entry))
|
|
122
|
+
add(file);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
if (standardLibrary && process.platform === "darwin")
|
|
126
|
+
runtime.support.push(...discoverDarwinRuntimeLibraries(executable, extensions, workspace, home));
|
|
127
|
+
return standardLibrary ? runtime : undefined;
|
|
128
|
+
}
|
|
129
|
+
return undefined;
|
|
130
|
+
};
|
|
131
|
+
/** xcrun only locates installed host tools here; it never compiles repository code.
|
|
132
|
+
* Mount the selected developer installation, not /Applications or a user's home. */
|
|
133
|
+
const darwinDeveloperTools = (workspace, home) => {
|
|
134
|
+
if (process.platform !== "darwin")
|
|
135
|
+
return [];
|
|
136
|
+
try {
|
|
137
|
+
const environment = { PATH: "/usr/bin:/bin", ...(process.env.DEVELOPER_DIR ? { DEVELOPER_DIR: process.env.DEVELOPER_DIR } : {}) };
|
|
138
|
+
const query = (command, args) => {
|
|
139
|
+
const result = spawnSync(command, args, { env: environment, cwd: os.tmpdir(), timeout: 3000, encoding: "utf8", maxBuffer: 16384 });
|
|
140
|
+
if (result.status !== 0)
|
|
141
|
+
throw new Error("Developer tool lookup failed");
|
|
142
|
+
return realpathSync.native(result.stdout.trim());
|
|
143
|
+
};
|
|
144
|
+
const developer = realpathSync.native(process.env.DEVELOPER_DIR ?? query("/usr/bin/xcode-select", ["--print-path"]));
|
|
145
|
+
if (developer === home || developer === path.parse(developer).root || within(workspace, developer) || within(developer, workspace))
|
|
146
|
+
return [];
|
|
147
|
+
const sdk = query("/usr/bin/xcrun", ["--sdk", "macosx", "--show-sdk-path"]);
|
|
148
|
+
if (!within(sdk, developer) || !existsSync(path.join(sdk, "SDKSettings.plist")))
|
|
149
|
+
return [];
|
|
150
|
+
// /usr/bin/git is an Apple launcher that may invoke xcodebuild and inspect
|
|
151
|
+
// files outside Developer. Select native Git within the same trusted root.
|
|
152
|
+
return ["cc", "ar", "git"].map(name => {
|
|
153
|
+
const executable = query("/usr/bin/xcrun", ["--find", name === "cc" ? "clang" : name]);
|
|
154
|
+
if (!within(executable, developer) || !executableBinary(executable))
|
|
155
|
+
throw new Error("Developer tool escapes selected installation");
|
|
156
|
+
return { name, executable, readOnlyRoot: developer, directory: true, environment: { SDKROOT: sdk, DEVELOPER_DIR: developer } };
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
return [];
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
/** Discover known user-installed language tools. The workspace can neither
|
|
164
|
+
* supply this PATH nor turn its own package.json into a trusted runtime mount.
|
|
165
|
+
* Mounts are single binaries or matching package roots, never the user's home. */
|
|
166
|
+
export const discoverHostToolchain = (workspaceRoot, searchPath = process.env.PATH ?? "") => {
|
|
167
|
+
const workspace = realpathSync.native(workspaceRoot);
|
|
168
|
+
const home = realpathSync.native(os.homedir());
|
|
169
|
+
const result = darwinDeveloperTools(workspace, home);
|
|
170
|
+
for (const name of ["node", "npm", "pnpm", "yarn", "bun", "python3", "python", "rustc", "cargo", "rustdoc", "cc", "ar"]) {
|
|
171
|
+
if (result.some(runtime => runtime.name === name))
|
|
172
|
+
continue;
|
|
173
|
+
const candidates = name === "node" ? [process.execPath] : searchPath.split(path.delimiter).filter(Boolean).map(directory => path.resolve(directory, name));
|
|
174
|
+
for (const candidate of candidates) {
|
|
175
|
+
try {
|
|
176
|
+
let executable = realpathSync.native(candidate);
|
|
177
|
+
if (within(candidate, workspace) || within(executable, workspace))
|
|
178
|
+
continue;
|
|
179
|
+
executable = resolveRustProxy(name, executable, home);
|
|
180
|
+
if (within(executable, workspace))
|
|
181
|
+
continue;
|
|
182
|
+
accessSync(executable, constants.X_OK);
|
|
183
|
+
if (!statSync(executable).isFile())
|
|
184
|
+
continue;
|
|
185
|
+
if (systemPath(executable)) {
|
|
186
|
+
result.push({ name, executable, readOnlyRoot: executable, directory: false });
|
|
187
|
+
break;
|
|
188
|
+
}
|
|
189
|
+
if (rustTools.has(name) || name === "python" || name === "python3") {
|
|
190
|
+
const runtime = languageRuntime(name, executable, workspace, home);
|
|
191
|
+
if (runtime) {
|
|
192
|
+
result.push(runtime);
|
|
193
|
+
break;
|
|
194
|
+
}
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
if (executableBinary(executable)) {
|
|
198
|
+
result.push({ name, executable, readOnlyRoot: executable, directory: false });
|
|
199
|
+
break;
|
|
200
|
+
}
|
|
201
|
+
// Package managers also install .bin shell shims. Never execute the shim:
|
|
202
|
+
// select the known adjacent package's declared JS entry point, which is
|
|
203
|
+
// validated by the same package-root checks as a normal symlink below.
|
|
204
|
+
if (packages[name] && path.basename(path.dirname(candidate)) === ".bin" && !/\.[cm]?js$/.test(executable)) {
|
|
205
|
+
for (const packageName of packages[name]) {
|
|
206
|
+
try {
|
|
207
|
+
const packageRoot = realpathSync.native(path.resolve(path.dirname(candidate), "..", packageName));
|
|
208
|
+
if (within(packageRoot, workspace) || within(workspace, packageRoot) || packageRoot === home)
|
|
209
|
+
continue;
|
|
210
|
+
const manifest = path.join(packageRoot, "package.json");
|
|
211
|
+
if (statSync(manifest).size > 65536)
|
|
212
|
+
continue;
|
|
213
|
+
const metadata = JSON.parse(readFileSync(manifest, "utf8"));
|
|
214
|
+
const bin = typeof metadata.bin === "string" ? metadata.bin : metadata.bin?.[name];
|
|
215
|
+
if (metadata.name !== packageName || !bin)
|
|
216
|
+
continue;
|
|
217
|
+
const target = realpathSync.native(path.resolve(packageRoot, bin));
|
|
218
|
+
if (!within(target, packageRoot) || !/\.[cm]?js$/.test(target) || !statSync(target).isFile())
|
|
219
|
+
continue;
|
|
220
|
+
accessSync(target, constants.X_OK);
|
|
221
|
+
executable = target;
|
|
222
|
+
break;
|
|
223
|
+
}
|
|
224
|
+
catch { /* Another allowed package layout may exist. */ }
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
if (!packages[name] || !/\.[cm]?js$/.test(executable))
|
|
228
|
+
continue;
|
|
229
|
+
let directory = path.dirname(executable);
|
|
230
|
+
let matched;
|
|
231
|
+
for (let depth = 0; depth < 6 && directory !== path.parse(directory).root && directory !== home; depth++, directory = path.dirname(directory)) {
|
|
232
|
+
if (within(workspace, directory) || within(directory, workspace))
|
|
233
|
+
break;
|
|
234
|
+
const manifest = path.join(directory, "package.json");
|
|
235
|
+
if (!existsSync(manifest))
|
|
236
|
+
continue;
|
|
237
|
+
if (statSync(manifest).size > 65536)
|
|
238
|
+
break;
|
|
239
|
+
const metadata = JSON.parse(readFileSync(manifest, "utf8"));
|
|
240
|
+
if (!metadata.name || !packages[name].includes(metadata.name))
|
|
241
|
+
break;
|
|
242
|
+
const bin = typeof metadata.bin === "string" ? metadata.bin : metadata.bin?.[name];
|
|
243
|
+
if (!bin || realpathSync.native(path.resolve(directory, bin)) !== executable)
|
|
244
|
+
break;
|
|
245
|
+
matched = { name, executable, readOnlyRoot: directory, directory: true };
|
|
246
|
+
break;
|
|
247
|
+
}
|
|
248
|
+
if (matched) {
|
|
249
|
+
result.push(matched);
|
|
250
|
+
break;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
catch { /* An unavailable or unverified candidate is not mounted. */ }
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
return result;
|
|
257
|
+
};
|
|
258
|
+
export const extraRuntimeMounts = (runtimes) => {
|
|
259
|
+
const mounts = new Map();
|
|
260
|
+
for (const runtime of runtimes)
|
|
261
|
+
for (const mount of [runtime, ...runtime.support ?? []]) {
|
|
262
|
+
if (!systemPath(mount.readOnlyRoot))
|
|
263
|
+
mounts.set(mount.readOnlyRoot, mount);
|
|
264
|
+
}
|
|
265
|
+
return [...mounts.values()];
|
|
266
|
+
};
|