openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
840 lines (839 loc) • 46.2 kB
JavaScript
import { t as createDeferredCore } from "./deferred-D0La5CRk.mjs";
import { c as isRecord } from "./record-coerce-TIsquoVR.mjs";
import { n as resolvePreferredOpenClawTmpDir } from "./tmp-openclaw-dir-BSb3HetY.mjs";
import { Et as _enum, Fn as object, Jn as string, Pn as number, Yt as discriminatedUnion, xn as literal } from "./schemas-BOYIvvln.mjs";
import { c as sha256File } from "./directory-durability-B6uYEZLt.mjs";
import { t as _usingCtx } from "./usingCtx-DwOBYOJf.mjs";
import { r as normalizeAgentIdStrict } from "./agent-id-D1-Q6vR4.mjs";
import { t as MANAGED_GITHUB_PROFILE_ID_PATTERN } from "./github-identity-profile-id-BJzGq1wi.mjs";
import { t as normalizeCloudRepo } from "./cloud-worker-project-profiles-DJ5jtY5M.mjs";
import { t as FORCED_WORKER_ABANDONMENT_ERROR } from "./placement-record-BepxDhdT.mjs";
import { a as MAX_WORKSPACE_MANIFEST_BYTES, n as MAX_WORKSPACE_INVENTORY_ENTRIES } from "./workspace-inventory-limits-DfDQlGHa.mjs";
import { c as requireGit } from "./git-DOeuOnFt.mjs";
import { t as boundedWorkerError } from "./worker-error-Cm0SzFNq.mjs";
import { t as sameWorkerBuild } from "./worker-build-identity-D_c48Wx_.mjs";
import { n as REMOTE_WORKSPACE_MANIFEST_JS } from "./workspace-sync-scripts-DgxLmW4q.mjs";
import { n as WORKSPACE_SEED_RETENTION_JS, t as WORKSPACE_SEED_RETENTION } from "./workspace-seed-retention-CLuCf_ZX.mjs";
import { i as workerProjectSeedKey, n as prepareWorkerWorkspaceGitPack } from "./workspace-git-base-DqTs5T65.mjs";
import { t as parseProjectGitUrl } from "./project-git-url-D2byNTeD.mjs";
import path from "node:path";
import fs from "node:fs/promises";
import { createHash } from "node:crypto";
//#region src/gateway/worker-environments/service-contract.ts
function deriveEnvironmentIntent(idempotencyKey) {
const digest = createHash("sha256").update(idempotencyKey).digest("hex");
return {
environmentId: `worker:${digest.slice(0, 32)}`,
provisionOperationId: `provision:v2:${digest}`
};
}
/** Canonical admission rejected the session owner, not a caller or process cancellation. */
var WorkerPlacementAdmissionTargetError = class extends Error {
constructor(..._args) {
super(..._args);
this.code = "invalid_state";
}
};
//#endregion
//#region src/gateway/worker-environments/environment-errors.ts
const FORCED_ABANDONMENT_DIAGNOSTIC_PREFIX = `${FORCED_WORKER_ABANDONMENT_ERROR}; `;
function hasForcedWorkerEnvironmentAbandonment(record) {
return record.destroyRequestedAtMs !== null && (record.lastError === "Worker result abandoned by forced operator teardown" || record.lastError?.startsWith(FORCED_ABANDONMENT_DIAGNOSTIC_PREFIX) === true);
}
function createWorkerEnvironmentErrorRecorder(store) {
return async (record, error, assertCurrent) => {
assertCurrent?.();
if (record.teardownTerminalState === "failed" && record.lastError) return record;
return store.recordError({
environmentId: record.environmentId,
state: record.state,
error: hasForcedWorkerEnvironmentAbandonment(record) ? `${FORCED_ABANDONMENT_DIAGNOSTIC_PREFIX}${boundedWorkerError(error, 1024 - FORCED_ABANDONMENT_DIAGNOSTIC_PREFIX.length)}` : boundedWorkerError(error),
assertCurrent
});
};
}
//#endregion
//#region src/gateway/worker-environments/provider-runtime-refresh.ts
var WorkerRuntimeRefreshPendingError = class extends Error {
constructor(detail) {
super(`Cloud worker runtime update is pending; recovery will retry when the worker is available: ${detail}`);
this.code = "invalid_state";
}
};
function createWorkerRuntimeRefresher(options) {
const { store, callBootstrap, serviceError, requireCurrentOwner, stopOwner, identityResolverFor } = options;
const { ensurePendingCredential } = options.credentialBroker;
const inFlight = /* @__PURE__ */ new Map();
const refresh = async (record, provider, installation, signal) => {
if (!installation || record.bootstrapReceipt && sameWorkerBuild(record.bootstrapReceipt, installation)) return;
if (record.state !== "attached" && record.state !== "ready" && record.state !== "idle" || !record.bootstrapReceipt || !record.leaseId) throw serviceError("invalid_state", "Worker runtime refresh requires an admitted lease");
const settled = createDeferredCore();
const listeners = /* @__PURE__ */ new Set();
const fact = {
settled: settled.promise,
onProgress(listener) {
listeners.add(listener);
return () => {
listeners.delete(listener);
};
}
};
const reportProgress = () => {
for (const listener of listeners) try {
listener();
} catch {}
};
inFlight.set(record.environmentId, fact);
try {
const sessionId = record.state === "attached" ? record.attachedSessionIds[0] : void 0;
let placementAuthority;
if (record.state === "attached") {
if (!sessionId || !options.placementStore) throw serviceError("invalid_state", "Worker runtime refresh requires its placement");
placementAuthority = await options.placementStore.prepareWorkerRuntimeRefresh({
sessionId,
environmentId: record.environmentId,
ownerEpoch: record.ownerEpoch
});
}
const assertCurrent = () => {
signal?.throwIfAborted();
const current = requireCurrentOwner(record);
if (options.isStopping() || current.destroyRequestedAtMs !== null) throw serviceError("invalid_state", "Worker runtime refresh owner is stopping");
placementAuthority?.assertCurrent();
};
try {
assertCurrent();
await stopOwner(record, void 0, { assertCurrent });
assertCurrent();
const receipt = await callBootstrap(installation, async (timeoutSignal) => {
const refreshSignal = signal ? AbortSignal.any([signal, timeoutSignal]) : timeoutSignal;
assertCurrent();
if (record.nodeDeviceId) {
if (installation.install !== "bundle" || !options.ensureNodeWorkerBundle) throw new Error("Worker node bundle installer is unavailable");
return options.ensureNodeWorkerBundle({
reason: "refresh",
environmentId: record.environmentId,
deviceId: record.nodeDeviceId,
artifact: installation,
prewarm: record.profileSnapshot.executionMode !== "remote-exec",
signal: refreshSignal,
assertCurrent,
onProgress: reportProgress
});
}
if (!record.sshEndpoint) throw new Error("Worker runtime refresh has no transport");
return options.bootstrapWorker({
operationId: record.provisionOperationId,
sshEndpoint: record.sshEndpoint,
installation,
resolveIdentity: identityResolverFor(record, provider, record.leaseId),
signal: refreshSignal,
assertCurrent
});
});
assertCurrent();
if (!sameWorkerBuild(receipt, installation)) throw new Error("Worker runtime refresh returned a mismatched build receipt");
const refreshed = await store.refreshBootstrapReceipt({
environmentId: record.environmentId,
...record.state === "attached" ? {
expectedState: record.state,
expectedPlacementGeneration: placementAuthority.generation,
expectedReclaimResult: placementAuthority.reclaimResult
} : { expectedState: record.state },
expectedOwnerEpoch: record.ownerEpoch,
expectedNodeDeviceId: record.nodeDeviceId,
expectedBootstrapReceipt: record.bootstrapReceipt,
bootstrapReceipt: {
...receipt,
installKind: "bundle"
},
assertCurrent
});
assertCurrent();
await ensurePendingCredential(refreshed, sessionId ?? null);
} finally {
placementAuthority?.release();
}
} finally {
if (inFlight.get(record.environmentId) === fact) inFlight.delete(record.environmentId);
settled.resolve();
}
};
return {
refresh,
read: (environmentId) => inFlight.get(environmentId)
};
}
//#endregion
//#region src/gateway/worker-environments/project-setup-script.ts
const PREPARE_PROJECT_WORKSPACE_JS = `async (input, inspectOnly = false) => {
const startedAt = performance.now();
const fs = require("node:fs");
const path = require("node:path");
const os = require("node:os");
const crypto = require("node:crypto");
const { spawn, spawnSync } = require("node:child_process");
const manifestScript = ${JSON.stringify(REMOTE_WORKSPACE_MANIFEST_JS)};
process.umask(0o077);
const machineHome = fs.realpathSync(os.homedir());
const env = { PATH: process.env.PATH, HOME: machineHome, LANG: "C.UTF-8", GIT_CONFIG_NOSYSTEM: "1", GIT_CONFIG_GLOBAL: os.devNull, GIT_CONFIG_COUNT: "2", GIT_CONFIG_KEY_0: "core.hooksPath", GIT_CONFIG_VALUE_0: os.devNull, GIT_CONFIG_KEY_1: "core.fsmonitor", GIT_CONFIG_VALUE_1: "false", GIT_TERMINAL_PROMPT: "0", GIT_ASKPASS: "", SSH_ASKPASS: "" };
const ownedDirectory = (parent, name, create = false) => {
const target = path.join(parent, name);
if (create && !fs.existsSync(target)) fs.mkdirSync(target, { mode: 0o700 });
const stat = fs.lstatSync(target);
if (stat.isSymbolicLink() || !stat.isDirectory() || fs.realpathSync(target) !== target) throw new Error("Prepared project directory escaped its owner");
return target;
};
const git = (root, args) => {
// Full object verification on cold snapshot storage needs the same budget as seed verification.
const timeout = 600000;
const maxBuffer = 262144;
const result = spawnSync("git", ["-C", root, ...args], { env, encoding: "utf8", timeout, maxBuffer });
if (result.error || result.status !== 0) {
const reason = result.error?.code === "ETIMEDOUT" ? "timed out after " + timeout + " ms"
: result.error?.code === "ENOBUFS" ? "output exceeded " + maxBuffer + " bytes"
: result.error?.code ?? (result.signal ? "signal " + result.signal : "exit " + result.status);
// Git output and full arguments can contain private repository paths or contents.
throw new Error("Prepared project Git verification failed (git " + args[0] + ": " + reason + ")");
}
return result.stdout.trim();
};
const manifest = (root, baseCommit = input.baseCommit, priorRefs = [], manifestHome = machineHome) => {
const result = spawnSync(process.execPath, ["-e", manifestScript, root, baseCommit, "eligible", ...priorRefs.map((ref) => ref.slice(7))], { env: { ...env, HOME: manifestHome }, encoding: "utf8", timeout: 600000, maxBuffer: 262144 });
if (result.status !== 0 || !/^sha256:[a-f0-9]{64}$/.test(result.stdout.trim())) throw new Error("Prepared project manifest verification failed: " + (result.stderr?.trim() || result.error?.message || result.status));
return result.stdout.trim();
};
const readManifest = (file, ref) => {
const fd = fs.openSync(file, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW | fs.constants.O_NONBLOCK);
try {
const stat = fs.fstatSync(fd);
if (!stat.isFile() || stat.size > ${MAX_WORKSPACE_MANIFEST_BYTES}) throw new Error("Prepared project manifest is unsafe");
const bytes = fs.readFileSync(fd);
if ("sha256:" + crypto.createHash("sha256").update(bytes).digest("hex") !== ref) throw new Error("Prepared project source manifest changed");
return bytes;
} finally { fs.closeSync(fd); }
};
const singleArtifact = (directory, pattern) => {
const entries = fs.opendirSync(directory);
try {
const entry = entries.readSync();
if (!entry || !pattern.test(entry.name) || entries.readSync()) throw new Error("Prepared project completion artifact is invalid");
return entry.name;
} finally { entries.closeSync(); }
};
const manifestEntries = (bytes, baseCommit) => {
const value = JSON.parse(bytes);
if (value.version !== 1 || value.baseCommit !== baseCommit || !Array.isArray(value.entries) || value.entries.length > ${MAX_WORKSPACE_INVENTORY_ENTRIES}) throw new Error("Prepared project manifest is invalid");
for (const entry of value.entries) {
const relative = entry.path;
if (typeof relative !== "string" || !relative || relative.includes("\\\\") || path.posix.isAbsolute(relative) || path.posix.normalize(relative) !== relative || relative === "." || relative === ".." || relative.startsWith("../") || relative === ".git" || relative.startsWith(".git/") || !["file", "directory", "symlink"].includes(entry.type)) throw new Error("Prepared project manifest path is unsafe");
}
return value.entries;
};
const removeSetupOutputs = (workspaceDir, previous) => {
const baseline = manifestEntries(previous.sourceBytes, previous.baseCommit);
const prepared = manifestEntries(previous.preparedBytes, previous.baseCommit);
const baselineFiles = new Set(baseline.filter((entry) => entry.type !== "directory").map((entry) => entry.path));
const baselineDirectories = new Set(baseline.filter((entry) => entry.type === "directory").map((entry) => entry.path));
const targetPath = (relative) => {
const segments = relative.split("/");
let parent = workspaceDir;
for (const segment of segments.slice(0, -1)) parent = ownedDirectory(parent, segment);
return path.join(parent, segments.at(-1));
};
// Remove obsolete eligible setup output here. Git owns replacement of newly
// tracked paths; unrelated ignored dependency/build caches remain in place.
for (const entry of prepared) {
if (entry.type !== "directory" && !baselineFiles.has(entry.path)) fs.unlinkSync(targetPath(entry.path));
}
for (const entry of prepared.filter((entry) => entry.type === "directory" && !baselineDirectories.has(entry.path)).sort((left, right) => right.path.split("/").length - left.path.split("/").length)) {
const target = targetPath(entry.path);
ownedDirectory(path.dirname(target), path.basename(target));
if (fs.readdirSync(target).length === 0) fs.rmdirSync(target);
}
};
const runSetup = (script, workspaceDir, homeDir) => {
// Verification and copying consume this command's budget before repository code starts.
const timeoutMs = input.timeoutMs - (performance.now() - startedAt);
if (!Number.isSafeInteger(input.timeoutMs) || input.timeoutMs > 2147483647 || timeoutMs <= 0) throw new Error("Prepared project command budget exhausted");
let child;
let timeout;
let stderr = "";
let failure;
// A deadline/signal may retire the group before exit; never signal that group twice.
let killed = false;
const killGroup = () => {
if (child?.pid && !killed) {
try { process.kill(-child.pid, "SIGKILL"); } catch (error) { if (error.code !== "ESRCH") throw error; }
killed = true;
}
};
const stop = (reason) => { failure ??= reason; killGroup(); };
const signals = ["SIGTERM", "SIGINT"].map((signal) => [signal, () => stop("interrupted by " + signal)]);
// A recipe can signal its parent before spawn returns. Own those signals
// before it starts, and release them even when spawn throws synchronously.
for (const [signal, handler] of signals) process.once(signal, handler);
return new Promise((resolve, reject) => {
child = spawn(script, [], {
cwd: workspaceDir,
env: { PATH: process.env.PATH, HOME: homeDir, LANG: "C.UTF-8", OPENCLAW_SOURCE_TREE_PATH: workspaceDir, OPENCLAW_WORKTREE_PATH: workspaceDir },
detached: true,
stdio: ["ignore", "ignore", "pipe"],
});
timeout = setTimeout(() => stop("timed out within the provider command budget (" + input.timeoutMs + " ms)"), timeoutMs);
child.stderr.on("data", (chunk) => { stderr = (stderr + chunk.toString()).slice(-16384); });
child.once("error", (error) => { failure ??= error.message; });
// Descendants can retain stderr after the script exits. Kill them at exit,
// then await close to prove all inherited pipes are drained before capture.
child.once("exit", killGroup);
child.once("close", (code) => {
if (code !== 0 || failure) reject(new Error("Prepared project setup failed: " + (failure || stderr.trim() || "exit " + code)));
else resolve();
});
}).finally(() => {
clearTimeout(timeout);
for (const [signal, handler] of signals) process.removeListener(signal, handler);
});
};
const workerRoot = ownedDirectory(machineHome, ".openclaw-worker");
const existing = path.join(workerRoot, "prepared", input.namespace, input.cacheKey);
const exists = fs.existsSync(existing);
const verified = input.verifiedRetained;
if (verified !== undefined && (verified !== null) !== exists) throw new Error("Prepared project changed during preparation");
// Inspection cannot create a workspace or execute repository code before the Gateway rechecks its owner.
if (inspectOnly && !exists) return;
let previous;
if (exists) {
const parent = ownedDirectory(ownedDirectory(workerRoot, "prepared"), input.namespace);
const directory = ownedDirectory(parent, input.cacheKey);
const workspaceDir = ownedDirectory(directory, "workspace");
const homeDir = ownedDirectory(directory, "home");
const admin = ownedDirectory(workspaceDir, ".git");
if (fs.existsSync(path.join(admin, "objects", "info", "alternates")) || fs.existsSync(path.join(admin, "info", "grafts"))) throw new Error("Prepared project Git base is not standalone");
const baseCommit = git(workspaceDir, ["rev-parse", "--verify", "HEAD^{commit}"]);
if (!/^[a-f0-9]{40}(?:[a-f0-9]{24})?$/.test(baseCommit)) throw new Error("Prepared project Git base is invalid");
if (verified === undefined) git(workspaceDir, ["fsck", "--full", "--strict", "--no-reflogs", baseCommit]);
const manifestRoot = ownedDirectory(ownedDirectory(homeDir, ".openclaw-worker"), "manifests");
const completionRoot = ownedDirectory(manifestRoot, "prepared");
const sourceDigest = singleArtifact(completionRoot, /^[a-f0-9]{64}$/);
const completionDirectory = ownedDirectory(completionRoot, sourceDigest);
const preparedFile = singleArtifact(completionDirectory, /^[a-f0-9]{64}[.]json$/);
const sourceManifestRef = "sha256:" + sourceDigest;
const preparedManifestRef = "sha256:" + preparedFile.slice(0, -5);
if (verified && (baseCommit !== verified.baseCommit || sourceManifestRef !== verified.sourceManifestRef || preparedManifestRef !== verified.preparedManifestRef)) throw new Error("Prepared project changed during preparation");
const completedManifest = path.join(completionDirectory, preparedFile);
const sourceBytes = readManifest(path.join(manifestRoot, sourceDigest + ".json"), sourceManifestRef);
const preparedBytes = readManifest(completedManifest, preparedManifestRef);
readManifest(path.join(manifestRoot, preparedFile), preparedManifestRef);
manifestEntries(sourceBytes, baseCommit);
if (verified === undefined && manifest(workspaceDir, baseCommit, [preparedManifestRef, sourceManifestRef], homeDir) !== preparedManifestRef) throw new Error("Prepared project completed workspace changed");
previous = { workspaceDir, homeDir, sourceManifestRef, preparedManifestRef, baseCommit, sourceBytes, preparedBytes, completedManifest, completionDirectory, manifestRoot };
if (inspectOnly) return { workspaceDir, homeDir, sourceManifestRef, preparedManifestRef, baseCommit };
}
const seeds = ownedDirectory(ownedDirectory(workerRoot, "git-seeds"), input.namespace);
const seed = ownedDirectory(seeds, input.seedKey);
ownedDirectory(seed, ".git");
if (git(seed, ["rev-parse", "HEAD"]) !== input.baseCommit || git(seed, ["status", "--porcelain=v1", "--untracked-files=all"])) throw new Error("Prepared project seed is not pristine");
const recipe = git(seed, ["ls-tree", input.baseCommit, "--", ".openclaw/worktree-setup.sh"]);
const expectedRecipe = input.setupRecipe ? "100755 blob " + input.setupRecipe + "\\t.openclaw/worktree-setup.sh" : null;
if (expectedRecipe ? recipe !== expectedRecipe : recipe.startsWith("100755 ")) throw new Error("Prepared project setup recipe differs from its admission");
const sourceManifestRef = manifest(seed);
const sourceFile = path.join(workerRoot, "manifests", sourceManifestRef.slice(7) + ".json");
const sourceBytes = readManifest(sourceFile, sourceManifestRef);
const preparedRoot = ownedDirectory(ownedDirectory(workerRoot, "prepared", !inspectOnly), input.namespace, !inspectOnly);
const directory = path.join(preparedRoot, input.cacheKey);
const fresh = !fs.existsSync(directory);
if (fresh && inspectOnly) return;
ownedDirectory(preparedRoot, input.cacheKey, fresh);
if (fresh) {
fs.mkdirSync(path.join(directory, "home"), { mode: 0o700 });
fs.cpSync(seed, path.join(directory, "workspace"), { recursive: true, errorOnExist: true, force: false, verbatimSymlinks: true });
}
const workspaceDir = ownedDirectory(directory, "workspace");
const homeDir = ownedDirectory(directory, "home");
const manifestRoot = ownedDirectory(ownedDirectory(homeDir, ".openclaw-worker", fresh), "manifests", fresh);
const completionRoot = ownedDirectory(manifestRoot, "prepared", fresh);
const changed = previous && previous.baseCommit !== input.baseCommit;
if (changed) {
// Invalidate the only completion witness before touching Git or running code.
// An interrupted update cannot replay setup, even when returning to an older commit.
fs.unlinkSync(previous.completedManifest);
fs.rmdirSync(previous.completionDirectory);
for (const ref of new Set([previous.sourceManifestRef, previous.preparedManifestRef])) fs.unlinkSync(path.join(manifestRoot, ref.slice(7) + ".json"));
removeSetupOutputs(workspaceDir, previous);
git(workspaceDir, ["fetch", "--depth=1", "--no-tags", "--no-write-fetch-head", "--update-shallow", seed, input.baseCommit]);
git(workspaceDir, ["checkout", "--detach", "--force", input.baseCommit]);
} else if (!fresh && previous.sourceManifestRef !== sourceManifestRef) {
throw new Error("Prepared project pristine baseline changed");
}
if ((fresh || changed) && input.setupRecipe && input.runSetupScript !== false) {
const script = path.join(ownedDirectory(workspaceDir, ".openclaw"), "worktree-setup.sh");
const stat = fs.lstatSync(script);
if (!stat.isFile() || stat.isSymbolicLink() || (stat.mode & 0o111) === 0) throw new Error("Prepared project setup is not an executable regular file");
await runSetup(script, workspaceDir, homeDir);
}
ownedDirectory(directory, "workspace");
ownedDirectory(directory, "home");
ownedDirectory(ownedDirectory(homeDir, ".openclaw-worker"), "manifests");
ownedDirectory(workspaceDir, ".git");
if (git(workspaceDir, ["rev-parse", "HEAD"]) !== input.baseCommit) throw new Error("Prepared project setup changed its Git base");
if (input.setupRecipe) {
const script = path.join(ownedDirectory(workspaceDir, ".openclaw"), "worktree-setup.sh");
const stat = fs.lstatSync(script);
if (!stat.isFile() || stat.isSymbolicLink() || (stat.mode & 0o111) === 0 || git(workspaceDir, ["hash-object", "--", script]) !== input.setupRecipe) throw new Error("Prepared project setup recipe changed during setup");
}
const preparedManifestRef = manifest(workspaceDir, input.baseCommit, [sourceManifestRef]);
const preparedBytes = readManifest(path.join(workerRoot, "manifests", preparedManifestRef.slice(7) + ".json"), preparedManifestRef);
if (fresh || changed) {
const completed = new Map([[sourceManifestRef, sourceBytes], [preparedManifestRef, preparedBytes]]);
for (const [ref, bytes] of completed) fs.writeFileSync(path.join(manifestRoot, ref.slice(7) + ".json"), bytes, { flag: "wx", mode: 0o600 });
const completionDirectory = ownedDirectory(completionRoot, sourceManifestRef.slice(7), true);
// Publish this content-addressed pair last. Root B/P artifacts alone cannot
// turn an interrupted setup into a reusable completed environment.
fs.writeFileSync(path.join(completionDirectory, preparedManifestRef.slice(7) + ".json"), preparedBytes, { flag: "wx", mode: 0o600 });
}
return { workspaceDir, homeDir, sourceManifestRef, preparedManifestRef };
}`;
/** Setup runs at the final absolute paths, before enrollment or session overlays. */
function createProjectSetupScript(input, inspectOnly = false) {
return `set -eu
node <<'PROJECT_SETUP_SCRIPT'
(${PREPARE_PROJECT_WORKSPACE_JS})(${JSON.stringify(input)}, ${inspectOnly})
.then((result) => process.stdout.write(JSON.stringify(result ?? null)))
.catch((error) => { console.error(error.message); process.exitCode = 1; });
PROJECT_SETUP_SCRIPT`;
}
//#endregion
//#region src/gateway/worker-environments/project-seed-script.ts
/** Only immutable Git content and non-secret preparation metadata enter the machine image. */
function createProjectSeedScript(input) {
return `set -eu
node <<'PROJECT_SEED_SCRIPT'
const fs = require("node:fs");
const path = require("node:path");
const os = require("node:os");
const crypto = require("node:crypto");
const { spawnSync } = require("node:child_process");
const input = ${JSON.stringify(input)};
const retention = ${JSON.stringify(WORKSPACE_SEED_RETENTION)};
${WORKSPACE_SEED_RETENTION_JS}
const prepareWorkspace = ${input.preparation ? PREPARE_PROJECT_WORKSPACE_JS : "undefined"};
process.umask(0o077);
const env = { ...Object.fromEntries(Object.entries(process.env).filter(([key]) => !/^(GIT_|GH_TOKEN$|GITHUB_TOKEN$)/i.test(key))), GIT_CONFIG_NOSYSTEM: "1", GIT_CONFIG_GLOBAL: os.devNull, GIT_TERMINAL_PROMPT: "0", GIT_ASKPASS: "", SSH_ASKPASS: "" };
const git = (root, args, stdin, networkEnv) => {
const result = spawnSync("git", ["-c", "core.hooksPath=" + os.devNull, "-c", "core.fsmonitor=false", "-c", "credential.helper=", "-c", "core.askPass=", "-c", "init.templateDir=", "-C", root, ...args], { env: networkEnv ?? env, encoding: "utf8", timeout: 600000, maxBuffer: 262144, stdio: [stdin ?? "ignore", "pipe", "pipe"] });
if (result.status !== 0) throw new Error(networkEnv ? "Project repository fetch failed" : "Project Git preparation failed: " + (result.stderr?.trim() || result.error?.message || "exit " + result.status));
return result.stdout.trim();
};
const ownedDirectory = (parent, target) => {
const stat = fs.lstatSync(target);
if (stat.isSymbolicLink() || !stat.isDirectory() || path.dirname(fs.realpathSync(target)) !== parent) throw new Error("Project seed directory escaped its owner");
return stat;
};
(async () => {
const home = fs.realpathSync(os.homedir());
const workerRoot = path.join(home, ".openclaw-worker");
fs.mkdirSync(workerRoot, { recursive: true, mode: 0o700 });
ownedDirectory(home, workerRoot);
const root = path.join(workerRoot, "git-seeds");
fs.mkdirSync(root, { mode: 0o700, recursive: true });
ownedDirectory(workerRoot, root);
const namespace = path.join(root, input.namespace);
fs.mkdirSync(namespace, { recursive: true, mode: 0o700 });
ownedDirectory(root, namespace);
const prune = () => {
const entries = fs.readdirSync(namespace, { withFileTypes: true })
.filter((entry) => entry.isDirectory())
.map((entry) => ({ name: entry.name, mtimeMs: ownedDirectory(namespace, path.join(namespace, entry.name)).mtimeMs }));
for (const entry of selectWorkspaceSeedsToPrune(entries, retention, Date.now(), input.seedKey)) {
const target = path.join(namespace, entry.name);
if (ownedDirectory(namespace, target).mtimeMs === entry.mtimeMs) fs.rmSync(target, { recursive: true });
}
};
const seed = path.join(namespace, input.seedKey);
const stagingPrefix = ".tmp-" + input.seedKey + "-";
if (input.pack && input.repository) throw new Error("Project seed transports are mutually exclusive");
const transport = input.pack ?? input.repository;
const directory = transport?.directory;
if (directory !== undefined) {
if (path.dirname(directory) !== namespace || !path.basename(directory).startsWith(stagingPrefix)) throw new Error("Project staging path escaped its owner");
ownedDirectory(namespace, directory);
}
try {
const retained = input.preparation && await prepareWorkspace({ ...input, ...input.preparation }, true);
const retainedWorkspace = input.preparation ? { retainedWorkspace: retained ?? null } : {};
if (!transport) {
if (fs.existsSync(seed)) {
ownedDirectory(namespace, seed);
ownedDirectory(seed, path.join(seed, ".git"));
const preparedWorkspace = retained?.baseCommit === input.baseCommit ? retained : undefined;
if (git(seed, ["rev-parse", "--verify", "HEAD"]) !== input.baseCommit || git(seed, ["status", "--porcelain=v1", "--untracked-files=all"])) throw new Error("Prepared project seed is not pristine");
prune();
process.stdout.write(JSON.stringify({ ready: true, preparedWorkspace, ...retainedWorkspace }));
return;
}
// Provisioning serializes this lease. Discard only this project's abandoned staging.
for (const entry of fs.readdirSync(namespace)) {
if (!entry.startsWith(stagingPrefix)) continue;
const stale = path.join(namespace, entry);
ownedDirectory(namespace, stale);
fs.rmSync(stale, { recursive: true });
}
const directory = fs.mkdtempSync(path.join(namespace, stagingPrefix));
process.stdout.write(JSON.stringify({ ready: false, directory, ...retainedWorkspace }));
return;
}
const repository = path.join(directory, "repository");
fs.mkdirSync(repository, { mode: 0o700 });
git(repository, ["init", "--quiet", "--object-format=" + (input.baseCommit.length === 40 ? "sha1" : "sha256"), "."]);
const repositoryUrl = input.repository?.url ?? input.pack?.repositoryUrl;
if (repositoryUrl !== undefined) {
const url = new URL(repositoryUrl);
const segments = url.pathname.slice(1).split("/");
if (url.origin !== "https://github.com" || url.href !== repositoryUrl || url.username || url.password || url.search || url.hash || segments.length !== 2 || segments.some((segment) => !/^[A-Za-z0-9_.-]+$/.test(segment)) || !segments[1].endsWith(".git") || !/^[a-f0-9]{40}$/.test(input.baseCommit)) throw new Error("Project repository source is invalid");
}
if (input.repository) {
// Public fetches cannot use ambient credentials, helpers, or redirects.
// Git enables libcurl's netrc lookup independently of credential helpers.
const authHome = fs.mkdtempSync(path.join(directory, ".fetch-home-"));
const networkEnv = { ...Object.fromEntries(Object.entries(env).filter(([key]) => !/^(HOME|USERPROFILE|NETRC)$/i.test(key))), HOME: authHome, USERPROFILE: authHome };
git(repository, ["-c", "http.followRedirects=false", "-c", "protocol.allow=never", "-c", "protocol.https.allow=always", "fetch", "--depth=1", "--no-tags", "--no-write-fetch-head", "--no-recurse-submodules", repositoryUrl, input.baseCommit], undefined, networkEnv);
} else {
const pack = path.join(directory, "base.pack");
const stat = fs.lstatSync(pack);
if (!stat.isFile() || stat.isSymbolicLink() || stat.size !== input.pack.bytes) throw new Error("Project pack size does not match");
const hash = crypto.createHash("sha256");
for await (const chunk of fs.createReadStream(pack)) hash.update(chunk);
if (hash.digest("hex") !== input.pack.sha256) throw new Error("Project pack digest does not match");
if (input.pack.retainedCommit) {
if (retained?.baseCommit !== input.pack.retainedCommit) throw new Error("Prepared project retained Git base changed before transfer");
// Fetch one local snapshot into independent objects, without alternates or
// ancestors that the retained checkout may never have received.
git(repository, ["fetch", "--depth=1", "--no-tags", "--no-write-fetch-head", "--update-shallow", retained.workspaceDir, input.pack.retainedCommit]);
}
fs.writeFileSync(path.join(repository, ".git", "shallow"), [...new Set([input.baseCommit, input.pack.retainedCommit].filter(Boolean))].join("\\n") + "\\n", { mode: 0o600 });
const fd = fs.openSync(pack, "r");
try { git(repository, ["index-pack", "--stdin", "--fix-thin"], fd); } finally { fs.closeSync(fd); }
}
// Session workspace binding verifies this credential-free source identity.
if (repositoryUrl !== undefined) git(repository, ["remote", "add", "origin", repositoryUrl]);
if (git(repository, ["rev-parse", "--verify", input.baseCommit + "^{commit}"]) !== input.baseCommit) throw new Error("Project seed commit does not match");
git(repository, ["fsck", "--full", "--strict", "--no-reflogs", input.baseCommit]);
git(repository, ["checkout", "--detach", "--force", input.baseCommit]);
if (git(repository, ["status", "--porcelain=v1", "--untracked-files=all"])) throw new Error("Prepared project checkout is not pristine");
fs.renameSync(repository, seed);
prune();
// Repository code keeps its separate Gateway authority check. A checkout that
// cannot run setup can complete under this seed command's existing owner.
const preparedWorkspace = input.preparation && (!input.preparation.setupRecipe || input.preparation.runSetupScript === false)
? await prepareWorkspace({ ...input, ...input.preparation, runSetupScript: false })
: undefined;
process.stdout.write(JSON.stringify({ ready: true, preparedWorkspace }));
} finally { if (directory !== undefined) fs.rmSync(directory, { recursive: true, force: true }); }
})().catch((error) => { console.error(error.message); process.exitCode = 1; });
PROJECT_SEED_SCRIPT`;
}
//#endregion
//#region src/gateway/worker-environments/repository-project-source.ts
const AgentId = string().min(1).max(64).refine((value) => {
const normalized = normalizeAgentIdStrict(value);
return normalized.ok && normalized.value === value;
});
const AccountId = number().int().positive().max(Number.MAX_SAFE_INTEGER);
const Identity = discriminatedUnion("source", [
object({ source: literal("anonymous") }).strict(),
object({
source: literal("system-detected"),
accountId: AccountId
}).strict(),
object({
source: literal("system-configured"),
profileId: string().regex(MANAGED_GITHUB_PROFILE_ID_PATTERN),
accountId: AccountId
}).strict(),
object({
source: literal("agent-override"),
profileId: string().regex(MANAGED_GITHUB_PROFILE_ID_PATTERN),
accountId: AccountId
}).strict()
]);
const Agent = object({
agentId: AgentId,
provenance: object({
agentId: AgentId,
createdVia: _enum([
"operator",
"agent",
"claw"
]),
creatorAgentId: AgentId.nullable(),
createdAtMs: number().int().nonnegative().max(Number.MAX_SAFE_INTEGER)
}).strict().nullable()
}).strict().refine((value) => value.provenance === null || value.provenance.agentId === value.agentId);
const RepositoryProject = object({
key: string().regex(/^[a-f0-9]{64}$/u),
baseCommit: string().regex(/^[a-f0-9]{40}$/u),
source: object({
kind: literal("repository"),
url: string().max(2048).refine((value) => parseProjectGitUrl(value)?.url === value),
repositoryId: string().min(1).max(256).regex(/^[A-Za-z0-9_+/=-]+$/u),
owner: object({
agent: Agent,
identity: Identity
}).strict()
}).strict()
});
/** Repository facts persist; current visibility and access remain admission checks. */
function readRepositoryWorkerProjectSnapshot(value) {
if (!isRecord(value) || value.source === void 0) return;
const parsed = RepositoryProject.safeParse(value);
if (Object.keys(value).some((key) => ![
"key",
"baseCommit",
"source",
"preparation"
].includes(key)) || !parsed.success) throw new Error("Worker environment has an invalid repository preparation snapshot");
return parsed.data;
}
//#endregion
//#region src/gateway/worker-environments/project-preparation.ts
async function readWorkerProjectSetupRecipe(project, signal) {
const tree = await requireGit(project.root, [
"ls-tree",
"-z",
project.baseCommit,
"--",
".openclaw/worktree-setup.sh"
], {
signal,
timeoutMs: 3e4
});
return /^100755 blob ([a-f0-9]{40}(?:[a-f0-9]{24})?)\t\.openclaw\/worktree-setup\.sh\0$/u.exec(tree)?.[1];
}
function readWorkerProjectSnapshot(value) {
if (value === void 0) return;
if (isRecord(value) && value.source !== void 0) return readRepositoryWorkerProjectSnapshot(value);
if (!isRecord(value) || typeof value.key !== "string" || !/^[a-f0-9]{64}$/u.test(value.key) || typeof value.baseCommit !== "string" || !/^[a-f0-9]{40}(?:[a-f0-9]{24})?$/u.test(value.baseCommit) || value.label !== void 0 && typeof value.label !== "string" || typeof value.root !== "string" || value.root.length > 4096 || !path.isAbsolute(value.root)) throw new Error("Worker environment has an invalid project preparation snapshot");
return {
key: value.key,
root: value.root,
baseCommit: value.baseCommit,
...value.label !== void 0 ? { label: value.label } : {}
};
}
function createWorkerProjectPreparation(params) {
if (!/^[A-Za-z0-9_-]{1,128}$/u.test(params.namespace)) throw new Error("Worker project preparation namespace is invalid");
const preparation = params.preparation;
if (preparation && (!/^[a-f0-9]{64}$/u.test(preparation.key) || !/^[a-f0-9]{64}$/u.test(preparation.cacheKey) || preparation.purpose !== "session" && preparation.purpose !== "reserve" || !Number.isSafeInteger(preparation.demandAtMs) || preparation.demandAtMs < 0 || preparation.setupRecipe !== void 0 && !/^[a-f0-9]{40}(?:[a-f0-9]{24})?$/u.test(preparation.setupRecipe))) throw new Error("Worker project preparation identity is invalid");
if (preparation?.setupRecipe && preparation.runSetupScript !== false && !params.setupAuthorized) throw new Error("Prepared project setup requires operator.admin authorization");
const abort = new AbortController();
const signal = params.signal ? AbortSignal.any([abort.signal, params.signal]) : abort.signal;
const seedKey = workerProjectSeedKey(params.project);
const label = "source" in params.project ? normalizeCloudRepo(params.project.source.url) : params.project.label;
let active;
let preparedWorkspace;
let verifiedRetained;
const requireCurrent = () => {
signal.throwIfAborted();
try {
params.requireCurrent();
} catch (error) {
abort.abort(error);
throw error;
}
};
const readPreparedWorkspace = (prepared) => {
if (!preparation) throw new Error("Project preparation did not request a prepared workspace");
const suffix = `/.openclaw-worker/prepared/${params.namespace}/${preparation.cacheKey}`;
if (!isRecord(prepared) || typeof prepared.workspaceDir !== "string" || prepared.workspaceDir.length > 4096 || !path.posix.isAbsolute(prepared.workspaceDir) || path.posix.normalize(prepared.workspaceDir) !== prepared.workspaceDir || !prepared.workspaceDir.endsWith(`${suffix}/workspace`) || prepared.homeDir !== path.posix.join(path.posix.dirname(prepared.workspaceDir), "home") || typeof prepared.sourceManifestRef !== "string" || !/^sha256:[a-f0-9]{64}$/u.test(prepared.sourceManifestRef) || typeof prepared.preparedManifestRef !== "string" || !/^sha256:[a-f0-9]{64}$/u.test(prepared.preparedManifestRef)) throw new Error("Prepared project returned invalid workspace identity");
return Object.freeze({
preparationKey: preparation.key,
cacheKey: preparation.cacheKey,
workspaceDir: prepared.workspaceDir,
homeDir: prepared.homeDir,
sourceManifestRef: prepared.sourceManifestRef,
preparedManifestRef: prepared.preparedManifestRef
});
};
const prepareSeed = async (transport) => {
requireCurrent();
const scriptInput = {
namespace: params.namespace,
seedKey,
baseCommit: params.project.baseCommit,
...preparation ? { preparation: {
preparationKey: preparation.key,
cacheKey: preparation.cacheKey,
setupRecipe: preparation.setupRecipe,
runSetupScript: preparation.runSetupScript
} } : {}
};
const inspection = JSON.parse(await transport.runScript(createProjectSeedScript(scriptInput), signal));
requireCurrent();
if (!isRecord(inspection) || typeof inspection.ready !== "boolean") throw new Error("Project preparation returned invalid seed status");
if (preparation) {
const retained = inspection.retainedWorkspace;
if (retained === null) verifiedRetained = null;
else {
if (!isRecord(retained) || typeof retained.baseCommit !== "string" || !/^[a-f0-9]{40}(?:[a-f0-9]{24})?$/u.test(retained.baseCommit) || retained.baseCommit.length !== params.project.baseCommit.length) throw new Error("Project preparation returned an invalid retained Git base");
const workspace = readPreparedWorkspace(retained);
verifiedRetained = {
baseCommit: retained.baseCommit,
sourceManifestRef: workspace.sourceManifestRef,
preparedManifestRef: workspace.preparedManifestRef
};
}
}
if (inspection.ready) return {
seedKey,
cacheHit: true,
...inspection.preparedWorkspace !== void 0 ? { preparedWorkspace: readPreparedWorkspace(inspection.preparedWorkspace) } : {}
};
const directory = inspection.directory;
const retainedCommit = verifiedRetained?.baseCommit;
if (typeof directory !== "string" || directory.length > 4096 || !path.posix.isAbsolute(directory) || !directory.includes(`/.openclaw-worker/git-seeds/${params.namespace}/`) || path.posix.normalize(directory) !== directory || path.posix.basename(path.posix.dirname(directory)) !== params.namespace || !path.posix.basename(directory).startsWith(`.tmp-${seedKey}-`)) throw new Error("Project preparation returned an invalid staging directory");
const temporaryRoot = await fs.mkdtemp(path.join(resolvePreferredOpenClawTmpDir(), "openclaw-project-base-"));
try {
requireCurrent();
let transfer;
if ("source" in params.project && !params.prepareRepositoryGitPack) transfer = { repository: {
directory,
url: params.project.source.url
} };
else {
const repository = "source" in params.project ? params.project.source : void 0;
const pack = "root" in params.project ? await prepareWorkerWorkspaceGitPack({
root: params.project.root,
baseCommit: params.project.baseCommit,
...typeof retainedCommit === "string" ? { retainedCommit } : {},
temporaryRoot,
signal
}) : await params.prepareRepositoryGitPack({
temporaryRoot,
signal
});
requireCurrent();
const bytes = (await fs.stat(pack)).size;
if (bytes > 4294967296) throw new Error("Project Git pack exceeds the workspace byte limit");
let sha256;
try {
var _usingCtx$1 = _usingCtx();
const handle = _usingCtx$1.a(await fs.open(pack, "r"));
({digest: sha256} = await sha256File(handle, { signal }));
} catch (_) {
_usingCtx$1.e = _;
} finally {
await _usingCtx$1.d();
}
requireCurrent();
await transport.upload(pack, path.posix.join(directory, "base.pack"), signal);
requireCurrent();
transfer = { pack: {
directory,
bytes,
sha256,
...repository ? { repositoryUrl: repository.url } : typeof retainedCommit === "string" ? { retainedCommit } : {}
} };
}
const installed = JSON.parse(await transport.runScript(createProjectSeedScript({
...scriptInput,
...transfer,
verifiedRetained
}), signal));
requireCurrent();
if (!isRecord(installed) || installed.ready !== true) throw new Error("Project checkout was not verified before capture");
return {
seedKey,
cacheHit: false,
...installed.preparedWorkspace !== void 0 ? {
preparedWorkspace: readPreparedWorkspace(installed.preparedWorkspace),
captureRequired: true
} : {}
};
} finally {
await fs.rm(temporaryRoot, {
recursive: true,
force: true
});
}
};
const prepare = async (transport) => {
requireCurrent();
if ("source" in params.project) {
if (!params.revalidateRepositorySource) throw new Error("Repository project preparation has no current source authority");
await params.revalidateRepositorySource(signal);
requireCurrent();
}
if (!preparation) {
const result = await prepareSeed(transport);
requireCurrent();
return result;
}
if (!transport.runScriptWithBudget) throw new Error("Prepared workspaces require a provider command budget");
const result = await prepareSeed(transport);
requireCurrent();
if (result.preparedWorkspace) {
preparedWorkspace = result.preparedWorkspace;
return result;
}
const prepared = JSON.parse(await transport.runScriptWithBudget((timeoutMs) => createProjectSetupScript({
namespace: params.namespace,
seedKey,
preparationKey: preparation.key,
cacheKey: preparation.cacheKey,
baseCommit: params.project.baseCommit,
setupRecipe: preparation.setupRecipe,
runSetupScript: preparation.runSetupScript,
timeoutMs,
verifiedRetained
}), signal));
requireCurrent();
preparedWorkspace = readPreparedWorkspace(prepared);
return {
...result,
preparedWorkspace,
captureRequired: true
};
};
return {
getPreparedWorkspace: () => preparedWorkspace,
project: {
key: params.project.key,
baseCommit: params.project.baseCommit,
..."source" in params.project ? {} : { root: params.project.root },
...label !== void 0 ? { label } : {},
...preparation ? {
preparation: {
key: preparation.key,
cacheKey: preparation.cacheKey,
purpose: preparation.purpose,
demandAtMs: preparation.demandAtMs
},
inspectPreparedWorkspace: async (transport) => {
requireCurrent();
const inspected = JSON.parse(await transport.runScript(createProjectSetupScript({
namespace: params.namespace,
seedKey,
preparationKey: preparation.key,
cacheKey: preparation.cacheKey,
baseCommit: params.project.baseCommit,
setupRecipe: preparation.setupRecipe,
runSetupScript: preparation.runSetupScript
}, true), signal));
requireCurrent();
if (!isRecord(inspected) || inspected.baseCommit !== params.project.baseCommit) throw new Error("Enrolled project has no matching completed workspace");
preparedWorkspace = readPreparedWorkspace(inspected);
}
} : {},
signal,
assertCurrent: requireCurrent,
prepare: (transport) => {
requireCurrent();
return active ??= prepare(transport);
}
},
close: () => abort.abort(new DOMException("Project preparation operation is closed", "AbortError"))
};
}
//#endregion
export { WorkerRuntimeRefreshPendingError as a, hasForcedWorkerEnvironmentAbandonment as c, readRepositoryWorkerProjectSnapshot as i, WorkerPlacementAdmissionTargetError as l, readWorkerProjectSetupRecipe as n, createWorkerRuntimeRefresher as o, readWorkerProjectSnapshot as r, createWorkerEnvironmentErrorRecorder as s, createWorkerProjectPreparation as t, deriveEnvironmentIntent as u };