@mcoda/mswarm 0.1.133 → 0.1.135-codali.2
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 +44 -0
- package/dist/codali-executor.d.ts +66 -1
- package/dist/codali-executor.d.ts.map +1 -1
- package/dist/codali-executor.js +83 -8
- package/dist/codali-executor.js.map +1 -1
- package/dist/invocation-token.d.ts.map +1 -1
- package/dist/invocation-token.js +1 -0
- package/dist/invocation-token.js.map +1 -1
- package/dist/native-reasoning.d.ts +8 -0
- package/dist/native-reasoning.d.ts.map +1 -0
- package/dist/native-reasoning.js +18 -0
- package/dist/native-reasoning.js.map +1 -0
- package/dist/runtime.d.ts +25 -1
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +220 -20
- package/dist/runtime.js.map +1 -1
- package/dist/server.d.ts +3 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +58 -8
- package/dist/server.js.map +1 -1
- package/dist/vendor/codali/agents/RoleResolution.d.ts.map +1 -1
- package/dist/vendor/codali/agents/RoleResolution.js +4 -4
- package/dist/vendor/codali/api/CodaliApi.d.ts +16 -0
- package/dist/vendor/codali/api/CodaliApi.d.ts.map +1 -1
- package/dist/vendor/codali/api/CodaliApi.js +39 -10
- package/dist/vendor/codali/cli/AskCommand.d.ts +3 -0
- package/dist/vendor/codali/cli/AskCommand.d.ts.map +1 -1
- package/dist/vendor/codali/cli/AskCommand.js +76 -13
- package/dist/vendor/codali/cli/RunCommand.d.ts +4 -0
- package/dist/vendor/codali/cli/RunCommand.d.ts.map +1 -1
- package/dist/vendor/codali/cli/RunCommand.js +110 -24
- package/dist/vendor/codali/cli/TerminalCommand.d.ts +35 -0
- package/dist/vendor/codali/cli/TerminalCommand.d.ts.map +1 -0
- package/dist/vendor/codali/cli/TerminalCommand.js +616 -0
- package/dist/vendor/codali/cli/TerminalCredentials.d.ts +3 -0
- package/dist/vendor/codali/cli/TerminalCredentials.d.ts.map +1 -0
- package/dist/vendor/codali/cli/TerminalCredentials.js +89 -0
- package/dist/vendor/codali/cli/TerminalInput.d.ts +24 -0
- package/dist/vendor/codali/cli/TerminalInput.d.ts.map +1 -0
- package/dist/vendor/codali/cli/TerminalInput.js +98 -0
- package/dist/vendor/codali/cli/TerminalText.d.ts +10 -0
- package/dist/vendor/codali/cli/TerminalText.d.ts.map +1 -0
- package/dist/vendor/codali/cli/TerminalText.js +53 -0
- package/dist/vendor/codali/cli.d.ts.map +1 -1
- package/dist/vendor/codali/cli.js +33 -4
- package/dist/vendor/codali/cognitive/ConfiguredPipeline.d.ts +34 -0
- package/dist/vendor/codali/cognitive/ConfiguredPipeline.d.ts.map +1 -0
- package/dist/vendor/codali/cognitive/ConfiguredPipeline.js +50 -0
- package/dist/vendor/codali/cognitive/ValidationRunner.d.ts +4 -0
- package/dist/vendor/codali/cognitive/ValidationRunner.d.ts.map +1 -1
- package/dist/vendor/codali/cognitive/ValidationRunner.js +18 -41
- package/dist/vendor/codali/config/NodeOrchestration.d.ts +21 -0
- package/dist/vendor/codali/config/NodeOrchestration.d.ts.map +1 -0
- package/dist/vendor/codali/config/NodeOrchestration.js +54 -0
- package/dist/vendor/codali/config/OrchestrationConfig.d.ts +100 -0
- package/dist/vendor/codali/config/OrchestrationConfig.d.ts.map +1 -0
- package/dist/vendor/codali/config/OrchestrationConfig.js +245 -0
- package/dist/vendor/codali/connectors/mcp/McpClient.d.ts +3 -1
- package/dist/vendor/codali/connectors/mcp/McpClient.d.ts.map +1 -1
- package/dist/vendor/codali/connectors/mcp/McpClient.js +3 -2
- package/dist/vendor/codali/connectors/mcp/McpServerRegistry.d.ts +2 -2
- package/dist/vendor/codali/connectors/mcp/McpServerRegistry.d.ts.map +1 -1
- package/dist/vendor/codali/connectors/mcp/McpServerRegistry.js +39 -16
- package/dist/vendor/codali/connectors/mcp/McpToolAdapter.js +2 -2
- package/dist/vendor/codali/connectors/media/ConfiguredMediaTools.d.ts +6 -0
- package/dist/vendor/codali/connectors/media/ConfiguredMediaTools.d.ts.map +1 -0
- package/dist/vendor/codali/connectors/media/ConfiguredMediaTools.js +41 -0
- package/dist/vendor/codali/connectors/media/ImageGenerationTool.js +3 -3
- package/dist/vendor/codali/connectors/media/MediaArtifactReceipt.d.ts +5 -0
- package/dist/vendor/codali/connectors/media/MediaArtifactReceipt.d.ts.map +1 -0
- package/dist/vendor/codali/connectors/media/MediaArtifactReceipt.js +65 -0
- package/dist/vendor/codali/connectors/media/MediaJobStore.d.ts +68 -0
- package/dist/vendor/codali/connectors/media/MediaJobStore.d.ts.map +1 -0
- package/dist/vendor/codali/connectors/media/MediaJobStore.js +199 -0
- package/dist/vendor/codali/connectors/media/MediaJobWorker.d.ts +2 -0
- package/dist/vendor/codali/connectors/media/MediaJobWorker.d.ts.map +1 -0
- package/dist/vendor/codali/connectors/media/MediaJobWorker.js +65 -0
- package/dist/vendor/codali/connectors/media/RemoteMediaJob.d.ts +6 -0
- package/dist/vendor/codali/connectors/media/RemoteMediaJob.d.ts.map +1 -0
- package/dist/vendor/codali/connectors/media/RemoteMediaJob.js +80 -0
- package/dist/vendor/codali/docdex/DocdexClient.d.ts +18 -1
- package/dist/vendor/codali/docdex/DocdexClient.d.ts.map +1 -1
- package/dist/vendor/codali/docdex/DocdexClient.js +135 -19
- package/dist/vendor/codali/docdex/ManagedDocdex.d.ts +29 -0
- package/dist/vendor/codali/docdex/ManagedDocdex.d.ts.map +1 -0
- package/dist/vendor/codali/docdex/ManagedDocdex.js +215 -0
- package/dist/vendor/codali/docdex/ManagedRuntime.d.ts +28 -0
- package/dist/vendor/codali/docdex/ManagedRuntime.d.ts.map +1 -0
- package/dist/vendor/codali/docdex/ManagedRuntime.js +212 -0
- package/dist/vendor/codali/eval/EvaluationProcess.d.ts +13 -0
- package/dist/vendor/codali/eval/EvaluationProcess.d.ts.map +1 -0
- package/dist/vendor/codali/eval/EvaluationProcess.js +76 -0
- package/dist/vendor/codali/eval/LongWorkflowJourney.d.ts +34 -0
- package/dist/vendor/codali/eval/LongWorkflowJourney.d.ts.map +1 -0
- package/dist/vendor/codali/eval/LongWorkflowJourney.js +243 -0
- package/dist/vendor/codali/eval/PlanningCorpus.d.ts +45 -0
- package/dist/vendor/codali/eval/PlanningCorpus.d.ts.map +1 -0
- package/dist/vendor/codali/eval/PlanningCorpus.js +189 -0
- package/dist/vendor/codali/eval/PlanningCorpusRunner.d.ts +13 -0
- package/dist/vendor/codali/eval/PlanningCorpusRunner.d.ts.map +1 -0
- package/dist/vendor/codali/eval/PlanningCorpusRunner.js +89 -0
- package/dist/vendor/codali/eval/TerminalCorpusRunner.d.ts +36 -0
- package/dist/vendor/codali/eval/TerminalCorpusRunner.d.ts.map +1 -0
- package/dist/vendor/codali/eval/TerminalCorpusRunner.js +222 -0
- package/dist/vendor/codali/eval/TerminalTaskCorpus.d.ts +26 -0
- package/dist/vendor/codali/eval/TerminalTaskCorpus.d.ts.map +1 -0
- package/dist/vendor/codali/eval/TerminalTaskCorpus.js +105 -0
- package/dist/vendor/codali/eval/TerminalWorkflowCorpus.d.ts +11 -0
- package/dist/vendor/codali/eval/TerminalWorkflowCorpus.d.ts.map +1 -0
- package/dist/vendor/codali/eval/TerminalWorkflowCorpus.js +116 -0
- package/dist/vendor/codali/eval/fixtures/TerminalRecoveryJourney.d.ts +4 -0
- package/dist/vendor/codali/eval/fixtures/TerminalRecoveryJourney.d.ts.map +1 -0
- package/dist/vendor/codali/eval/fixtures/TerminalRecoveryJourney.js +132 -0
- package/dist/vendor/codali/gateway/CodaliGateway.d.ts +2 -0
- package/dist/vendor/codali/gateway/CodaliGateway.d.ts.map +1 -1
- package/dist/vendor/codali/gateway/CodaliGateway.js +16 -4
- package/dist/vendor/codali/gateway/ConfiguredGateway.d.ts +23 -0
- package/dist/vendor/codali/gateway/ConfiguredGateway.d.ts.map +1 -0
- package/dist/vendor/codali/gateway/ConfiguredGateway.js +44 -0
- package/dist/vendor/codali/gateway/GatewayTracer.d.ts +1 -1
- package/dist/vendor/codali/gateway/GatewayTracer.d.ts.map +1 -1
- package/dist/vendor/codali/gateway/GatewayTracer.js +2 -27
- package/dist/vendor/codali/gateway/LocalGatewayTaskRunner.d.ts.map +1 -1
- package/dist/vendor/codali/gateway/LocalGatewayTaskRunner.js +13 -1
- package/dist/vendor/codali/index.d.ts +23 -0
- package/dist/vendor/codali/index.d.ts.map +1 -1
- package/dist/vendor/codali/index.js +12 -0
- package/dist/vendor/codali/permissions/PermissionGate.d.ts +44 -0
- package/dist/vendor/codali/permissions/PermissionGate.d.ts.map +1 -0
- package/dist/vendor/codali/permissions/PermissionGate.js +97 -0
- package/dist/vendor/codali/providers/ConfiguredEmbeddings.d.ts +43 -0
- package/dist/vendor/codali/providers/ConfiguredEmbeddings.d.ts.map +1 -0
- package/dist/vendor/codali/providers/ConfiguredEmbeddings.js +87 -0
- package/dist/vendor/codali/providers/ConfiguredModelScope.d.ts +69 -0
- package/dist/vendor/codali/providers/ConfiguredModelScope.d.ts.map +1 -0
- package/dist/vendor/codali/providers/ConfiguredModelScope.js +113 -0
- package/dist/vendor/codali/providers/ConfiguredProvider.d.ts +79 -0
- package/dist/vendor/codali/providers/ConfiguredProvider.d.ts.map +1 -0
- package/dist/vendor/codali/providers/ConfiguredProvider.js +238 -0
- package/dist/vendor/codali/providers/ModelServiceError.d.ts +10 -0
- package/dist/vendor/codali/providers/ModelServiceError.d.ts.map +1 -0
- package/dist/vendor/codali/providers/ModelServiceError.js +45 -0
- package/dist/vendor/codali/providers/MswarmWorkerProvider.d.ts.map +1 -1
- package/dist/vendor/codali/providers/MswarmWorkerProvider.js +4 -0
- package/dist/vendor/codali/providers/OpenAiCompatibleProvider.d.ts.map +1 -1
- package/dist/vendor/codali/providers/OpenAiCompatibleProvider.js +134 -52
- package/dist/vendor/codali/providers/ProviderTypes.d.ts +14 -0
- package/dist/vendor/codali/providers/ProviderTypes.d.ts.map +1 -1
- package/dist/vendor/codali/runtime/CodaliJobRuntime.d.ts +6 -0
- package/dist/vendor/codali/runtime/CodaliJobRuntime.d.ts.map +1 -1
- package/dist/vendor/codali/runtime/CodaliJobRuntime.js +36 -1
- package/dist/vendor/codali/runtime/CodaliRuntime.d.ts +30 -0
- package/dist/vendor/codali/runtime/CodaliRuntime.d.ts.map +1 -1
- package/dist/vendor/codali/runtime/CodaliRuntime.js +71 -1
- package/dist/vendor/codali/runtime/CompletionReport.d.ts +5 -0
- package/dist/vendor/codali/runtime/CompletionReport.d.ts.map +1 -0
- package/dist/vendor/codali/runtime/CompletionReport.js +12 -0
- package/dist/vendor/codali/runtime/ProgressTracker.d.ts +17 -0
- package/dist/vendor/codali/runtime/ProgressTracker.d.ts.map +1 -0
- package/dist/vendor/codali/runtime/ProgressTracker.js +27 -0
- package/dist/vendor/codali/runtime/RequestedTestEvidence.d.ts +12 -0
- package/dist/vendor/codali/runtime/RequestedTestEvidence.d.ts.map +1 -0
- package/dist/vendor/codali/runtime/RequestedTestEvidence.js +16 -0
- package/dist/vendor/codali/runtime/RequiredRetrieval.d.ts +13 -0
- package/dist/vendor/codali/runtime/RequiredRetrieval.d.ts.map +1 -0
- package/dist/vendor/codali/runtime/RequiredRetrieval.js +25 -0
- package/dist/vendor/codali/runtime/ReviewSource.d.ts +6 -0
- package/dist/vendor/codali/runtime/ReviewSource.d.ts.map +1 -0
- package/dist/vendor/codali/runtime/ReviewSource.js +38 -0
- package/dist/vendor/codali/runtime/Runner.d.ts +13 -0
- package/dist/vendor/codali/runtime/Runner.d.ts.map +1 -1
- package/dist/vendor/codali/runtime/Runner.js +220 -158
- package/dist/vendor/codali/runtime/SecretRedaction.d.ts +11 -0
- package/dist/vendor/codali/runtime/SecretRedaction.d.ts.map +1 -0
- package/dist/vendor/codali/runtime/SecretRedaction.js +85 -0
- package/dist/vendor/codali/runtime/SessionController.d.ts +148 -0
- package/dist/vendor/codali/runtime/SessionController.d.ts.map +1 -0
- package/dist/vendor/codali/runtime/SessionController.js +1356 -0
- package/dist/vendor/codali/runtime/StructuralEvidence.d.ts +22 -0
- package/dist/vendor/codali/runtime/StructuralEvidence.d.ts.map +1 -0
- package/dist/vendor/codali/runtime/StructuralEvidence.js +87 -0
- package/dist/vendor/codali/runtime/TaskReviewContext.d.ts +34 -0
- package/dist/vendor/codali/runtime/TaskReviewContext.d.ts.map +1 -0
- package/dist/vendor/codali/runtime/TaskReviewContext.js +34 -0
- package/dist/vendor/codali/runtime/ValidationOutput.d.ts +5 -0
- package/dist/vendor/codali/runtime/ValidationOutput.d.ts.map +1 -0
- package/dist/vendor/codali/runtime/ValidationOutput.js +32 -0
- package/dist/vendor/codali/runtime/VerificationCommand.d.ts +3 -0
- package/dist/vendor/codali/runtime/VerificationCommand.d.ts.map +1 -0
- package/dist/vendor/codali/runtime/VerificationCommand.js +48 -0
- package/dist/vendor/codali/runtime/WorkStatusPage.d.ts +15 -0
- package/dist/vendor/codali/runtime/WorkStatusPage.d.ts.map +1 -0
- package/dist/vendor/codali/runtime/WorkStatusPage.js +58 -0
- package/dist/vendor/codali/runtime/WorkspaceLedger.d.ts +13 -0
- package/dist/vendor/codali/runtime/WorkspaceLedger.d.ts.map +1 -0
- package/dist/vendor/codali/runtime/WorkspaceLedger.js +145 -0
- package/dist/vendor/codali/runtime/WorkspaceLock.d.ts.map +1 -1
- package/dist/vendor/codali/runtime/WorkspaceLock.js +20 -17
- package/dist/vendor/codali/runtime/WorkspaceSnapshot.d.ts +13 -0
- package/dist/vendor/codali/runtime/WorkspaceSnapshot.d.ts.map +1 -0
- package/dist/vendor/codali/runtime/WorkspaceSnapshot.js +88 -0
- package/dist/vendor/codali/session/AtomicState.d.ts +13 -0
- package/dist/vendor/codali/session/AtomicState.d.ts.map +1 -0
- package/dist/vendor/codali/session/AtomicState.js +109 -0
- package/dist/vendor/codali/session/SessionStoragePath.d.ts +15 -0
- package/dist/vendor/codali/session/SessionStoragePath.d.ts.map +1 -0
- package/dist/vendor/codali/session/SessionStoragePath.js +89 -0
- package/dist/vendor/codali/session/SessionStore.d.ts +9 -0
- package/dist/vendor/codali/session/SessionStore.d.ts.map +1 -1
- package/dist/vendor/codali/session/SessionStore.js +181 -47
- package/dist/vendor/codali/session/WorkState.d.ts +23 -0
- package/dist/vendor/codali/session/WorkState.d.ts.map +1 -0
- package/dist/vendor/codali/session/WorkState.js +367 -0
- package/dist/vendor/codali/session/WorkTypes.d.ts +94 -0
- package/dist/vendor/codali/session/WorkTypes.d.ts.map +1 -0
- package/dist/vendor/codali/session/WorkTypes.js +1 -0
- package/dist/vendor/codali/tools/ToolRegistry.d.ts +2 -0
- package/dist/vendor/codali/tools/ToolRegistry.d.ts.map +1 -1
- package/dist/vendor/codali/tools/ToolRegistry.js +12 -2
- package/dist/vendor/codali/tools/ToolTypes.d.ts +16 -0
- package/dist/vendor/codali/tools/ToolTypes.d.ts.map +1 -1
- package/dist/vendor/codali/tools/docdex/DocdexTools.d.ts +2 -0
- package/dist/vendor/codali/tools/docdex/DocdexTools.d.ts.map +1 -1
- package/dist/vendor/codali/tools/docdex/DocdexTools.js +17 -2
- package/dist/vendor/codali/tools/filesystem/FileTools.d.ts.map +1 -1
- package/dist/vendor/codali/tools/filesystem/FileTools.js +30 -10
- package/dist/vendor/codali/tools/shell/AsyncProcess.d.ts +16 -0
- package/dist/vendor/codali/tools/shell/AsyncProcess.d.ts.map +1 -0
- package/dist/vendor/codali/tools/shell/AsyncProcess.js +94 -0
- package/dist/vendor/codali/tools/shell/DarwinRuntimeLibraries.d.ts +13 -0
- package/dist/vendor/codali/tools/shell/DarwinRuntimeLibraries.d.ts.map +1 -0
- package/dist/vendor/codali/tools/shell/DarwinRuntimeLibraries.js +130 -0
- package/dist/vendor/codali/tools/shell/HostToolchain.d.ts +19 -0
- package/dist/vendor/codali/tools/shell/HostToolchain.d.ts.map +1 -0
- package/dist/vendor/codali/tools/shell/HostToolchain.js +264 -0
- package/dist/vendor/codali/tools/shell/IsolatedExecutor.d.ts +15 -0
- package/dist/vendor/codali/tools/shell/IsolatedExecutor.d.ts.map +1 -0
- package/dist/vendor/codali/tools/shell/IsolatedExecutor.js +134 -0
- package/dist/vendor/codali/tools/shell/ShellTool.d.ts.map +1 -1
- package/dist/vendor/codali/tools/shell/ShellTool.js +9 -29
- package/dist/vendor/codali/tools/shell/ShellWorkingDirectory.d.ts +3 -0
- package/dist/vendor/codali/tools/shell/ShellWorkingDirectory.d.ts.map +1 -0
- package/dist/vendor/codali/tools/shell/ShellWorkingDirectory.js +30 -0
- package/package.json +31 -29
- package/scripts/backend-http-drain.mjs +152 -0
- package/scripts/media_gpu_lock.py +238 -0
- package/scripts/stable-audio-3-openai-server.py +45 -14
- package/scripts/stable-diffusion-cpp-openai-server.py +34 -4
- package/scripts/stable-diffusion-cpp-webm-duration.patch +22 -0
- package/scripts/wan22-openai-server.py +97 -18
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"IsolatedExecutor.d.ts","sourceRoot":"","sources":["../../../src/tools/shell/IsolatedExecutor.ts"],"names":[],"mappings":"AAMA,OAAO,EAAsB,KAAK,WAAW,EAAE,KAAK,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAC/F,OAAO,EAA6C,KAAK,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAEjG,MAAM,WAAW,cAAc;IAAG,SAAS,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,YAAY,GAAG,cAAc,GAAG,aAAa,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAAE;AAEhI,mFAAmF;AACnF,eAAO,MAAM,oBAAoB,GAAI,MAAM,MAAM,EAAE,SAAS,MAAM,EAAE,YAAY,MAAM,EAAE,EAAE,UAAU,WAAW,EAAE,EAAE,iBAAe,KAAG,MAyBpI,CAAC;AAQF,eAAO,MAAM,cAAc,QAAO,cAcjC,CAAC;AAEF,eAAO,MAAM,sBAAsB,GAAI,UAAS;IAAE,OAAO,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,cAAc,CAAA;CAAO,KAAG,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,WAAW,KAAK,OAAO,CAAC,iBAAiB,CAAC,CAkD3L,CAAC"}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { existsSync, realpathSync, mkdtempSync, rmSync, promises as fs } from "node:fs";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { spawnSync } from "node:child_process";
|
|
5
|
+
import { runProcess } from "./AsyncProcess.js";
|
|
6
|
+
import { resolveShellWorkingDirectory } from "./ShellWorkingDirectory.js";
|
|
7
|
+
import { ToolExecutionError } from "../ToolTypes.js";
|
|
8
|
+
import { discoverHostToolchain, extraRuntimeMounts } from "./HostToolchain.js";
|
|
9
|
+
/** All paths have been canonicalized by the host, before launching the sandbox. */
|
|
10
|
+
export const darwinSandboxProfile = (root, scratch, writePaths, runtimes, network = false) => {
|
|
11
|
+
const quoted = (value) => JSON.stringify(value);
|
|
12
|
+
const ancestors = new Set();
|
|
13
|
+
for (const target of [root, scratch, ...extraRuntimeMounts(runtimes).map(runtime => runtime.readOnlyRoot)]) {
|
|
14
|
+
for (let parent = path.dirname(target); parent !== path.dirname(parent); parent = path.dirname(parent))
|
|
15
|
+
ancestors.add(parent);
|
|
16
|
+
}
|
|
17
|
+
return [
|
|
18
|
+
"(version 1)", "(deny default)", "(allow process*)", "(allow sysctl-read)",
|
|
19
|
+
// dyld opens the root directory when locating its shared cache. This literal
|
|
20
|
+
// permits the directory itself, never a recursive read of the filesystem.
|
|
21
|
+
'(allow file-read* (literal "/"))',
|
|
22
|
+
// realpath/lstat needs ancestor metadata, not directory contents or sibling files.
|
|
23
|
+
...[...ancestors].map(parent => `(allow file-read-metadata (literal ${quoted(parent)}))`),
|
|
24
|
+
'(allow file-read* (subpath "/usr") (subpath "/bin") (subpath "/sbin") (subpath "/System") (subpath "/Library") (subpath "/private/etc") (subpath "/dev"))',
|
|
25
|
+
// Apple's /bin/sh reads this system selector before starting npm scripts.
|
|
26
|
+
// Permit only that selector, not other private /var files or directories.
|
|
27
|
+
'(allow file-read* (literal "/private/var/select/sh") (literal "/var/select/sh"))',
|
|
28
|
+
'(allow file-read-metadata (literal "/private/var/select") (literal "/var/select"))',
|
|
29
|
+
`(allow file-read* (subpath ${quoted(root)}))`,
|
|
30
|
+
...extraRuntimeMounts(runtimes).map(runtime => `(allow file-read* (${runtime.directory ? "subpath" : "literal"} ${quoted(runtime.readOnlyRoot)}))`),
|
|
31
|
+
`(allow file-read* file-write* (subpath ${quoted(scratch)}))`,
|
|
32
|
+
...writePaths.map(file => `(allow file-write* (subpath ${quoted(file)}))`),
|
|
33
|
+
...[".git", ".mcoda"].map(dir => `(deny file-write* (subpath ${quoted(path.join(root, dir))}))`),
|
|
34
|
+
network ? "(allow network*)" : "(deny network*)",
|
|
35
|
+
].join("\n");
|
|
36
|
+
};
|
|
37
|
+
const bubblewrapRoot = () => {
|
|
38
|
+
const args = ["--die-with-parent", "--new-session", "--unshare-all", "--ro-bind", "/usr", "/usr", "--dev", "/dev", "--proc", "/proc", "--tmpfs", "/tmp"];
|
|
39
|
+
for (const dir of ["/bin", "/sbin", "/lib", "/lib64"])
|
|
40
|
+
if (existsSync(dir))
|
|
41
|
+
args.push("--ro-bind", realpathSync(dir), dir);
|
|
42
|
+
return args;
|
|
43
|
+
};
|
|
44
|
+
export const executorStatus = () => {
|
|
45
|
+
if (process.platform === "linux") {
|
|
46
|
+
if (!existsSync("/usr/bin/bwrap"))
|
|
47
|
+
return { available: false, backend: "unavailable", reason: "Install bubblewrap to enable isolated shell execution" };
|
|
48
|
+
const probe = spawnSync("/usr/bin/bwrap", [...bubblewrapRoot(), "--", "/usr/bin/true"], { timeout: 3000, stdio: "ignore" });
|
|
49
|
+
return probe.status === 0 ? { available: true, backend: "bubblewrap" } : { available: false, backend: "unavailable", reason: "bubblewrap user namespaces are unavailable" };
|
|
50
|
+
}
|
|
51
|
+
if (process.platform === "darwin" && existsSync("/usr/bin/sandbox-exec")) {
|
|
52
|
+
const scratch = realpathSync(mkdtempSync(path.join(os.tmpdir(), "codali-isolation-probe-")));
|
|
53
|
+
try {
|
|
54
|
+
const probe = spawnSync("/usr/bin/sandbox-exec", ["-p", darwinSandboxProfile(scratch, scratch, [], []), "/usr/bin/true"], { timeout: 3000, stdio: "ignore" });
|
|
55
|
+
if (probe.status === 0)
|
|
56
|
+
return { available: true, backend: "sandbox-exec" };
|
|
57
|
+
}
|
|
58
|
+
finally {
|
|
59
|
+
rmSync(scratch, { recursive: true, force: true });
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return { available: false, backend: "unavailable", reason: "No supported isolation backend on this platform" };
|
|
63
|
+
};
|
|
64
|
+
export const createIsolatedExecutor = (options = {}) => {
|
|
65
|
+
const status = options.status ?? executorStatus();
|
|
66
|
+
return async (command, args, context) => {
|
|
67
|
+
if (!status.available)
|
|
68
|
+
throw new ToolExecutionError("tool_permission_denied", status.reason ?? "Isolation unavailable");
|
|
69
|
+
const root = realpathSync(context.workspaceRoot);
|
|
70
|
+
const cwd = resolveShellWorkingDirectory(root, context.shellWorkingDirectory);
|
|
71
|
+
if (root === path.parse(root).root)
|
|
72
|
+
throw new ToolExecutionError("tool_permission_denied", "Filesystem root cannot be an isolated workspace");
|
|
73
|
+
const runtimes = discoverHostToolchain(root);
|
|
74
|
+
const writePaths = (context.allowedWritePaths ?? ["."]).map(file => {
|
|
75
|
+
const requested = path.resolve(root, file);
|
|
76
|
+
if (requested !== root && !requested.startsWith(`${root}${path.sep}`))
|
|
77
|
+
throw new ToolExecutionError("tool_permission_denied", "Shell write scope escapes workspace");
|
|
78
|
+
if (!existsSync(requested))
|
|
79
|
+
throw new ToolExecutionError("tool_permission_denied", "Create the scoped directory/file with write_file before shell execution");
|
|
80
|
+
const real = realpathSync(requested);
|
|
81
|
+
if (real !== root && !real.startsWith(`${root}${path.sep}`))
|
|
82
|
+
throw new ToolExecutionError("tool_permission_denied", "Shell write scope resolves outside workspace");
|
|
83
|
+
if ([".git", ".mcoda"].some(dir => real === path.join(root, dir) || real.startsWith(`${root}/${dir}/`)))
|
|
84
|
+
throw new ToolExecutionError("tool_permission_denied", "Repository control metadata cannot be a write scope");
|
|
85
|
+
return real;
|
|
86
|
+
});
|
|
87
|
+
if (status.backend === "bubblewrap") {
|
|
88
|
+
const launch = bubblewrapRoot();
|
|
89
|
+
if (options.network) {
|
|
90
|
+
launch.push("--share-net");
|
|
91
|
+
for (const file of ["/etc/resolv.conf", "/etc/hosts", "/etc/nsswitch.conf", "/etc/ssl/certs"])
|
|
92
|
+
if (existsSync(file))
|
|
93
|
+
launch.push("--ro-bind", realpathSync(file), file);
|
|
94
|
+
}
|
|
95
|
+
launch.push("--dir", path.dirname(root), "--ro-bind", root, root);
|
|
96
|
+
for (const runtime of extraRuntimeMounts(runtimes))
|
|
97
|
+
launch.push("--dir", path.dirname(runtime.readOnlyRoot), "--ro-bind", runtime.readOnlyRoot, runtime.readOnlyRoot);
|
|
98
|
+
launch.push("--dir", "/tmp/codali-bin");
|
|
99
|
+
for (const runtime of runtimes)
|
|
100
|
+
launch.push("--symlink", runtime.executable, `/tmp/codali-bin/${runtime.name}`);
|
|
101
|
+
for (const file of writePaths)
|
|
102
|
+
launch.push("--bind", file, file);
|
|
103
|
+
launch.push("--chdir", cwd, "--dir", "/tmp/home", "--setenv", "HOME", "/tmp/home", "--setenv", "TMPDIR", "/tmp", "--setenv", "PATH", "/tmp/codali-bin:/usr/local/bin:/usr/bin:/bin");
|
|
104
|
+
for (const [key, value] of Object.entries({ CARGO_HOME: "/tmp/home/cargo", XDG_CACHE_HOME: "/tmp/home/cache", PYTHONPYCACHEPREFIX: "/tmp/home/pycache", PYTHONUSERBASE: "/tmp/home/python" }))
|
|
105
|
+
launch.push("--setenv", key, value);
|
|
106
|
+
// Git hooks/config and private session state are not part of executable workspace access.
|
|
107
|
+
for (const dir of [".git", ".mcoda"])
|
|
108
|
+
if (existsSync(path.join(root, dir)))
|
|
109
|
+
launch.push("--ro-bind", path.join(root, dir), path.join(root, dir));
|
|
110
|
+
launch.push("--", command, ...args);
|
|
111
|
+
return runProcess("/usr/bin/bwrap", launch, context);
|
|
112
|
+
}
|
|
113
|
+
const scratch = realpathSync(await fs.mkdtemp(path.join(os.tmpdir(), "codali-shell-")));
|
|
114
|
+
try {
|
|
115
|
+
await fs.chmod(scratch, 0o700);
|
|
116
|
+
const privateHome = path.join(scratch, "home");
|
|
117
|
+
const privateBin = path.join(scratch, "bin");
|
|
118
|
+
await fs.mkdir(privateHome, { mode: 0o700 });
|
|
119
|
+
await fs.mkdir(privateBin, { mode: 0o700 });
|
|
120
|
+
for (const runtime of runtimes)
|
|
121
|
+
await fs.symlink(runtime.executable, path.join(privateBin, runtime.name));
|
|
122
|
+
const profile = darwinSandboxProfile(root, scratch, writePaths, runtimes, options.network);
|
|
123
|
+
return await runProcess("/usr/bin/sandbox-exec", ["-p", profile, runtimes.find(runtime => runtime.name === command)?.executable ?? command, ...args], context, {
|
|
124
|
+
...runtimes.find(runtime => runtime.name === "cc")?.environment,
|
|
125
|
+
HOME: privateHome, TMPDIR: scratch, PATH: `${privateBin}:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin`,
|
|
126
|
+
CARGO_HOME: path.join(privateHome, "cargo"), XDG_CACHE_HOME: path.join(privateHome, "cache"),
|
|
127
|
+
PYTHONPYCACHEPREFIX: path.join(privateHome, "pycache"), PYTHONUSERBASE: path.join(privateHome, "python"),
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
finally {
|
|
131
|
+
await fs.rm(scratch, { recursive: true, force: true });
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ShellTool.d.ts","sourceRoot":"","sources":["../../../src/tools/shell/ShellTool.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ShellTool.d.ts","sourceRoot":"","sources":["../../../src/tools/shell/ShellTool.ts"],"names":[],"mappings":"AACA,OAAO,EAAwC,KAAK,cAAc,EAAE,MAAM,iBAAiB,CAAC;AA6C5F,eAAO,MAAM,eAAe,QAAO,cAmDjC,CAAC"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { spawnSync } from "node:child_process";
|
|
2
1
|
import path from "node:path";
|
|
3
2
|
import { ToolExecutionError } from "../ToolTypes.js";
|
|
3
|
+
import { runProcess } from "./AsyncProcess.js";
|
|
4
|
+
import { resolveShellWorkingDirectory } from "./ShellWorkingDirectory.js";
|
|
4
5
|
const resolveAllowlist = (context) => {
|
|
5
6
|
return context.shellAllowlist ?? [];
|
|
6
7
|
};
|
|
@@ -40,15 +41,17 @@ const isDestructiveShellAction = (command, args) => {
|
|
|
40
41
|
};
|
|
41
42
|
export const createShellTool = () => ({
|
|
42
43
|
name: "run_shell",
|
|
43
|
-
description:
|
|
44
|
+
description: 'Run an allowlisted executable. Default cwd is the workspace root; set cwd for a module-local command, e.g. {"command":"npm","args":["test"],"cwd":"packages/app"}. In terminal mode the workspace is read-only unless writePaths names root-relative paths inside the claimed task. Existing source files in those paths require current reads and structural analysis.',
|
|
44
45
|
readOnly: false,
|
|
45
46
|
capability: "shell",
|
|
46
47
|
inputSchema: {
|
|
47
48
|
type: "object",
|
|
48
49
|
required: ["command"],
|
|
49
50
|
properties: {
|
|
50
|
-
command: { type: "string" },
|
|
51
|
+
command: { type: "string", description: "One executable name, without arguments or shell syntax." },
|
|
51
52
|
args: { type: "array", items: { type: "string" } },
|
|
53
|
+
cwd: { type: "string", description: "Existing working directory inside the workspace, relative to its root. Default: workspace root. Does not change the base of writePaths." },
|
|
54
|
+
writePaths: { type: "array", items: { type: "string" }, description: "Terminal mode: root-relative existing files/directories to make writable, regardless of cwd. Default: no workspace writes. Must fit the claimed task scope." },
|
|
52
55
|
},
|
|
53
56
|
},
|
|
54
57
|
handler: async (args, context) => {
|
|
@@ -57,7 +60,7 @@ export const createShellTool = () => ({
|
|
|
57
60
|
retryable: false,
|
|
58
61
|
});
|
|
59
62
|
}
|
|
60
|
-
const { command, args: commandArgs = [] } = args;
|
|
63
|
+
const { command, args: commandArgs = [], cwd } = args;
|
|
61
64
|
if (!context.allowDestructiveOperations
|
|
62
65
|
&& isDestructiveShellAction(command, commandArgs)) {
|
|
63
66
|
throw new ToolExecutionError("tool_permission_denied", "Destructive operation blocked by policy", {
|
|
@@ -77,30 +80,7 @@ export const createShellTool = () => ({
|
|
|
77
80
|
details: { command },
|
|
78
81
|
});
|
|
79
82
|
}
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
encoding: "utf8",
|
|
83
|
-
});
|
|
84
|
-
if (result.error) {
|
|
85
|
-
throw result.error;
|
|
86
|
-
}
|
|
87
|
-
const stdout = result.stdout ?? "";
|
|
88
|
-
const stderr = result.stderr ?? "";
|
|
89
|
-
const exitCode = result.status ?? 0;
|
|
90
|
-
if (exitCode !== 0) {
|
|
91
|
-
const message = stderr || stdout || `Command failed with exit code ${exitCode}`;
|
|
92
|
-
throw new ToolExecutionError("tool_execution_failed", message, {
|
|
93
|
-
retryable: false,
|
|
94
|
-
details: { command, exitCode },
|
|
95
|
-
});
|
|
96
|
-
}
|
|
97
|
-
return {
|
|
98
|
-
output: stdout || stderr,
|
|
99
|
-
data: {
|
|
100
|
-
stdout,
|
|
101
|
-
stderr,
|
|
102
|
-
exitCode,
|
|
103
|
-
},
|
|
104
|
-
};
|
|
83
|
+
const shellWorkingDirectory = resolveShellWorkingDirectory(context.workspaceRoot, cwd);
|
|
84
|
+
return (context.shellExecutor ?? runProcess)(command, commandArgs, { ...context, shellWorkingDirectory });
|
|
105
85
|
},
|
|
106
86
|
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ShellWorkingDirectory.d.ts","sourceRoot":"","sources":["../../../src/tools/shell/ShellWorkingDirectory.ts"],"names":[],"mappings":"AAIA,gFAAgF;AAChF,eAAO,MAAM,4BAA4B,GAAI,eAAe,MAAM,EAAE,YAAY,MAAM,KAAG,MAoBxF,CAAC"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { realpathSync, statSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { ToolExecutionError } from "../ToolTypes.js";
|
|
4
|
+
/** Resolve only the process directory; write scopes still use workspaceRoot. */
|
|
5
|
+
export const resolveShellWorkingDirectory = (workspaceRoot, requested) => {
|
|
6
|
+
if (requested !== undefined && (typeof requested !== "string" || !requested.trim())) {
|
|
7
|
+
throw new ToolExecutionError("tool_invalid_args", "cwd must be a non-empty directory path inside the workspace");
|
|
8
|
+
}
|
|
9
|
+
const root = realpathSync.native(workspaceRoot);
|
|
10
|
+
const inside = (candidate) => {
|
|
11
|
+
const relative = path.relative(root, candidate);
|
|
12
|
+
return relative !== ".." && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative);
|
|
13
|
+
};
|
|
14
|
+
const candidate = path.resolve(root, requested ?? ".");
|
|
15
|
+
if (!inside(candidate))
|
|
16
|
+
throw new ToolExecutionError("tool_permission_denied", "Shell cwd escapes the workspace");
|
|
17
|
+
try {
|
|
18
|
+
const resolved = realpathSync.native(candidate);
|
|
19
|
+
if (!inside(resolved))
|
|
20
|
+
throw new ToolExecutionError("tool_permission_denied", "Shell cwd resolves outside the workspace");
|
|
21
|
+
if (!statSync(resolved).isDirectory())
|
|
22
|
+
throw new ToolExecutionError("tool_invalid_args", "Shell cwd must be an existing directory");
|
|
23
|
+
return resolved;
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
if (error instanceof ToolExecutionError)
|
|
27
|
+
throw error;
|
|
28
|
+
throw new ToolExecutionError("tool_invalid_args", "Shell cwd is not an accessible existing directory");
|
|
29
|
+
}
|
|
30
|
+
};
|
package/package.json
CHANGED
|
@@ -1,13 +1,21 @@
|
|
|
1
1
|
{
|
|
2
|
-
"
|
|
3
|
-
"version": "0.1.133",
|
|
4
|
-
"description": "Owner-run mswarm node for exposing local mcoda agents through the mswarm self-hosted gateway.",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"main": "dist/server.js",
|
|
7
|
-
"types": "dist/server.d.ts",
|
|
2
|
+
"author": "bekir dag",
|
|
8
3
|
"bin": {
|
|
9
|
-
"mswarm
|
|
10
|
-
"mswarm": "dist/server.js"
|
|
4
|
+
"mswarm": "dist/server.js",
|
|
5
|
+
"mswarm-self-hosted-node": "dist/server.js"
|
|
6
|
+
},
|
|
7
|
+
"bugs": {
|
|
8
|
+
"url": "https://github.com/bekirdag/mcoda/issues"
|
|
9
|
+
},
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"@mcoda/db": "0.1.135-codali.2",
|
|
12
|
+
"@mcoda/shared": "0.1.135-codali.2",
|
|
13
|
+
"fastify": "^5.7.4",
|
|
14
|
+
"undici": "^6.28.0"
|
|
15
|
+
},
|
|
16
|
+
"description": "Owner-run mswarm node for exposing local mcoda agents through the mswarm self-hosted gateway.",
|
|
17
|
+
"engines": {
|
|
18
|
+
"node": ">=20"
|
|
11
19
|
},
|
|
12
20
|
"files": [
|
|
13
21
|
"dist",
|
|
@@ -16,23 +24,11 @@
|
|
|
16
24
|
"scripts",
|
|
17
25
|
"!scripts/**/__pycache__/**",
|
|
18
26
|
"!scripts/**/*.pyc",
|
|
27
|
+
"!scripts/**/*.test.mjs",
|
|
19
28
|
"README.md",
|
|
20
29
|
"LICENSE"
|
|
21
30
|
],
|
|
22
|
-
"engines": {
|
|
23
|
-
"node": ">=20"
|
|
24
|
-
},
|
|
25
|
-
"repository": {
|
|
26
|
-
"type": "git",
|
|
27
|
-
"url": "git+https://github.com/bekirdag/mcoda.git",
|
|
28
|
-
"directory": "packages/mswarm"
|
|
29
|
-
},
|
|
30
|
-
"bugs": {
|
|
31
|
-
"url": "https://github.com/bekirdag/mcoda/issues"
|
|
32
|
-
},
|
|
33
31
|
"homepage": "https://github.com/bekirdag/mcoda/tree/main/packages/mswarm#readme",
|
|
34
|
-
"license": "MIT",
|
|
35
|
-
"author": "bekir dag",
|
|
36
32
|
"keywords": [
|
|
37
33
|
"mcoda",
|
|
38
34
|
"mswarm",
|
|
@@ -42,18 +38,24 @@
|
|
|
42
38
|
"agents",
|
|
43
39
|
"ollama"
|
|
44
40
|
],
|
|
41
|
+
"license": "MIT",
|
|
42
|
+
"main": "dist/server.js",
|
|
43
|
+
"name": "@mcoda/mswarm",
|
|
45
44
|
"publishConfig": {
|
|
46
45
|
"access": "public"
|
|
47
46
|
},
|
|
48
|
-
"
|
|
49
|
-
"
|
|
50
|
-
"
|
|
51
|
-
"
|
|
52
|
-
"@mcoda/shared": "0.1.133"
|
|
47
|
+
"repository": {
|
|
48
|
+
"directory": "packages/mswarm",
|
|
49
|
+
"type": "git",
|
|
50
|
+
"url": "git+https://github.com/bekirdag/mcoda.git"
|
|
53
51
|
},
|
|
54
52
|
"scripts": {
|
|
55
53
|
"build": "pnpm --filter @mcoda/shared run build && pnpm --filter @mcoda/codali run build && tsc -p tsconfig.json && node ../../scripts/copy-mswarm-codali-vendor.js",
|
|
56
54
|
"lint": "echo \"lint not configured\"",
|
|
57
|
-
"test": "pnpm run build && node ../../scripts/run-node-tests.js dist"
|
|
58
|
-
|
|
59
|
-
}
|
|
55
|
+
"test": "pnpm run build && node ../../scripts/run-node-tests.js dist && pnpm run test:backend-drain",
|
|
56
|
+
"test:backend-drain": "node --experimental-websocket --test scripts/backend-http-drain.test.mjs"
|
|
57
|
+
},
|
|
58
|
+
"type": "module",
|
|
59
|
+
"types": "dist/server.d.ts",
|
|
60
|
+
"version": "0.1.135-codali.2"
|
|
61
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Operator bootstrap for legacy HTTP services without a shutdown handler.
|
|
4
|
+
* Run inside the target container against its loopback Node inspector only.
|
|
5
|
+
* This is not a public administration endpoint. Qualification must pin the
|
|
6
|
+
* runtime/image and prove both request completion and durable-job quiescence.
|
|
7
|
+
*/
|
|
8
|
+
import { fileURLToPath } from 'node:url';
|
|
9
|
+
import { resolve } from 'node:path';
|
|
10
|
+
|
|
11
|
+
// Self-contained because the inspector evaluates this function in the target.
|
|
12
|
+
export function controlHttpAdmission({ action, role, port, nonce, expectedPid }) {
|
|
13
|
+
if (!['gateway', 'proxy'].includes(role) || !Number.isInteger(port) || port < 1 || port > 65535 ||
|
|
14
|
+
!/^[a-zA-Z0-9-]{16,100}$/.test(nonce) || expectedPid !== process.pid ||
|
|
15
|
+
!['inspect', 'barrier', 'status', 'close', 'restore'].includes(action)) {
|
|
16
|
+
throw new Error('Invalid or mismatched deployment target');
|
|
17
|
+
}
|
|
18
|
+
const key = Symbol.for('mcoda.backend.http-drain.v1');
|
|
19
|
+
let state = globalThis[key];
|
|
20
|
+
if (state?.phase === 'restored' && action === 'barrier') {
|
|
21
|
+
delete globalThis[key];
|
|
22
|
+
state = undefined;
|
|
23
|
+
}
|
|
24
|
+
const snapshot = () => ({
|
|
25
|
+
nonce: state.nonce, role: state.role, port: state.port, pid: process.pid,
|
|
26
|
+
phase: state.phase, blockedRequests: state.blockedRequests,
|
|
27
|
+
controlRequests: state.controlRequests, installedAt: state.installedAt,
|
|
28
|
+
closedAt: state.closedAt, closeError: state.closeError,
|
|
29
|
+
listening: state.server.listening,
|
|
30
|
+
});
|
|
31
|
+
if (state && (state.nonce !== nonce || state.port !== port || state.role !== role)) {
|
|
32
|
+
throw new Error('Another deployment owns this process');
|
|
33
|
+
}
|
|
34
|
+
if (!state) {
|
|
35
|
+
if (!['inspect', 'barrier'].includes(action)) throw new Error('No admission barrier is installed');
|
|
36
|
+
// Legacy bootstrap only: the inspected runtime's HTTP handle shape must be
|
|
37
|
+
// qualified before deployment. Fail closed on additional/bypassing handlers.
|
|
38
|
+
const servers = process._getActiveHandles().filter((handle) =>
|
|
39
|
+
handle?.constructor?.name === 'Server' && typeof handle.address === 'function' &&
|
|
40
|
+
handle.address()?.port === port && typeof handle.closeIdleConnections === 'function');
|
|
41
|
+
if (servers.length !== 1) throw new Error('Expected exactly one qualified HTTP server');
|
|
42
|
+
const server = servers[0];
|
|
43
|
+
const listeners = server.listeners('request');
|
|
44
|
+
if (action === 'inspect') return { nonce, role, port, pid: process.pid, phase: 'unmodified',
|
|
45
|
+
runtimeVersion: process.version, listening: server.listening, requestListeners: listeners.length,
|
|
46
|
+
alternateListeners: Object.fromEntries(['checkContinue', 'checkExpectation', 'upgrade'].map((name) => [name, server.listenerCount(name)])) };
|
|
47
|
+
if (listeners.length !== 1 || ['checkContinue', 'checkExpectation', 'upgrade'].some((name) => server.listenerCount(name))) {
|
|
48
|
+
throw new Error('Unsupported HTTP request dispatch; no listener changed');
|
|
49
|
+
}
|
|
50
|
+
state = { nonce, role, port, server, listeners, address: server.address(),
|
|
51
|
+
phase: 'barrier', installedAt: Date.now(), blockedRequests: 0, controlRequests: 0 };
|
|
52
|
+
const controlPath = /^\/v1\/swarm\/self-hosted\/node\/(?:heartbeat|load|models|jobs\/poll|jobs\/[A-Za-z0-9_-]+\/(?:start|events|result))$/;
|
|
53
|
+
state.listener = function (request, response) {
|
|
54
|
+
const pathname = (request.url ?? '').split('?')[0];
|
|
55
|
+
const health = request.method === 'GET' && pathname === '/healthz';
|
|
56
|
+
if (health || (state.role === 'gateway' && request.method === 'POST' && controlPath.test(pathname))) {
|
|
57
|
+
state.controlRequests++;
|
|
58
|
+
for (const listener of state.listeners) listener.call(this, request, response);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
state.blockedRequests++;
|
|
62
|
+
response.shouldKeepAlive = false;
|
|
63
|
+
response.writeHead(503, { 'content-type': 'application/json', 'retry-after': '15', connection: 'close' });
|
|
64
|
+
response.end(JSON.stringify({ error: { code: 'deployment_draining', message: 'Service deployment is draining; retry shortly.' } }));
|
|
65
|
+
request.resume();
|
|
66
|
+
};
|
|
67
|
+
globalThis[key] = state;
|
|
68
|
+
server.removeListener('request', listeners[0]);
|
|
69
|
+
server.on('request', state.listener);
|
|
70
|
+
}
|
|
71
|
+
if (action === 'inspect' || action === 'barrier' || action === 'status') return snapshot();
|
|
72
|
+
if (action === 'close') {
|
|
73
|
+
if (state.phase === 'barrier') {
|
|
74
|
+
state.phase = 'closing';
|
|
75
|
+
// Never close active connections. The callback proves every HTTP request
|
|
76
|
+
// has finished; the operator separately checks durable queue records.
|
|
77
|
+
state.server.close((error) => {
|
|
78
|
+
clearInterval(state.idleReaper);
|
|
79
|
+
state.closeError = error ? String(error.message) : undefined;
|
|
80
|
+
state.phase = error ? 'close_failed' : 'closed';
|
|
81
|
+
state.closedAt = Date.now();
|
|
82
|
+
});
|
|
83
|
+
state.server.closeIdleConnections();
|
|
84
|
+
// A response accepted before the barrier can become idle after close()
|
|
85
|
+
// starts. Reap only idle sockets as those responses finish.
|
|
86
|
+
state.idleReaper = setInterval(() => state.server.closeIdleConnections(), 100);
|
|
87
|
+
state.idleReaper.unref();
|
|
88
|
+
}
|
|
89
|
+
return snapshot();
|
|
90
|
+
}
|
|
91
|
+
if (state.phase === 'closing' || state.phase === 'close_failed') throw new Error('HTTP close has not completed');
|
|
92
|
+
if (state.phase === 'closed') {
|
|
93
|
+
state.phase = 'restoring';
|
|
94
|
+
state.server.listen({ port: state.port, host: state.address.address }, () => {
|
|
95
|
+
state.server.removeListener('request', state.listener);
|
|
96
|
+
for (const listener of state.listeners) state.server.on('request', listener);
|
|
97
|
+
state.phase = 'restored';
|
|
98
|
+
});
|
|
99
|
+
} else if (state.phase === 'barrier') {
|
|
100
|
+
state.server.removeListener('request', state.listener);
|
|
101
|
+
for (const listener of state.listeners) state.server.on('request', listener);
|
|
102
|
+
state.phase = 'restored';
|
|
103
|
+
}
|
|
104
|
+
return snapshot();
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export async function inspectorControl(options) {
|
|
108
|
+
const inspectorPort = options.inspectorPort ?? 9229;
|
|
109
|
+
if (!Number.isInteger(inspectorPort) || inspectorPort < 1 || inspectorPort > 65535) throw new Error('Invalid inspector port');
|
|
110
|
+
if (typeof WebSocket !== 'function') throw new Error('Use Node20 with --experimental-websocket, or Node22');
|
|
111
|
+
const response = await fetch(`http://127.0.0.1:${inspectorPort}/json/list`, { signal: AbortSignal.timeout(5000), redirect: 'error' });
|
|
112
|
+
if (!response.ok) throw new Error(`Inspector discovery failed: ${response.status}`);
|
|
113
|
+
const targets = await response.json();
|
|
114
|
+
if (!Array.isArray(targets) || targets.length !== 1) throw new Error('Inspector target is ambiguous');
|
|
115
|
+
const endpoint = new URL(targets[0].webSocketDebuggerUrl);
|
|
116
|
+
if (endpoint.protocol !== 'ws:' || endpoint.hostname !== '127.0.0.1' || Number(endpoint.port) !== inspectorPort || endpoint.username || endpoint.password) {
|
|
117
|
+
throw new Error('Inspector must remain on the selected container loopback port');
|
|
118
|
+
}
|
|
119
|
+
const socket = new WebSocket(endpoint.href);
|
|
120
|
+
return await new Promise((resolveResult, reject) => {
|
|
121
|
+
const timeout = setTimeout(() => finish(new Error('Inspector operation timed out')), 10_000);
|
|
122
|
+
let finished = false;
|
|
123
|
+
function finish(error, value) {
|
|
124
|
+
if (finished) return;
|
|
125
|
+
finished = true;
|
|
126
|
+
clearTimeout(timeout);
|
|
127
|
+
try { socket.close(); } catch { /* Preserve the original failure. */ }
|
|
128
|
+
if (error) reject(error); else resolveResult(value);
|
|
129
|
+
}
|
|
130
|
+
socket.addEventListener('error', () => finish(new Error('Inspector connection failed')));
|
|
131
|
+
socket.addEventListener('close', () => { if (!finished) finish(new Error('Inspector disconnected')); });
|
|
132
|
+
socket.addEventListener('open', () => socket.send(JSON.stringify({ id: 1, method: 'Runtime.evaluate', params: {
|
|
133
|
+
expression: `(${controlHttpAdmission.toString()})(${JSON.stringify(options)})`, returnByValue: true,
|
|
134
|
+
} })));
|
|
135
|
+
socket.addEventListener('message', ({ data }) => {
|
|
136
|
+
try {
|
|
137
|
+
const message = JSON.parse(String(data));
|
|
138
|
+
if (message.id !== 1) return;
|
|
139
|
+
if (message.error || message.result?.exceptionDetails) return finish(new Error('Target rejected drain operation: ' + JSON.stringify(message.error ?? message.result.exceptionDetails)));
|
|
140
|
+
const value = message.result?.result?.value;
|
|
141
|
+
if (!value || value.pid !== options.expectedPid || value.nonce !== options.nonce) return finish(new Error('Invalid target drain receipt'));
|
|
142
|
+
finish(undefined, value);
|
|
143
|
+
} catch (error) { finish(error); }
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
149
|
+
const options = JSON.parse(process.argv[2] ?? '{}');
|
|
150
|
+
try { console.log(JSON.stringify(await inspectorControl(options))); }
|
|
151
|
+
catch (error) { console.error(error.message); process.exitCode = 1; }
|
|
152
|
+
}
|