Spaces:
Paused
Paused
| import fs from "node:fs/promises"; | |
| import path from "node:path"; | |
| import type { CliDeps } from "../cli/deps.js"; | |
| import type { OpenClawConfig } from "../config/config.js"; | |
| import { SILENT_REPLY_TOKEN } from "../auto-reply/tokens.js"; | |
| import { agentCommand } from "../commands/agent.js"; | |
| import { resolveMainSessionKey } from "../config/sessions/main-session.js"; | |
| import { createSubsystemLogger } from "../logging/subsystem.js"; | |
| import { type RuntimeEnv, defaultRuntime } from "../runtime.js"; | |
| const log = createSubsystemLogger("gateway/boot"); | |
| const BOOT_FILENAME = "BOOT.md"; | |
| export type BootRunResult = | |
| | { status: "skipped"; reason: "missing" | "empty" } | |
| | { status: "ran" } | |
| | { status: "failed"; reason: string }; | |
| function buildBootPrompt(content: string) { | |
| return [ | |
| "You are running a boot check. Follow BOOT.md instructions exactly.", | |
| "", | |
| "BOOT.md:", | |
| content, | |
| "", | |
| "If BOOT.md asks you to send a message, use the message tool (action=send with channel + target).", | |
| "Use the `target` field (not `to`) for message tool destinations.", | |
| `After sending with the message tool, reply with ONLY: ${SILENT_REPLY_TOKEN}.`, | |
| `If nothing needs attention, reply with ONLY: ${SILENT_REPLY_TOKEN}.`, | |
| ].join("\n"); | |
| } | |
| async function loadBootFile( | |
| workspaceDir: string, | |
| ): Promise<{ content?: string; status: "ok" | "missing" | "empty" }> { | |
| const bootPath = path.join(workspaceDir, BOOT_FILENAME); | |
| try { | |
| const content = await fs.readFile(bootPath, "utf-8"); | |
| const trimmed = content.trim(); | |
| if (!trimmed) { | |
| return { status: "empty" }; | |
| } | |
| return { status: "ok", content: trimmed }; | |
| } catch (err) { | |
| const anyErr = err as { code?: string }; | |
| if (anyErr.code === "ENOENT") { | |
| return { status: "missing" }; | |
| } | |
| throw err; | |
| } | |
| } | |
| export async function runBootOnce(params: { | |
| cfg: OpenClawConfig; | |
| deps: CliDeps; | |
| workspaceDir: string; | |
| }): Promise<BootRunResult> { | |
| const bootRuntime: RuntimeEnv = { | |
| log: () => {}, | |
| error: (message) => log.error(String(message)), | |
| exit: defaultRuntime.exit, | |
| }; | |
| let result: Awaited<ReturnType<typeof loadBootFile>>; | |
| try { | |
| result = await loadBootFile(params.workspaceDir); | |
| } catch (err) { | |
| const message = err instanceof Error ? err.message : String(err); | |
| log.error(`boot: failed to read ${BOOT_FILENAME}: ${message}`); | |
| return { status: "failed", reason: message }; | |
| } | |
| if (result.status === "missing" || result.status === "empty") { | |
| return { status: "skipped", reason: result.status }; | |
| } | |
| const sessionKey = resolveMainSessionKey(params.cfg); | |
| const message = buildBootPrompt(result.content ?? ""); | |
| try { | |
| await agentCommand( | |
| { | |
| message, | |
| sessionKey, | |
| deliver: false, | |
| }, | |
| bootRuntime, | |
| params.deps, | |
| ); | |
| return { status: "ran" }; | |
| } catch (err) { | |
| const messageText = err instanceof Error ? err.message : String(err); | |
| log.error(`boot: agent run failed: ${messageText}`); | |
| return { status: "failed", reason: messageText }; | |
| } | |
| } | |