fix: align sqlite branch after rebase

This commit is contained in:
Peter Steinberger
2026-05-17 09:58:53 +01:00
parent 124c0f2e47
commit e7fe29d472
5 changed files with 85 additions and 1094 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -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) {

View File

@@ -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),
}));

View File

@@ -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";

View File

@@ -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 {