mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-10 10:42:43 +00:00
feat: lightweight bootstrap context mode for heartbeat/cron runs (openclaw#26064) thanks @jose-velez
Verified: - pnpm build - pnpm check (fails on pre-existing unrelated repo issues in extensions/diffs and src/agents/tools/nodes-tool.test.ts) - pnpm vitest run src/agents/bootstrap-files.test.ts src/infra/heartbeat-runner.model-override.test.ts src/cli/cron-cli.test.ts - pnpm test:macmini (fails on pre-existing extensions/diffs import errors; touched suites pass) Co-authored-by: jose-velez <10926182+jose-velez@users.noreply.github.com> Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
||||
import {
|
||||
@@ -97,4 +98,32 @@ describe("resolveBootstrapContextForRun", () => {
|
||||
|
||||
expect(extra?.content).toBe("extra");
|
||||
});
|
||||
|
||||
it("uses heartbeat-only bootstrap files in lightweight heartbeat mode", async () => {
|
||||
const workspaceDir = await makeTempWorkspace("openclaw-bootstrap-");
|
||||
await fs.writeFile(path.join(workspaceDir, "HEARTBEAT.md"), "check inbox", "utf8");
|
||||
await fs.writeFile(path.join(workspaceDir, "SOUL.md"), "persona", "utf8");
|
||||
|
||||
const files = await resolveBootstrapFilesForRun({
|
||||
workspaceDir,
|
||||
contextMode: "lightweight",
|
||||
runKind: "heartbeat",
|
||||
});
|
||||
|
||||
expect(files.length).toBeGreaterThan(0);
|
||||
expect(files.every((file) => file.name === "HEARTBEAT.md")).toBe(true);
|
||||
});
|
||||
|
||||
it("keeps bootstrap context empty in lightweight cron mode", async () => {
|
||||
const workspaceDir = await makeTempWorkspace("openclaw-bootstrap-");
|
||||
await fs.writeFile(path.join(workspaceDir, "HEARTBEAT.md"), "check inbox", "utf8");
|
||||
|
||||
const files = await resolveBootstrapFilesForRun({
|
||||
workspaceDir,
|
||||
contextMode: "lightweight",
|
||||
runKind: "cron",
|
||||
});
|
||||
|
||||
expect(files).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -13,6 +13,9 @@ import {
|
||||
type WorkspaceBootstrapFile,
|
||||
} from "./workspace.js";
|
||||
|
||||
export type BootstrapContextMode = "full" | "lightweight";
|
||||
export type BootstrapContextRunKind = "default" | "heartbeat" | "cron";
|
||||
|
||||
export function makeBootstrapWarn(params: {
|
||||
sessionLabel: string;
|
||||
warn?: (message: string) => void;
|
||||
@@ -41,6 +44,23 @@ function sanitizeBootstrapFiles(
|
||||
return sanitized;
|
||||
}
|
||||
|
||||
function applyContextModeFilter(params: {
|
||||
files: WorkspaceBootstrapFile[];
|
||||
contextMode?: BootstrapContextMode;
|
||||
runKind?: BootstrapContextRunKind;
|
||||
}): WorkspaceBootstrapFile[] {
|
||||
const contextMode = params.contextMode ?? "full";
|
||||
const runKind = params.runKind ?? "default";
|
||||
if (contextMode !== "lightweight") {
|
||||
return params.files;
|
||||
}
|
||||
if (runKind === "heartbeat") {
|
||||
return params.files.filter((file) => file.name === "HEARTBEAT.md");
|
||||
}
|
||||
// cron/default lightweight mode keeps bootstrap context empty on purpose.
|
||||
return [];
|
||||
}
|
||||
|
||||
export async function resolveBootstrapFilesForRun(params: {
|
||||
workspaceDir: string;
|
||||
config?: OpenClawConfig;
|
||||
@@ -48,6 +68,8 @@ export async function resolveBootstrapFilesForRun(params: {
|
||||
sessionId?: string;
|
||||
agentId?: string;
|
||||
warn?: (message: string) => void;
|
||||
contextMode?: BootstrapContextMode;
|
||||
runKind?: BootstrapContextRunKind;
|
||||
}): Promise<WorkspaceBootstrapFile[]> {
|
||||
const sessionKey = params.sessionKey ?? params.sessionId;
|
||||
const rawFiles = params.sessionKey
|
||||
@@ -56,7 +78,11 @@ export async function resolveBootstrapFilesForRun(params: {
|
||||
sessionKey: params.sessionKey,
|
||||
})
|
||||
: await loadWorkspaceBootstrapFiles(params.workspaceDir);
|
||||
const bootstrapFiles = filterBootstrapFilesForSession(rawFiles, sessionKey);
|
||||
const bootstrapFiles = applyContextModeFilter({
|
||||
files: filterBootstrapFilesForSession(rawFiles, sessionKey),
|
||||
contextMode: params.contextMode,
|
||||
runKind: params.runKind,
|
||||
});
|
||||
|
||||
const updated = await applyBootstrapHookOverrides({
|
||||
files: bootstrapFiles,
|
||||
@@ -76,6 +102,8 @@ export async function resolveBootstrapContextForRun(params: {
|
||||
sessionId?: string;
|
||||
agentId?: string;
|
||||
warn?: (message: string) => void;
|
||||
contextMode?: BootstrapContextMode;
|
||||
runKind?: BootstrapContextRunKind;
|
||||
}): Promise<{
|
||||
bootstrapFiles: WorkspaceBootstrapFile[];
|
||||
contextFiles: EmbeddedContextFile[];
|
||||
|
||||
@@ -524,6 +524,8 @@ export async function runEmbeddedAttempt(
|
||||
sessionKey: params.sessionKey,
|
||||
sessionId: params.sessionId,
|
||||
warn: makeBootstrapWarn({ sessionLabel, warn: (message) => log.warn(message) }),
|
||||
contextMode: params.bootstrapContextMode,
|
||||
runKind: params.bootstrapContextRunKind,
|
||||
});
|
||||
const workspaceNotes = hookAdjustedBootstrapFiles.some(
|
||||
(file) => file.name === DEFAULT_BOOTSTRAP_FILENAME && !file.missing,
|
||||
|
||||
@@ -79,6 +79,10 @@ export type RunEmbeddedPiAgentParams = {
|
||||
toolResultFormat?: ToolResultFormat;
|
||||
/** If true, suppress tool error warning payloads for this run (including mutating tools). */
|
||||
suppressToolErrorWarnings?: boolean;
|
||||
/** Bootstrap context mode for workspace file injection. */
|
||||
bootstrapContextMode?: "full" | "lightweight";
|
||||
/** Run kind hint for context mode behavior. */
|
||||
bootstrapContextRunKind?: "default" | "heartbeat" | "cron";
|
||||
execOverrides?: Pick<ExecToolDefaults, "host" | "security" | "ask" | "node">;
|
||||
bashElevated?: ExecElevatedDefaults;
|
||||
timeoutMs: number;
|
||||
|
||||
Reference in New Issue
Block a user