import { resolveClawdbotPackageRoot } from "../../infra/clawdbot-root.js"; import { scheduleGatewaySigusr1Restart } from "../../infra/restart.js"; import { DOCTOR_NONINTERACTIVE_HINT, type RestartSentinelPayload, writeRestartSentinel, } from "../../infra/restart-sentinel.js"; import { runGatewayUpdate } from "../../infra/update-runner.js"; import { ErrorCodes, errorShape, formatValidationErrors, validateUpdateRunParams, } from "../protocol/index.js"; import type { GatewayRequestHandlers } from "./types.js"; export const updateHandlers: GatewayRequestHandlers = { "update.run": async ({ params, respond }) => { if (!validateUpdateRunParams(params)) { respond( false, undefined, errorShape( ErrorCodes.INVALID_REQUEST, `invalid update.run params: ${formatValidationErrors(validateUpdateRunParams.errors)}`, ), ); return; } const sessionKey = typeof (params as { sessionKey?: unknown }).sessionKey === "string" ? (params as { sessionKey?: string }).sessionKey?.trim() || undefined : undefined; const note = typeof (params as { note?: unknown }).note === "string" ? (params as { note?: string }).note?.trim() || undefined : undefined; const restartDelayMsRaw = (params as { restartDelayMs?: unknown }).restartDelayMs; const restartDelayMs = typeof restartDelayMsRaw === "number" && Number.isFinite(restartDelayMsRaw) ? Math.max(0, Math.floor(restartDelayMsRaw)) : undefined; const timeoutMsRaw = (params as { timeoutMs?: unknown }).timeoutMs; const timeoutMs = typeof timeoutMsRaw === "number" && Number.isFinite(timeoutMsRaw) ? Math.max(1000, Math.floor(timeoutMsRaw)) : undefined; let result: Awaited>; try { const root = (await resolveClawdbotPackageRoot({ moduleUrl: import.meta.url, argv1: process.argv[1], cwd: process.cwd(), })) ?? process.cwd(); result = await runGatewayUpdate({ timeoutMs, cwd: root, argv1: process.argv[1], }); } catch (err) { result = { status: "error", mode: "unknown", reason: String(err), steps: [], durationMs: 0, }; } const payload: RestartSentinelPayload = { kind: "update", status: result.status, ts: Date.now(), sessionKey, message: note ?? null, doctorHint: DOCTOR_NONINTERACTIVE_HINT, stats: { mode: result.mode, root: result.root ?? undefined, before: result.before ?? null, after: result.after ?? null, steps: result.steps.map((step) => ({ name: step.name, command: step.command, cwd: step.cwd, durationMs: step.durationMs, log: { stdoutTail: step.stdoutTail ?? null, stderrTail: step.stderrTail ?? null, exitCode: step.exitCode ?? null, }, })), reason: result.reason ?? null, durationMs: result.durationMs, }, }; let sentinelPath: string | null = null; try { sentinelPath = await writeRestartSentinel(payload); } catch { sentinelPath = null; } const restart = scheduleGatewaySigusr1Restart({ delayMs: restartDelayMs, reason: "update.run", }); respond( true, { ok: true, result, restart, sentinel: { path: sentinelPath, payload, }, }, undefined, ); }, };