openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
891 lines (890 loc) • 35 kB
JavaScript
import { a as addTimerTimeoutGraceMs } from "./number-coercion-CLj0HTDM.js";
import { s as sleepWithAbort } from "./src-BQ327IOM.js";
import { t as createSubsystemLogger } from "./subsystem-Dy2tqXOS.js";
import { t as createDeferredCore } from "./deferred-D0La5CRk.js";
import { a as formatNodeRunnerUpdateRequired, n as NODE_RUNNER_UPDATE_REQUIRED_ISSUE } from "./node-runner-inventory-CsPbvshW.js";
import { r as runCommandWithTimeout } from "./exec-BIE-3oLG.js";
import { D as NODE_WORKER_WORKSPACE_EXEC_COMMAND, x as NODE_WORKER_ENVIRONMENT_STOP_COMMAND } from "./node-commands-BC8PhxqU.js";
import "./backoff-BkMI1WEL.js";
import { t as sameWorkerBuild } from "./worker-build-identity-D_c48Wx_.js";
import { a as parseNodeWorkerWorkspaceExecResult } from "./node-workspace-protocol-BIrX07V5.js";
import { r as NodeWorkerWorkspaceTransferError } from "./node-workspace-transfer-protocol-DhJOhHIM.js";
import { T as serializeWorkerWorkspaceManifest, _ as withWorkspaceHashMemo, l as pruneWorkspaceHashMemo, s as measureLocalWorkspaceReconciliation } from "./workspace-actual-manifest-BGixPzCq.js";
import { t as boundedWorkerError } from "./worker-error-BQ2GkQ1M.js";
import { a as recoverWorkerWorkspaceReconciliation, s as assertWorkspaceResultStable, t as applyStagedWorkerWorkspace } from "./workspace-reconcile-U3HlbfL4.js";
import { d as workerWorkspaceResultStaging } from "./workspace-result-staging-CwYxWnxD.js";
import { n as REMOTE_WORKSPACE_MANIFEST_JS } from "./workspace-sync-scripts-Dyx_KHcJ.js";
import { n as measureNodeWorkerLaunchBytes, r as raceNodeWorkerOperation } from "./node-launch-adapter-CYClo3Jk.js";
import { i as joinWorkerTunnelStops, r as WorkerTunnelOwnerDisconnectedError } from "./tunnel-contract-CB5bcNwX.js";
import { f as validateWorkspaceSyncRequest, l as resolveWorkerWorkspaceGitAuthor, y as workspaceSyncError } from "./workspace-sync-helpers-DLkfl3bZ.js";
import { r as workerProjectSeedKey } from "./workspace-git-base-qAMhycVO.js";
import { t as nodeWorkerGatewayNamespace } from "./node-worker-gateway-namespace-kRiA3LFq.js";
import { n as runInstrumentedWorkspaceReconcile } from "./workspace-finalize-B4kcMRD7.js";
import { t as createWorkerWorkspaceQuiescence } from "./workspace-quiescence-DqfH4Upm.js";
import path from "node:path";
import fs from "node:fs/promises";
import os from "node:os";
import { createHash } from "node:crypto";
//#region src/gateway/worker-environments/node-worker-workspace-fallback.ts
const GIT_TIMEOUT_MS = 6e4;
const COMMIT_PATTERN = /^[a-f0-9]{40}(?:[a-f0-9]{24})?$/u;
const MANIFEST_REF_PATTERN = /^sha256:[a-f0-9]{64}$/u;
const GIT_NONINTERACTIVE_ARGS = [
"-c",
"credential.helper=",
"-c",
"core.askPass="
];
const workspaceSyncLog = createSubsystemLogger("gateway/worker-workspace");
function recordNodeSyncPath(environmentId, sessionId, outcome, originStartedAt) {
workspaceSyncLog.info("worker workspace sync path selected", {
environmentId,
sessionId,
path: outcome.kind === "synced" ? "origin" : "gateway-push",
reason: outcome.kind === "synced" ? outcome.seeded ? "published-origin-seeded" : "published-origin" : outcome.reason,
originAttemptMs: performance.now() - originStartedAt
});
}
async function localGit(root, args) {
const result = await runCommandWithTimeout([
"git",
"-c",
`core.hooksPath=${os.devNull}`,
"-c",
"core.fsmonitor=false",
...GIT_NONINTERACTIVE_ARGS,
"-C",
root,
...args
], {
timeoutMs: GIT_TIMEOUT_MS,
maxOutputBytes: 262144,
maxCombinedOutputBytes: 524288,
outputCapture: "head",
baseEnv: {
...process.env,
GIT_TERMINAL_PROMPT: "0",
GIT_ASKPASS: "",
SSH_ASKPASS: ""
}
});
if (result.termination !== "exit" || result.code !== 0) throw new Error("local Git inspection failed");
return result.stdout.trim();
}
function credentialFreeHttpOrigin(raw) {
let parsed;
try {
parsed = new URL(raw);
} catch {
return;
}
return (parsed.protocol === "http:" || parsed.protocol === "https:") && parsed.username === "" && parsed.password === "" && parsed.search === "" && parsed.hash === "" ? parsed.href : void 0;
}
async function requiresWorkspaceTransfer(root) {
for (const marker of [".worktreeinclude", ".gitmodules"]) try {
await fs.lstat(path.join(root, marker));
return true;
} catch (error) {
if (error.code !== "ENOENT") throw error;
}
try {
return /\bfilter\s*=\s*lfs\b/u.test(await fs.readFile(path.join(root, ".gitattributes"), "utf8"));
} catch (error) {
if (error.code !== "ENOENT") throw error;
return false;
}
}
async function inspectEligibleOrigin(localPath) {
try {
const canonicalPath = await fs.realpath(localPath);
let root;
try {
root = await fs.realpath(await localGit(canonicalPath, ["rev-parse", "--show-toplevel"]));
} catch {
return {
kind: "fallback",
reason: "not-git-workspace"
};
}
if (root !== canonicalPath) return {
kind: "fallback",
reason: "not-repository-root"
};
if (await requiresWorkspaceTransfer(root)) return {
kind: "fallback",
reason: "workspace-transfer-required"
};
const [status, commit, rawOrigin] = await Promise.all([
localGit(root, [
"status",
"--porcelain=v1",
"--untracked-files=all"
]),
localGit(root, ["rev-parse", "HEAD"]),
localGit(root, [
"remote",
"get-url",
"origin"
]).catch(() => "")
]);
if (status) return {
kind: "fallback",
reason: "workspace-dirty"
};
const origin = credentialFreeHttpOrigin(rawOrigin);
if (!COMMIT_PATTERN.test(commit) || !origin) return {
kind: "fallback",
reason: "origin-unavailable"
};
return {
kind: "eligible",
identity: {
commit,
origin,
root
}
};
} catch {
return {
kind: "fallback",
reason: "inspection-failed"
};
}
}
function succeeded(result) {
return result.termination === "exit" && result.code === 0;
}
/** Optional published-origin fast path; HTTPS transfer remains the canonical fallback. */
function createNodeWorkerWorkspaceFallback(exec) {
let supportsSeeds = true;
let seedStoreFailureLogged = false;
const capture = async (dir, base, reference) => await exec({
argv: [
"node",
"-e",
REMOTE_WORKSPACE_MANIFEST_JS,
dir,
...base ? [base, "eligible"] : ["", "all"],
...reference ? [reference.slice(7)] : []
],
timeoutMs: GIT_TIMEOUT_MS,
transportRetry: "idempotent"
});
const checkoutAndCapture = async (identity, workspaceDir, expectedManifestRef, seeded) => {
const checkedOut = await exec({
argv: [
"git",
...GIT_NONINTERACTIVE_ARGS,
"checkout",
"--detach",
"--force",
identity.commit
],
timeoutMs: GIT_TIMEOUT_MS,
transportRetry: "never"
});
if (!succeeded(checkedOut) || checkedOut.workspaceDir !== workspaceDir) return {
kind: "fallback",
reason: "checkout-failed"
};
const captured = await capture(checkedOut.workspaceDir, identity.commit);
const manifestRef = captured.stdout.trim();
if (!succeeded(captured) || !MANIFEST_REF_PATTERN.test(manifestRef)) return {
kind: "fallback",
reason: "manifest-capture-failed"
};
if (manifestRef !== expectedManifestRef) return {
kind: "fallback",
reason: "manifest-mismatch"
};
return {
kind: "synced",
seeded,
result: {
mode: "git",
remoteWorkspaceDir: checkedOut.workspaceDir,
manifestRef
}
};
};
return {
async captureManifest(dir, base, reference) {
const captured = await capture(dir, base, reference);
const manifestRef = captured.stdout.trim();
if (!succeeded(captured) || !MANIFEST_REF_PATTERN.test(manifestRef)) {
const detail = boundedWorkerError(captured.stderr.trim() || (!succeeded(captured) ? `${captured.termination} (exit code ${captured.code}, signal ${captured.signal})` : "invalid manifest reference"));
throw new Error(`Node workspace manifest capture failed: ${detail}`);
}
return manifestRef;
},
async trySyncWorkspace(request, expectedManifestRef) {
validateWorkspaceSyncRequest(request);
const inspection = await inspectEligibleOrigin(request.localPath);
if (inspection.kind === "fallback") return inspection;
const { identity } = inspection;
const seedKey = createHash("sha256").update(identity.origin).digest("hex");
let outcome;
if (supportsSeeds) try {
const applied = await exec({
argv: ["openclaw-internal-workspace-seed"],
seed: {
action: "apply",
key: seedKey
},
timeoutMs: GIT_TIMEOUT_MS,
transportRetry: "never"
});
if (succeeded(applied) && applied.stdout.trim() === "applied") {
const remote = await exec({
argv: [
"git",
...GIT_NONINTERACTIVE_ARGS,
"remote",
"get-url",
"origin"
],
timeoutMs: GIT_TIMEOUT_MS,
transportRetry: "never"
});
if (!succeeded(remote) || remote.stdout.trim() !== identity.origin) throw new Error("Node workspace seed origin mismatch");
const fetched = await exec({
argv: [
"git",
...GIT_NONINTERACTIVE_ARGS,
"fetch",
"--prune",
"origin",
"+refs/heads/*:refs/remotes/origin/*"
],
timeoutMs: GIT_TIMEOUT_MS,
transportRetry: "never"
});
if (!succeeded(fetched)) throw workspaceSyncError(fetched);
outcome = await checkoutAndCapture(identity, applied.workspaceDir, expectedManifestRef, true);
}
} catch (error) {
if (error instanceof Error && error.message.includes("INVALID_REQUEST")) supportsSeeds = false;
else workspaceSyncLog.info("node worker workspace seeded sync failed; cloning", { error: boundedWorkerError(error) });
}
if (outcome?.kind !== "synced") {
const cloned = await exec({
argv: [
"git",
...GIT_NONINTERACTIVE_ARGS,
"-c",
"init.templateDir=",
"clone",
"--filter=blob:none",
"--no-checkout",
"--",
identity.origin,
"."
],
resetWorkspace: true,
timeoutMs: GIT_TIMEOUT_MS,
transportRetry: "never"
});
if (!succeeded(cloned)) return {
kind: "fallback",
reason: "clone-failed"
};
outcome = await checkoutAndCapture(identity, cloned.workspaceDir, expectedManifestRef, false);
}
if (outcome.kind === "synced" && supportsSeeds) try {
const stored = await exec({
argv: ["openclaw-internal-workspace-seed"],
seed: {
action: "store",
key: seedKey,
maxAgeMs: 216e5
},
timeoutMs: 18e4,
transportRetry: "never"
});
if (!succeeded(stored)) throw workspaceSyncError(stored);
} catch (error) {
if (error instanceof Error && error.message.includes("INVALID_REQUEST")) supportsSeeds = false;
if (!seedStoreFailureLogged) {
seedStoreFailureLogged = true;
workspaceSyncLog.warn("node worker workspace seed store failed", { error: boundedWorkerError(error) });
}
}
return outcome;
},
async finalizeSync(request, result) {
if (result.mode === "plain") return result;
const author = await resolveWorkerWorkspaceGitAuthor(request, async (argv) => runCommandWithTimeout(argv, {
timeoutMs: GIT_TIMEOUT_MS,
maxOutputBytes: 1024
}));
const git = [
"git",
"-C",
result.remoteWorkspaceDir,
"config",
"--local"
];
for (const [key, value] of Object.entries(author)) {
if (!value) continue;
const configured = await exec({
argv: [
...git,
`user.${key}`,
value
],
transportRetry: "never"
});
if (!succeeded(configured)) throw workspaceSyncError(configured);
}
return result;
}
};
}
//#endregion
//#region src/gateway/worker-environments/node-worker-workspace-actions.ts
function createNodeWorkerWorkspaceActions(params) {
const { restoredWorkspace } = params;
let workspaceReady = restoredWorkspace !== void 0;
const exec = async (command) => {
if (!workspaceReady) throw new Error("node worker workspace is unavailable before sync");
return await params.runWorkspaceCommand(command);
};
const workspace = createNodeWorkerWorkspaceFallback(exec);
const quiesceWorkspace = createWorkerWorkspaceQuiescence({
ownerSignal: params.ownerSignal,
sharedHost: true,
runWorkspaceCommand: exec
});
const validateRestoredWorkspace = async () => {
if (!restoredWorkspace) return;
const prepared = await params.workspaceTransfer.prepareSync({
environmentId: params.environmentId,
ownerEpoch: params.ownerEpoch,
sessionId: params.sessionId,
generation: params.ownerEpoch,
localPath: restoredWorkspace.localPath,
isAuthorized: params.isOwnerCurrent,
signal: params.ownerSignal
});
params.workspaceTransfer.revoke(params.environmentId, prepared.token);
};
const placementHashMemo = /* @__PURE__ */ new Map();
const reconcileWorkspace = (request) => runInstrumentedWorkspaceReconcile((metrics) => reconcileWorkspaceRun(request, metrics));
const reconcileWorkspaceRun = async (request, metrics) => {
pruneWorkspaceHashMemo(placementHashMemo);
const runLocalReconciliation = (operation) => measureLocalWorkspaceReconciliation(metrics, () => withWorkspaceHashMemo(placementHashMemo, operation, metrics.gateway));
const pending = request.journal.load();
if (pending) {
await recoverWorkerWorkspaceReconciliation({
root: request.localPath,
journal: pending
});
request.journal.abort();
}
const uploadToken = params.workspaceTransfer.prepareUpload(params.environmentId, request.baseManifestRef);
let uploadedResult;
try {
uploadedResult = await exec({
argv: ["openclaw-internal-workspace-transfer"],
transfer: {
direction: "upload",
token: uploadToken,
baseManifestRef: request.baseManifestRef
},
timeoutMs: 6e5,
transportRetry: "never"
});
} finally {
params.workspaceTransfer.revoke(params.environmentId, uploadToken);
}
if (uploadedResult.termination !== "exit" || uploadedResult.code !== 0) throw new Error("Node workspace reconcile upload failed");
const uploaded = params.workspaceTransfer.takeUpload(params.environmentId, request.baseManifestRef);
try {
const changed = uploaded.currentManifestRef !== request.baseManifestRef;
let expectedRemoteRef = uploaded.currentManifestRef;
const verifyStable = async () => {
if (await workspace.captureManifest(request.remoteWorkspaceDir, uploaded.base.baseCommit, expectedRemoteRef) !== expectedRemoteRef) throw new Error("Cloud workspace changed during final reconciliation");
};
await verifyStable();
const publishAcceptedManifest = async (accepted) => {
if (accepted.manifestRef === expectedRemoteRef) return;
const token = params.workspaceTransfer.publishSnapshot(params.environmentId, {
manifest: accepted.manifest,
manifestRef: accepted.manifestRef,
rawManifest: serializeWorkerWorkspaceManifest(accepted.manifest),
root: await fs.realpath(request.localPath)
});
try {
const published = await exec({
argv: ["openclaw-internal-workspace-transfer"],
transfer: {
direction: "download",
token,
manifestRef: accepted.manifestRef
},
timeoutMs: 6e5,
transportRetry: "never"
});
if (published.termination !== "exit" || published.code !== 0 || published.stdout.trim() !== accepted.manifestRef) throw new Error("Node workspace accepted manifest publication failed");
expectedRemoteRef = accepted.manifestRef;
} finally {
params.workspaceTransfer.revoke(params.environmentId, token);
}
};
const preparedStagedResult = request.stagedResult ? await runLocalReconciliation(async () => await workerWorkspaceResultStaging.prepareRequestedWorkerWorkspaceResult({
request,
stagingRoot: uploaded.stagingRoot,
currentManifestRef: uploaded.currentManifestRef,
baseManifestRaw: uploaded.baseRaw,
currentManifestRaw: uploaded.currentRaw,
publishAcceptedManifest
})) : void 0;
let appliedWorkspaceResult;
if (!preparedStagedResult) appliedWorkspaceResult = await runLocalReconciliation(async () => await applyStagedWorkerWorkspace({
root: request.localPath,
stagingRoot: uploaded.stagingRoot,
baseManifestRef: request.baseManifestRef,
currentManifestRef: uploaded.currentManifestRef,
base: uploaded.base,
current: uploaded.current,
journal: request.journal,
publishAcceptedManifest
}));
return {
get manifestRef() {
return expectedRemoteRef;
},
changed,
verifyStable,
verifyLocalStable: async () => await runLocalReconciliation(async () => await (appliedWorkspaceResult?.verifyLocalStable() ?? assertWorkspaceResultStable({
root: request.localPath,
base: uploaded.base,
current: uploaded.current
}))),
getAppliedWorkspaceResult: () => appliedWorkspaceResult,
...preparedStagedResult ? {
...preparedStagedResult,
applyPreparedStagedResult: async () => {
await runLocalReconciliation(async () => await preparedStagedResult.applyPreparedStagedResult());
appliedWorkspaceResult = preparedStagedResult.getAppliedWorkspaceResult();
}
} : {}
};
} finally {
await fs.rm(uploaded.stagingRoot, {
recursive: true,
force: true
});
}
};
return {
validateRestoredWorkspace,
runWorkspaceCommand: exec,
stageAttachments: async (request) => {
const prepared = await params.workspaceTransfer.prepareAttachments({
...request,
environmentId: params.environmentId
});
try {
const result = await exec({
argv: ["openclaw-internal-workspace-transfer"],
transfer: {
direction: "download",
token: prepared.token,
manifestRef: prepared.snapshot.manifestRef,
attachments: true
},
transportRetry: "never",
assertCurrent: () => {
if (!request.isAuthorized()) throw new Error("Worker attachment transfer authority closed");
},
signal: request.signal
});
if (result.termination !== "exit" || result.code !== 0 || result.stdout.trim() !== prepared.snapshot.manifestRef) throw new Error("Worker attachment transfer failed");
} finally {
params.workspaceTransfer.revoke(params.environmentId, prepared.token);
}
},
syncWorkspace: async (request) => {
workspaceReady = true;
try {
const prepared = await params.workspaceTransfer.prepareSync({
environmentId: params.environmentId,
ownerEpoch: params.ownerEpoch,
sessionId: params.sessionId,
generation: params.ownerEpoch,
localPath: request.localPath,
isAuthorized: params.isOwnerCurrent,
signal: params.ownerSignal
});
try {
if (!request.projectKey) {
const originStartedAt = performance.now();
const origin = await workspace.trySyncWorkspace(request, prepared.snapshot.manifestRef);
recordNodeSyncPath(params.environmentId, params.sessionId, origin, originStartedAt);
if (origin.kind === "synced") return await workspace.finalizeSync(request, origin.result);
}
const transferred = await exec({
argv: ["openclaw-internal-workspace-transfer"],
transfer: {
direction: "download",
token: prepared.token,
manifestRef: prepared.snapshot.manifestRef,
...request.projectKey && prepared.snapshot.manifest.baseCommit ? { seedKey: workerProjectSeedKey({
key: request.projectKey,
baseCommit: prepared.snapshot.manifest.baseCommit
}) } : {}
},
timeoutMs: 6e5,
transportRetry: "never"
});
if (transferred.termination !== "exit" || transferred.code !== 0 || transferred.stdout.trim() !== prepared.snapshot.manifestRef) throw new Error("Node workspace transfer failed");
return await workspace.finalizeSync(request, {
mode: prepared.snapshot.manifest.baseCommit ? "git" : "plain",
remoteWorkspaceDir: transferred.workspaceDir,
manifestRef: prepared.snapshot.manifestRef
});
} finally {
params.workspaceTransfer.revoke(params.environmentId, prepared.token);
}
} catch (error) {
workspaceReady = restoredWorkspace !== void 0;
throw error;
}
},
quiesceWorkspace,
reconcileWorkspace
};
}
//#endregion
//#region src/gateway/worker-environments/node-worker-tunnel.ts
const DEFAULT_COMMAND_TIMEOUT_MS = 6e4;
const COMMAND_RESULT_GRACE_MS = 5e3;
const RETRY_DELAY_MS = 100;
const tunnelLog = createSubsystemLogger("gateway/worker-tunnel");
const RETRYABLE_TRANSPORT_CODES = /* @__PURE__ */ new Set([
"DISCONNECTED",
"NOT_CONNECTED",
"PAIRING_CHANGED",
"PRIVATE_DIALECT_UNAVAILABLE",
"ROUTE_CHANGED",
"TIMEOUT",
"UNAVAILABLE"
]);
function spawnResultFromReceipt(receipt) {
if (receipt.state === "completed") return {
stdout: receipt.resultJson,
stderr: "",
code: 0,
signal: null,
killed: false,
termination: "exit"
};
if (receipt.state === "failed" || receipt.state === "interrupted" || receipt.state === "cancelled") return {
stdout: "",
stderr: receipt.errorText,
code: 1,
signal: null,
killed: receipt.state === "cancelled" || receipt.state === "interrupted",
termination: "exit"
};
throw new Error("node worker launch returned without a terminal receipt");
}
function payloadJson(value) {
if (!value) throw new Error("node workspace command omitted its result");
try {
return JSON.parse(value);
} catch {
throw new Error("node workspace command returned malformed JSON");
}
}
/** Owns node-channel handles without treating the persistent machine as a disposable lease. */
function createNodeWorkerTunnelManager(options) {
const entries = /* @__PURE__ */ new Map();
const retiredEntries = /* @__PURE__ */ new Set();
let resolveWorkspaceBinding;
const gatewayNamespace = nodeWorkerGatewayNamespace(options.gatewayDeviceId);
const hasDurableBinding = (entry) => {
const current = options.getEnvironment(entry.environmentId);
return Boolean(current && current.ownerEpoch === entry.ownerEpoch && current.bootstrapReceipt?.installKind === "bundle" && sameWorkerBuild(current.bootstrapReceipt, entry.expectedBuild) && current.attachedSessionIds.length <= 1 && (current.attachedSessionIds.length === 0 || current.attachedSessionIds[0] === entry.sessionId));
};
const isLiveEntry = (entry) => entries.get(entry.environmentId) === entry && !entry.abortController.signal.aborted;
const isEnvironmentOwner = (entry) => hasDurableBinding(entry) && isLiveEntry(entry);
const findNode = async (entry, signal) => {
const transport = options.getTransport();
if (!transport) throw new Error("device worker node transport is unavailable");
const node = (await raceNodeWorkerOperation(transport.listCurrentNodes(), signal)).find((candidate) => candidate.nodeId === entry.deviceId);
if (!node) throw new WorkerTunnelOwnerDisconnectedError("device worker node is not connected with the supervisor dialect");
return {
transport,
node
};
};
const runWorkspaceCommand = async (entry, command) => {
const assertCurrent = () => {
if (!isEnvironmentOwner(entry)) throw new Error("node worker workspace authority closed");
command.assertCurrent?.();
};
const commandTimeoutMs = command.timeoutMs ?? DEFAULT_COMMAND_TIMEOUT_MS;
const transportTimeoutMs = addTimerTimeoutGraceMs(commandTimeoutMs, COMMAND_RESULT_GRACE_MS) ?? commandTimeoutMs;
const deadline = Date.now() + transportTimeoutMs;
const signals = [entry.abortController.signal, AbortSignal.timeout(transportTimeoutMs)];
if (command.signal) signals.push(command.signal);
const signal = AbortSignal.any(signals);
const input = {
gatewayNamespace,
environmentId: entry.environmentId,
sessionId: entry.sessionId,
generation: entry.ownerEpoch,
argv: [...command.argv],
...command.input === void 0 ? {} : { input: command.input },
timeoutMs: commandTimeoutMs,
...command.resetWorkspace === void 0 ? {} : { resetWorkspace: command.resetWorkspace },
...command.transfer === void 0 ? {} : { transfer: command.transfer },
...command.seed === void 0 ? {} : { seed: command.seed }
};
while (true) {
assertCurrent();
const remainingMs = deadline - Date.now();
if (remainingMs <= 0 || signal.aborted) throw signal.reason ?? /* @__PURE__ */ new Error("node worker workspace command timed out");
let result;
try {
const { node, transport } = await findNode(entry, signal);
assertCurrent();
result = await transport.invoke({
node,
command: NODE_WORKER_WORKSPACE_EXEC_COMMAND,
params: input,
timeoutMs: remainingMs,
signal,
isDispatchAuthorized: () => {
assertCurrent();
return true;
}
});
} catch (error) {
assertCurrent();
if (command.transportRetry !== "idempotent" || signal.aborted || !isEnvironmentOwner(entry)) throw error;
await sleepWithAbort(Math.min(RETRY_DELAY_MS, Math.max(1, deadline - Date.now())), signal);
continue;
}
if (!result.ok) {
const code = result.error?.code ?? "UNAVAILABLE";
if (code === "WORKSPACE_TRANSFER_FAILED") throw new NodeWorkerWorkspaceTransferError(result.error?.message ?? "workspace-transfer-failed: transfer did not complete");
if (command.transportRetry === "idempotent" && RETRYABLE_TRANSPORT_CODES.has(code)) {
await sleepWithAbort(Math.min(RETRY_DELAY_MS, remainingMs), signal);
continue;
}
throw new Error(result.error?.message && code === "INVALID_REQUEST" ? `node workspace command failed (${code}): ${result.error.message}` : `node workspace command failed (${code})`);
}
const parsed = parseNodeWorkerWorkspaceExecResult(payloadJson(result.payloadJSON));
if (!parsed) throw new Error("node workspace command violated its private result contract");
return parsed;
}
};
const createHandle = (entry, restoredWorkspace) => {
const buildLaunchInput = (plan, claim) => ({
environmentSession: 1,
launchId: plan.assignment.turnId,
gatewayNamespace,
expectedBundleHash: entry.expectedBuild.bundleHash,
placementGeneration: claim.placementGeneration,
descriptor: plan
});
const { validateRestoredWorkspace, ...workspaceActions } = createNodeWorkerWorkspaceActions({
environmentId: entry.environmentId,
ownerEpoch: entry.ownerEpoch,
sessionId: entry.sessionId,
ownerSignal: entry.abortController.signal,
isOwnerCurrent: () => isLiveEntry(entry),
restoredWorkspace,
workspaceTransfer: options.workspaceTransfer,
runWorkspaceCommand: (command) => runWorkspaceCommand(entry, command)
});
return {
handle: {
...workspaceActions,
environmentId: entry.environmentId,
ownerEpoch: entry.ownerEpoch,
measureLaunchTurn: (plan, claim) => measureNodeWorkerLaunchBytes(entry.deviceId, buildLaunchInput(plan, claim)),
launchTurn: async (request) => {
if (entry.executionMode !== "worker-turn") throw new Error("remote-exec environments do not launch embedded worker turns");
const plan = request.plan;
const claim = request.turnClaim;
const isDispatchAuthorized = () => isEnvironmentOwner(entry) && claim.owner.kind === "worker" && claim.owner.environmentId === entry.environmentId && claim.owner.ownerEpoch === entry.ownerEpoch && claim.sessionId === plan.admission.sessionId && claim.runId === plan.assignment.runId && options.validateWorkerTurn(claim);
const operation = options.launchNodeWorker({
deviceId: entry.deviceId,
input: buildLaunchInput(plan, claim),
isDispatchAuthorized,
isCancellationAuthorized: () => hasDurableBinding(entry),
timeoutMs: request.timeoutMs ?? DEFAULT_COMMAND_TIMEOUT_MS,
...request.credentialExpiresAtMs === void 0 ? {} : { credentialExpiresAtMs: request.credentialExpiresAtMs },
onDispatchReady: request.onDispatchReady,
signal: request.signal ? AbortSignal.any([entry.abortController.signal, request.signal]) : entry.abortController.signal
});
entry.launchTasks.add(operation);
try {
return spawnResultFromReceipt(await operation);
} finally {
entry.launchTasks.delete(operation);
}
},
stop: async () => {
await stopEntry(entry);
}
},
validateRestoredWorkspace
};
};
function stopEntry(entry, reason) {
if (entries.get(entry.environmentId) === entry) entries.delete(entry.environmentId);
entry.abortController.abort(/* @__PURE__ */ new Error("node worker tunnel owner stopped"));
entry.readiness.reject(/* @__PURE__ */ new Error("node worker tunnel stopped before connecting"));
return stopEnvironmentOwner(entry, reason, async () => {
await entry.initialization?.catch(() => void 0);
await Promise.allSettled(entry.launchTasks);
});
}
function stopEnvironmentOwner(entry, reason, drain) {
if (entry.stopPromise) {
if (entry.stopReason === reason || !retiredEntries.has(entry)) return entry.stopPromise;
return entry.stopPromise.catch((error) => {
if (!reason) throw error;
}).then(() => retiredEntries.has(entry) ? stopEnvironmentOwner(entry, reason) : void 0);
}
retiredEntries.add(entry);
entry.stopReason = reason;
entry.stopPromise = (async () => {
await drain?.();
let stopping = true;
try {
if (entry.executionMode === "worker-turn" && reason === void 0) {
const signal = AbortSignal.timeout(DEFAULT_COMMAND_TIMEOUT_MS);
const { transport, node } = await findNode(entry, signal);
if (node.workerHost.environmentSession !== 1) throw new Error(formatNodeRunnerUpdateRequired(node.nodeId, NODE_RUNNER_UPDATE_REQUIRED_ISSUE));
const operation = transport.invoke({
node,
command: NODE_WORKER_ENVIRONMENT_STOP_COMMAND,
params: {
gatewayNamespace,
environmentId: entry.environmentId,
sessionId: entry.sessionId,
ownerEpoch: entry.ownerEpoch
},
timeoutMs: DEFAULT_COMMAND_TIMEOUT_MS,
signal,
isDispatchAuthorized: () => stopping && retiredEntries.has(entry)
});
const result = await raceNodeWorkerOperation(operation, signal);
if (!result.ok) {
const code = result.error?.code ?? "UNAVAILABLE";
const message = `node worker environment stop failed (${code})`;
throw RETRYABLE_TRANSPORT_CODES.has(code) ? new WorkerTunnelOwnerDisconnectedError(message) : new Error(message);
}
}
} finally {
stopping = false;
await options.workspaceTransfer.close(entry.environmentId);
}
if (reason !== "provider-destroying") retiredEntries.delete(entry);
})().finally(() => {
if (retiredEntries.has(entry)) entry.stopPromise = void 0;
});
return entry.stopPromise;
}
async function stop(environmentId, ownerEpoch, reason) {
const matches = (entry) => entry.environmentId === environmentId && (ownerEpoch === void 0 || ownerEpoch === entry.ownerEpoch);
const live = [...entries.values()].filter(matches);
const retired = [...retiredEntries].filter(matches);
const operations = [...live.map((entry) => stopEntry(entry, reason)), ...retired.map((entry) => stopEnvironmentOwner(entry, reason))];
if (operations.length === 0) {
const record = options.getEnvironment(environmentId);
if (record?.nodeDeviceId && (ownerEpoch === void 0 || record.ownerEpoch === ownerEpoch)) {
if (reason === "provider-destroying" || reason === "provider-destroyed") operations.push(options.workspaceTransfer.close(environmentId));
else {
if (record.attachedSessionIds.length > 1) throw new Error("node worker environment teardown has an ambiguous session owner");
const sessionId = record.attachedSessionIds[0];
if (sessionId) operations.push(stopEnvironmentOwner({
deviceId: record.nodeDeviceId,
environmentId,
ownerEpoch: record.ownerEpoch,
sessionId,
executionMode: record.profileSnapshot.executionMode === "remote-exec" ? "remote-exec" : "worker-turn"
}, reason));
}
}
}
await joinWorkerTunnelStops(operations);
}
return {
bindWorkspaceBindingResolver(resolver) {
resolveWorkspaceBinding = resolver;
},
async start(request) {
const current = entries.get(request.environmentId);
const retiring = [...retiredEntries].filter((entry) => entry.environmentId === request.environmentId);
if (retiring.some((entry) => entry.ownerEpoch > request.ownerEpoch)) throw new Error("node worker tunnel owner epoch is stale");
if (current) {
if (request.ownerEpoch < current.ownerEpoch) throw new Error("node worker tunnel owner epoch is stale");
if (request.ownerEpoch === current.ownerEpoch) {
if (current.abortController.signal.aborted || current.executionMode !== request.executionMode || current.deviceId !== request.deviceId || current.sessionId !== request.sessionId || !sameWorkerBuild(current.expectedBuild, request.expectedBuild)) throw new Error("node worker tunnel owner binding changed within one epoch");
return current.readiness.promise;
}
}
const readiness = createDeferredCore();
readiness.promise.catch(() => void 0);
const entry = {
...request,
abortController: new AbortController(),
launchTasks: /* @__PURE__ */ new Set(),
readiness
};
entries.set(entry.environmentId, entry);
entry.initialization = (async () => {
if (current) await stopEntry(current);
await Promise.all(retiring.map((owner) => stopEnvironmentOwner(owner)));
if (!isLiveEntry(entry)) return;
const restoredWorkspace = resolveWorkspaceBinding ? await raceNodeWorkerOperation(resolveWorkspaceBinding({
environmentId: request.environmentId,
ownerEpoch: request.ownerEpoch,
sessionId: request.sessionId
}), entry.abortController.signal) : void 0;
if (!isLiveEntry(entry)) return;
const created = createHandle(entry, restoredWorkspace);
await created.validateRestoredWorkspace();
if (!isLiveEntry(entry)) return;
entry.handle = created.handle;
readiness.resolve(created.handle);
})();
entry.initialization.catch((error) => {
readiness.reject(error);
stopEntry(entry).catch((cleanupError) => {
tunnelLog.warn("node worker tunnel cleanup failed after initialization error", {
environmentId: entry.environmentId,
ownerEpoch: entry.ownerEpoch,
error: boundedWorkerError(cleanupError)
});
});
});
return await readiness.promise;
},
stop,
async stopAll() {
const environmentIds = /* @__PURE__ */ new Set([
...entries.keys(),
...[...retiredEntries].map((entry) => entry.environmentId),
...options.listEnvironments().filter((record) => record.nodeDeviceId).map((record) => record.environmentId)
]);
const stopped = await Promise.allSettled([...environmentIds].map((environmentId) => stop(environmentId)));
stopped.push(...await Promise.allSettled([options.workspaceTransfer.closeAll()]));
const failure = stopped.find((result) => result.status === "rejected");
if (failure) throw failure.reason;
},
status(environmentId) {
const entry = entries.get(environmentId);
return entry && !entry.abortController.signal.aborted ? entry.handle ? "connected" : "connecting" : "stopped";
}
};
}
//#endregion
export { createNodeWorkerTunnelManager };