refactor: dedupe runtime and helper flows

This commit is contained in:
Peter Steinberger
2026-03-02 12:53:19 +00:00
parent 5d3f066bbd
commit b02b94673f
17 changed files with 819 additions and 610 deletions

View File

@@ -344,6 +344,40 @@ async function moveToTrashBestEffort(pathname: string): Promise<void> {
}
}
function respondWorkspaceFileInvalid(respond: RespondFn, name: string, reason: string): void {
respond(
false,
undefined,
errorShape(ErrorCodes.INVALID_REQUEST, `unsafe workspace file "${name}" (${reason})`),
);
}
function respondWorkspaceFileUnsafe(respond: RespondFn, name: string): void {
respond(
false,
undefined,
errorShape(ErrorCodes.INVALID_REQUEST, `unsafe workspace file "${name}"`),
);
}
function respondWorkspaceFileMissing(params: {
respond: RespondFn;
agentId: string;
workspaceDir: string;
name: string;
filePath: string;
}): void {
params.respond(
true,
{
agentId: params.agentId,
workspace: params.workspaceDir,
file: { name: params.name, path: params.filePath, missing: true },
},
undefined,
);
}
export const agentsHandlers: GatewayRequestHandlers = {
"agents.list": ({ params, respond }) => {
if (!validateAgentsListParams(params)) {
@@ -601,26 +635,11 @@ export const agentsHandlers: GatewayRequestHandlers = {
allowMissing: true,
});
if (resolvedPath.kind === "invalid") {
respond(
false,
undefined,
errorShape(
ErrorCodes.INVALID_REQUEST,
`unsafe workspace file "${name}" (${resolvedPath.reason})`,
),
);
respondWorkspaceFileInvalid(respond, name, resolvedPath.reason);
return;
}
if (resolvedPath.kind === "missing") {
respond(
true,
{
agentId,
workspace: workspaceDir,
file: { name, path: filePath, missing: true },
},
undefined,
);
respondWorkspaceFileMissing({ respond, agentId, workspaceDir, name, filePath });
return;
}
let safeRead: Awaited<ReturnType<typeof readLocalFileSafely>>;
@@ -628,22 +647,10 @@ export const agentsHandlers: GatewayRequestHandlers = {
safeRead = await readLocalFileSafely({ filePath: resolvedPath.ioPath });
} catch (err) {
if (err instanceof SafeOpenError && err.code === "not-found") {
respond(
true,
{
agentId,
workspace: workspaceDir,
file: { name, path: filePath, missing: true },
},
undefined,
);
respondWorkspaceFileMissing({ respond, agentId, workspaceDir, name, filePath });
return;
}
respond(
false,
undefined,
errorShape(ErrorCodes.INVALID_REQUEST, `unsafe workspace file "${name}"`),
);
respondWorkspaceFileUnsafe(respond, name);
return;
}
respond(
@@ -690,14 +697,7 @@ export const agentsHandlers: GatewayRequestHandlers = {
allowMissing: true,
});
if (resolvedPath.kind === "invalid") {
respond(
false,
undefined,
errorShape(
ErrorCodes.INVALID_REQUEST,
`unsafe workspace file "${name}" (${resolvedPath.reason})`,
),
);
respondWorkspaceFileInvalid(respond, name, resolvedPath.reason);
return;
}
const content = String(params.content ?? "");
@@ -709,11 +709,7 @@ export const agentsHandlers: GatewayRequestHandlers = {
encoding: "utf8",
});
} catch {
respond(
false,
undefined,
errorShape(ErrorCodes.INVALID_REQUEST, `unsafe workspace file "${name}"`),
);
respondWorkspaceFileUnsafe(respond, name);
return;
}
const meta = await statFileSafely(resolvedPath.ioPath);

View File

@@ -284,6 +284,32 @@ async function closeAcpRuntimeForSession(params: {
return undefined;
}
async function cleanupSessionBeforeMutation(params: {
cfg: ReturnType<typeof loadConfig>;
key: string;
target: ReturnType<typeof resolveGatewaySessionStoreTarget>;
entry: SessionEntry | undefined;
legacyKey?: string;
canonicalKey?: string;
reason: "session-reset" | "session-delete";
}) {
const cleanupError = await ensureSessionRuntimeCleanup({
cfg: params.cfg,
key: params.key,
target: params.target,
sessionId: params.entry?.sessionId,
});
if (cleanupError) {
return cleanupError;
}
return await closeAcpRuntimeForSession({
cfg: params.cfg,
sessionKey: params.legacyKey ?? params.canonicalKey ?? params.target.canonicalKey ?? params.key,
entry: params.entry,
reason: params.reason,
});
}
export const sessionsHandlers: GatewayRequestHandlers = {
"sessions.list": ({ params, respond }) => {
if (!assertValidParams(params, validateSessionsListParams, "sessions.list", respond)) {
@@ -445,20 +471,17 @@ export const sessionsHandlers: GatewayRequestHandlers = {
},
);
await triggerInternalHook(hookEvent);
const sessionId = entry?.sessionId;
const cleanupError = await ensureSessionRuntimeCleanup({ cfg, key, target, sessionId });
if (cleanupError) {
respond(false, undefined, cleanupError);
return;
}
const acpCleanupError = await closeAcpRuntimeForSession({
const mutationCleanupError = await cleanupSessionBeforeMutation({
cfg,
sessionKey: legacyKey ?? canonicalKey ?? target.canonicalKey ?? key,
key,
target,
entry,
legacyKey,
canonicalKey,
reason: "session-reset",
});
if (acpCleanupError) {
respond(false, undefined, acpCleanupError);
if (mutationCleanupError) {
respond(false, undefined, mutationCleanupError);
return;
}
let oldSessionId: string | undefined;
@@ -542,22 +565,20 @@ export const sessionsHandlers: GatewayRequestHandlers = {
const deleteTranscript = typeof p.deleteTranscript === "boolean" ? p.deleteTranscript : true;
const { entry, legacyKey, canonicalKey } = loadSessionEntry(key);
const sessionId = entry?.sessionId;
const cleanupError = await ensureSessionRuntimeCleanup({ cfg, key, target, sessionId });
if (cleanupError) {
respond(false, undefined, cleanupError);
return;
}
const acpCleanupError = await closeAcpRuntimeForSession({
const mutationCleanupError = await cleanupSessionBeforeMutation({
cfg,
sessionKey: legacyKey ?? canonicalKey ?? target.canonicalKey ?? key,
key,
target,
entry,
legacyKey,
canonicalKey,
reason: "session-delete",
});
if (acpCleanupError) {
respond(false, undefined, acpCleanupError);
if (mutationCleanupError) {
respond(false, undefined, mutationCleanupError);
return;
}
const sessionId = entry?.sessionId;
const deleted = await updateSessionStore(storePath, (store) => {
const { primaryKey } = migrateAndPruneSessionStoreKey({ cfg, key, store });
const hadEntry = Boolean(store[primaryKey]);