mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-27 18:06:00 +00:00
fix: align sqlite branch after rebase
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -1,3 +1,5 @@
|
||||
import fsSync, { promises as fs } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { DEFAULT_SUBAGENT_ARCHIVE_AFTER_MINUTES } from "../config/agent-limits.js";
|
||||
import { getRuntimeConfig } from "../config/config.js";
|
||||
import {
|
||||
@@ -203,6 +205,81 @@ export function resolveSubagentRunOrphanReason(params: {
|
||||
}
|
||||
}
|
||||
|
||||
function isResolvedChildPath(params: { childPath: string; rootPath: string }) {
|
||||
const rootWithSep = params.rootPath.endsWith(path.sep)
|
||||
? params.rootPath
|
||||
: `${params.rootPath}${path.sep}`;
|
||||
return params.childPath.startsWith(rootWithSep);
|
||||
}
|
||||
|
||||
export async function safeRemoveAttachmentsDir(entry: SubagentRunRecord): Promise<void> {
|
||||
if (!entry.attachmentsDir || !entry.attachmentsRootDir) {
|
||||
return;
|
||||
}
|
||||
|
||||
const resolveReal = async (targetPath: string): Promise<string | null> => {
|
||||
try {
|
||||
return await fs.realpath(targetPath);
|
||||
} catch (err) {
|
||||
if ((err as NodeJS.ErrnoException | undefined)?.code === "ENOENT") {
|
||||
return null;
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
const [rootReal, dirReal] = await Promise.all([
|
||||
resolveReal(entry.attachmentsRootDir),
|
||||
resolveReal(entry.attachmentsDir),
|
||||
]);
|
||||
if (!dirReal) {
|
||||
return;
|
||||
}
|
||||
|
||||
const rootBase = rootReal ?? path.resolve(entry.attachmentsRootDir);
|
||||
if (!isResolvedChildPath({ childPath: dirReal, rootPath: rootBase })) {
|
||||
return;
|
||||
}
|
||||
await fs.rm(dirReal, { recursive: true, force: true });
|
||||
} catch {
|
||||
// best effort
|
||||
}
|
||||
}
|
||||
|
||||
function safeRemoveAttachmentsDirSync(entry: SubagentRunRecord): void {
|
||||
if (!entry.attachmentsDir || !entry.attachmentsRootDir) {
|
||||
return;
|
||||
}
|
||||
|
||||
const resolveReal = (targetPath: string): string | null => {
|
||||
try {
|
||||
return fsSync.realpathSync.native(targetPath);
|
||||
} catch (err) {
|
||||
if ((err as NodeJS.ErrnoException | undefined)?.code === "ENOENT") {
|
||||
return null;
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
const rootReal = resolveReal(entry.attachmentsRootDir);
|
||||
const dirReal = resolveReal(entry.attachmentsDir);
|
||||
if (!dirReal) {
|
||||
return;
|
||||
}
|
||||
|
||||
const rootBase = rootReal ?? path.resolve(entry.attachmentsRootDir);
|
||||
if (!isResolvedChildPath({ childPath: dirReal, rootPath: rootBase })) {
|
||||
return;
|
||||
}
|
||||
fsSync.rmSync(dirReal, { recursive: true, force: true });
|
||||
} catch {
|
||||
// best effort
|
||||
}
|
||||
}
|
||||
|
||||
export function reconcileOrphanedRun(params: {
|
||||
runId: string;
|
||||
entry: SubagentRunRecord;
|
||||
@@ -243,6 +320,11 @@ export function reconcileOrphanedRun(params: {
|
||||
params.entry.cleanupCompletedAt = now;
|
||||
changed = true;
|
||||
}
|
||||
const shouldDeleteAttachments =
|
||||
params.entry.cleanup === "delete" || !params.entry.retainAttachmentsOnKeep;
|
||||
if (shouldDeleteAttachments) {
|
||||
safeRemoveAttachmentsDirSync(params.entry);
|
||||
}
|
||||
const removed = params.runs.delete(params.runId);
|
||||
params.resumedRuns.delete(params.runId);
|
||||
if (!removed && !changed) {
|
||||
|
||||
@@ -28,6 +28,7 @@ const gatewayMocks = vi.hoisted(() => ({
|
||||
const helperMocks = vi.hoisted(() => ({
|
||||
persistSubagentSessionTiming: vi.fn(async () => {}),
|
||||
logAnnounceGiveUp: vi.fn(),
|
||||
safeRemoveAttachmentsDir: vi.fn(async () => {}),
|
||||
}));
|
||||
|
||||
const runtimeMocks = vi.hoisted(() => ({
|
||||
@@ -93,6 +94,7 @@ vi.mock("./subagent-registry-helpers.js", () => ({
|
||||
capFrozenResultText: (text: string) => text.trim(),
|
||||
logAnnounceGiveUp: helperMocks.logAnnounceGiveUp,
|
||||
persistSubagentSessionTiming: helperMocks.persistSubagentSessionTiming,
|
||||
safeRemoveAttachmentsDir: helperMocks.safeRemoveAttachmentsDir,
|
||||
resolveAnnounceRetryDelayMs: (retryCount: number) =>
|
||||
Math.min(1_000 * 2 ** Math.max(0, retryCount - 1), 8_000),
|
||||
}));
|
||||
|
||||
@@ -46,6 +46,7 @@ import {
|
||||
reconcileOrphanedRun,
|
||||
resolveAnnounceRetryDelayMs,
|
||||
resolveSubagentRunOrphanReason,
|
||||
safeRemoveAttachmentsDir,
|
||||
} from "./subagent-registry-helpers.js";
|
||||
import { createSubagentRegistryLifecycleController } from "./subagent-registry-lifecycle.js";
|
||||
import { subagentRuns } from "./subagent-registry-memory.js";
|
||||
|
||||
@@ -48,7 +48,6 @@ import { isMessagingToolSendAction } from "../../agents/pi-embedded-messaging.js
|
||||
import { runEmbeddedPiAgent } from "../../agents/pi-embedded.js";
|
||||
import { buildAgentRuntimeOutcomePlan } from "../../agents/runtime-plan/build.js";
|
||||
import {
|
||||
deleteSessionEntry,
|
||||
getSessionEntry,
|
||||
resolveGroupSessionKey,
|
||||
type SessionEntry,
|
||||
@@ -2747,8 +2746,6 @@ export async function runAgentTurnWithFallback(params: {
|
||||
const isCompactionFailure = !isBilling && isCompactionFailureError(message);
|
||||
const providerRequestError =
|
||||
!isBilling && !shouldSurfaceToControlUi ? classifyProviderRequestError(err) : undefined;
|
||||
const isSessionCorruption = /function call turn comes immediately after/i.test(message);
|
||||
const isRoleOrderingError = /incorrect role information|roles must alternate/i.test(message);
|
||||
const isTransientHttp = isTransientHttpError(message);
|
||||
|
||||
if (isReplyOperationRestartAbort(params.replyOperation)) {
|
||||
@@ -2812,51 +2809,6 @@ export async function runAgentTurnWithFallback(params: {
|
||||
}),
|
||||
};
|
||||
}
|
||||
if (isRoleOrderingError) {
|
||||
const didReset = await params.resetSessionAfterRoleOrderingConflict(message);
|
||||
if (didReset) {
|
||||
params.replyOperation?.fail("run_failed", err);
|
||||
return {
|
||||
kind: "final",
|
||||
payload: markAgentRunFailureReplyPayload({
|
||||
text: "⚠️ Message ordering conflict. I've reset the conversation - please try again.",
|
||||
}),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-recover from Gemini session corruption by resetting the session
|
||||
if (isSessionCorruption && params.sessionKey) {
|
||||
const sessionKey = params.sessionKey;
|
||||
defaultRuntime.error(
|
||||
`Session history corrupted (Gemini function call ordering). Resetting session: ${params.sessionKey}`,
|
||||
);
|
||||
|
||||
try {
|
||||
// Keep the in-memory snapshot consistent with the SQLite row reset.
|
||||
if (params.activeSessionStore) {
|
||||
delete params.activeSessionStore[sessionKey];
|
||||
}
|
||||
|
||||
deleteSessionEntry({
|
||||
agentId: sessionAgentId,
|
||||
sessionKey,
|
||||
});
|
||||
} catch (cleanupErr) {
|
||||
defaultRuntime.error(
|
||||
`Failed to reset corrupted session ${params.sessionKey}: ${String(cleanupErr)}`,
|
||||
);
|
||||
}
|
||||
|
||||
params.replyOperation?.fail("session_corruption_reset", err);
|
||||
return {
|
||||
kind: "final",
|
||||
payload: markAgentRunFailureReplyPayload({
|
||||
text: "⚠️ Session history was corrupted. I've reset the conversation - please try again!",
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
if (providerRequestError) {
|
||||
params.replyOperation?.fail("run_failed", err);
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user