UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

203 lines (202 loc) 8.88 kB
import { r as truncateUtf16Safe } from "./utf16-slice-D_ngcYKd.js"; import { t as VERSION } from "./version-v1kuAkGj.js"; import { _ as writeSessionSqliteMigrationManifest, a as filterRestoreManifestTargets, d as readSessionSqliteMigrationManifest } from "./doctor-session-sqlite-migration-run-CJtP-RPT.js"; import { t as prepareGithubIssue } from "./github-issue-DCMPc0-s.js"; import fs from "node:fs"; import path from "node:path"; import { randomUUID } from "node:crypto"; //#region src/commands/doctor-session-sqlite-failure.ts /** Sanitized support reports for migration failure recovery. */ function writeSessionSqliteMigrationFailureReports(manifestPath, params) { const manifest = readSessionSqliteMigrationManifest(manifestPath); const { jsonPath, markdownPath } = resolveFailureReportPaths(manifestPath); if (manifest?.failureReports?.githubIssue) return { jsonPath, markdownPath }; const targets = manifest ? params.trustedTargets ? filterRestoreManifestTargets(manifest, params.trustedTargets) : manifest.targets : []; const payload = { generatedAt: (/* @__PURE__ */ new Date()).toISOString(), manifestPath: sanitizeFailureReportText(shortenFailureReportPath(manifestPath)), reason: params.reason, recoveryCommand: "openclaw doctor --session-sqlite recover --github-issue", restoreStatus: manifest?.restore?.status ?? "not_attempted", runId: manifest?.runId ?? path.basename(manifestPath, ".json"), targets: targets.map((target) => ({ agentId: sanitizeFailureReportText(target.agentId), completedMoves: target.completedMoves.length, issues: target.issues.map((issue) => ({ code: issue.code, message: sanitizeFailureIssueMessage(issue, target), ...issue.sessionKey ? { sessionKey: redactSessionKey(issue.sessionKey) } : {} })), plannedMoves: target.plannedMoves.length, sqlitePath: sanitizeFailureReportText(shortenFailureReportPath(target.sqlitePath)), storePath: sanitizeFailureReportText(shortenFailureReportPath(target.storePath)), validationBeforeArchive: target.validationBeforeArchive })), version: VERSION }; fs.writeFileSync(jsonPath, `${JSON.stringify(payload, null, 2)}\n`, { mode: 384 }); fs.writeFileSync(markdownPath, renderFailureMarkdown(payload), { mode: 384 }); if (manifest) { manifest.failureReports = { ...manifest.failureReports?.githubIssue ? { githubIssue: manifest.failureReports.githubIssue } : {}, jsonPath, markdownPath }; writeSessionSqliteMigrationManifest({ manifest, manifestPath }); } return { jsonPath, markdownPath }; } function createSessionSqliteMigrationFailureIssue(manifestPath, trustedTargets) { const manifest = readSessionSqliteMigrationManifest(manifestPath); if (!manifest) return; const title = (manifest.failureReports?.githubIssue)?.title ?? `Session SQLite migration recovery report (${manifest.runId})`; const bodyPath = manifest.failureReports ? resolveFailureReportPaths(manifestPath).markdownPath : void 0; const targets = trustedTargets ? filterRestoreManifestTargets(manifest, trustedTargets) : manifest.targets; const persistedBody = bodyPath ? readFailureMarkdown(bodyPath) : void 0; if (bodyPath && !persistedBody) return; const body = [ "OpenClaw doctor generated this sanitized report from a local session SQLite migration recovery.", "", persistedBody ?? renderFailureMarkdown({ generatedAt: (/* @__PURE__ */ new Date()).toISOString(), manifestPath: sanitizeFailureReportText(shortenFailureReportPath(manifestPath)), reason: "session SQLite migration failed", recoveryCommand: "openclaw doctor --session-sqlite recover --github-issue", restoreStatus: manifest.restore?.status ?? "not_attempted", runId: manifest.runId, targets: targets.map((target) => ({ agentId: sanitizeFailureReportText(target.agentId), completedMoves: target.completedMoves.length, issues: target.issues.map((issue) => ({ code: issue.code, message: sanitizeFailureIssueMessage(issue, target) })), plannedMoves: target.plannedMoves.length, sqlitePath: sanitizeFailureReportText(shortenFailureReportPath(target.sqlitePath)), storePath: sanitizeFailureReportText(shortenFailureReportPath(target.storePath)), validationBeforeArchive: target.validationBeforeArchive })), version: VERSION }) ].join("\n"); return { body: truncateUtf16Safe(body, 2e4), ...bodyPath ? { bodyPath } : {}, title }; } /** Claims one exact support payload before any request or browser handoff can publish it. */ function claimSessionSqliteMigrationGithubIssue(manifestPath, issue, authority) { authority.assertCurrent(); const manifest = readSessionSqliteMigrationManifest(manifestPath); if (!manifest?.failureReports) return; if (manifest.failureReports.githubIssue) return { issue: manifest.failureReports.githubIssue, status: "existing" }; const currentIssue = createSessionSqliteMigrationFailureIssue(manifestPath); if (!currentIssue || prepareGithubIssue(currentIssue).marker !== issue.marker) return; const claimed = { marker: issue.marker, status: "attempted", title: issue.title }; manifest.manifestVersion = 4; manifest.failureReports.githubIssue = claimed; writeSessionSqliteMigrationManifest({ manifest, manifestPath }); return { issue: claimed, status: "claimed" }; } /** Releases a claim only after the caller proves no public request or browser handoff occurred. */ function clearSessionSqliteMigrationGithubIssueClaim(manifestPath, marker, authority) { authority.assertCurrent(); const manifest = readSessionSqliteMigrationManifest(manifestPath); const failureReports = manifest?.failureReports; const issue = failureReports?.githubIssue; if (!manifest || !failureReports || !issue || issue.marker !== marker || issue.status !== "attempted") return false; delete failureReports.githubIssue; writeSessionSqliteMigrationManifest({ manifest, manifestPath }); return true; } function readFailureMarkdown(filePath) { try { return fs.readFileSync(filePath, "utf8"); } catch { return; } } function resolveFailureReportPaths(manifestPath) { return { jsonPath: manifestPath.replace(/\.json$/u, ".failure.json"), markdownPath: manifestPath.replace(/\.json$/u, ".failure.md") }; } function renderFailureMarkdown(payload) { const lines = [ "# Session SQLite Migration Failure", "", `- Run: ${payload.runId}`, `- Generated: ${payload.generatedAt}`, `- OpenClaw version: ${payload.version}`, `- Reason: ${sanitizeFailureReportText(payload.reason)}`, `- Restore status: ${payload.restoreStatus}`, `- Recovery command: \`${payload.recoveryCommand}\``, "", "## Targets" ]; for (const target of payload.targets) { lines.push("", `### ${target.agentId}`, "", `- Store: ${target.storePath}`, `- SQLite: ${target.sqlitePath}`, `- Planned moves: ${target.plannedMoves}`, `- Completed moves: ${target.completedMoves}`, `- Validation before archive: ${target.validationBeforeArchive}`, `- Issues: ${target.issues.length}`); for (const issue of target.issues.slice(0, 10)) lines.push(` - [${issue.code}] ${issue.sessionKey ? `${issue.sessionKey}: ` : ""}${issue.message}`); } lines.push(""); return `${lines.join("\n")}\n`; } function sanitizeFailureReportText(value) { const sanitized = value.replace(/[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}/g, "[redacted-email]").replace(/(api[_-]?key|token|secret|password)[=-][A-Za-z0-9._-]+/gi, "$1-[redacted]").replace(/(api[_-]?key|token|secret|password)=\S+/gi, "$1=[redacted]"); return truncateUtf16Safe(sanitized, 500); } function shortenFailureReportPath(filePath) { const home = process.env.HOME; if (home && filePath.startsWith(`${home}${path.sep}`)) return `~${path.sep}${path.relative(home, filePath)}`; return filePath; } function sanitizeFailureIssueMessage(issue, target) { let message = issue.message; for (const filePath of [ target.storePath, target.sqlitePath, ...target.plannedMoves.flatMap((move) => [move.sourcePath, move.archivePath]), ...target.completedMoves.flatMap((move) => [move.sourcePath, move.archivePath]) ]) message = message.split(filePath).join(shortenFailureReportPath(filePath)); if (issue.sessionKey) message = message.split(issue.sessionKey).join(redactSessionKey(issue.sessionKey)); message = redactAbsoluteHomePaths(message); return sanitizeFailureReportText(message); } function redactSessionKey(sessionKey) { if (!sessionKey.trim()) return "[redacted-session-key]"; return `[redacted-session-key:${randomUUID().slice(0, 8)}]`; } function redactAbsoluteHomePaths(value) { const home = process.env.HOME; if (!home) return value; return value.split(home).join("~"); } //#endregion export { writeSessionSqliteMigrationFailureReports as i, clearSessionSqliteMigrationGithubIssueClaim as n, createSessionSqliteMigrationFailureIssue as r, claimSessionSqliteMigrationGithubIssue as t };