openclaw / src /claws /bootstrap.ts
SaylorTwift's picture
SaylorTwift HF Staff
Add files using upload-large-folder tool
d197cf3 verified
Raw History Blame Contribute Delete
3.17 kB
import { createHash } from "node:crypto";
import { realpath } from "node:fs/promises";
import { resolve } from "node:path";
import { MAX_WORKSPACE_BOOTSTRAP_FILE_BYTES } from "../agents/workspace-bootstrap-read.js";
import { DEFAULT_BOOTSTRAP_FILENAME, seedWorkspaceBootstrap } from "../agents/workspace.js";
import { root as fsSafeRoot } from "../infra/fs-safe.js";
import type { OpenClawStateDatabaseOptions } from "../state/openclaw-state-db.js";
import { clawContainedRelativePath } from "./path-containment.js";
import type { ClawAddPlan } from "./types.js";
export class ClawBootstrapWriteError extends Error {
constructor(
readonly code: string,
message: string,
) {
super(message);
this.name = "ClawBootstrapWriteError";
}
}
function contentDigest(content: Uint8Array): string {
return `sha256:${createHash("sha256").update(content).digest("hex")}`;
}
export async function seedClawPackageBootstrap(
plan: ClawAddPlan,
options: {
nowMs?: number;
seedBootstrap?: typeof seedWorkspaceBootstrap;
} & OpenClawStateDatabaseOptions = {},
): Promise<"seeded" | "already-seeded" | "consumed" | undefined> {
const actions = plan.actions.filter((action) => action.kind === "bootstrap");
if (actions.length === 0) {
return undefined;
}
if (actions.length !== 1) {
throw new ClawBootstrapWriteError(
"bootstrap_plan_invalid",
"A Claw add plan may contain only one package bootstrap action.",
);
}
const action = actions[0];
if (!action) {
throw new ClawBootstrapWriteError(
"bootstrap_plan_invalid",
"The package bootstrap action is missing.",
);
}
if (!action.source || !action.digest) {
throw new ClawBootstrapWriteError(
"bootstrap_plan_invalid",
"The package bootstrap action lacks source integrity.",
);
}
const packageRoot = await realpath(resolve(plan.claw.packageRoot));
const sourcePath = resolve(action.source);
const sourceRelative = clawContainedRelativePath(packageRoot, sourcePath);
if (!sourceRelative) {
throw new ClawBootstrapWriteError(
"bootstrap_source_escape",
"BOOTSTRAP.md must remain inside the Claw package.",
);
}
const sourceRoot = await fsSafeRoot(packageRoot);
const read = await sourceRoot.read(sourceRelative, {
hardlinks: "reject",
maxBytes: MAX_WORKSPACE_BOOTSTRAP_FILE_BYTES,
symlinks: "reject",
});
if (resolve(read.realPath) !== sourcePath || contentDigest(read.buffer) !== action.digest) {
throw new ClawBootstrapWriteError(
"bootstrap_source_changed",
"BOOTSTRAP.md changed after consent; run add --dry-run again.",
);
}
const expectedTarget = resolve(plan.agent.workspace, DEFAULT_BOOTSTRAP_FILENAME);
if (resolve(action.target) !== expectedTarget) {
throw new ClawBootstrapWriteError(
"bootstrap_target_changed",
"The package bootstrap target is not the new agent workspace root.",
);
}
return (options.seedBootstrap ?? seedWorkspaceBootstrap)({
dir: plan.agent.workspace,
content: read.buffer,
...(options.nowMs !== undefined ? { nowMs: options.nowMs } : {}),
stateOptions: options,
});
}