refactor: centralize acp session resolution guards

This commit is contained in:
Peter Steinberger
2026-03-08 17:11:05 +00:00
parent 25d0aa7296
commit f6cb77134c
3 changed files with 50 additions and 101 deletions

View File

@@ -49,7 +49,9 @@ import {
normalizeAcpErrorCode,
normalizeActorKey,
normalizeSessionKey,
requireReadySessionMeta,
resolveAcpAgentFromSessionKey,
resolveAcpSessionResolutionError,
resolveMissingMetaError,
resolveRuntimeIdleTtlMs,
} from "./manager.utils.js";
@@ -332,15 +334,7 @@ export class AcpSessionManager {
cfg: params.cfg,
sessionKey,
});
if (resolution.kind === "none") {
throw new AcpRuntimeError(
"ACP_SESSION_INIT_FAILED",
`Session is not ACP-enabled: ${sessionKey}`,
);
}
if (resolution.kind === "stale") {
throw resolution.error;
}
const resolvedMeta = requireReadySessionMeta(resolution);
const {
runtime,
handle: ensuredHandle,
@@ -348,7 +342,7 @@ export class AcpSessionManager {
} = await this.ensureRuntimeHandle({
cfg: params.cfg,
sessionKey,
meta: resolution.meta,
meta: resolvedMeta,
});
let handle = ensuredHandle;
let meta = ensuredMeta;
@@ -414,19 +408,11 @@ export class AcpSessionManager {
cfg: params.cfg,
sessionKey,
});
if (resolution.kind === "none") {
throw new AcpRuntimeError(
"ACP_SESSION_INIT_FAILED",
`Session is not ACP-enabled: ${sessionKey}`,
);
}
if (resolution.kind === "stale") {
throw resolution.error;
}
const resolvedMeta = requireReadySessionMeta(resolution);
const { runtime, handle, meta } = await this.ensureRuntimeHandle({
cfg: params.cfg,
sessionKey,
meta: resolution.meta,
meta: resolvedMeta,
});
const capabilities = await this.resolveRuntimeCapabilities({ runtime, handle });
if (!capabilities.controls.includes("session/set_mode") || !runtime.setMode) {
@@ -479,19 +465,11 @@ export class AcpSessionManager {
cfg: params.cfg,
sessionKey,
});
if (resolution.kind === "none") {
throw new AcpRuntimeError(
"ACP_SESSION_INIT_FAILED",
`Session is not ACP-enabled: ${sessionKey}`,
);
}
if (resolution.kind === "stale") {
throw resolution.error;
}
const resolvedMeta = requireReadySessionMeta(resolution);
const { runtime, handle, meta } = await this.ensureRuntimeHandle({
cfg: params.cfg,
sessionKey,
meta: resolution.meta,
meta: resolvedMeta,
});
const inferredPatch = inferRuntimeOptionPatchFromConfigOption(key, value);
const capabilities = await this.resolveRuntimeCapabilities({ runtime, handle });
@@ -558,17 +536,9 @@ export class AcpSessionManager {
cfg: params.cfg,
sessionKey,
});
if (resolution.kind === "none") {
throw new AcpRuntimeError(
"ACP_SESSION_INIT_FAILED",
`Session is not ACP-enabled: ${sessionKey}`,
);
}
if (resolution.kind === "stale") {
throw resolution.error;
}
const resolvedMeta = requireReadySessionMeta(resolution);
const nextOptions = mergeRuntimeOptions({
current: resolveRuntimeOptionsFromMeta(resolution.meta),
current: resolveRuntimeOptionsFromMeta(resolvedMeta),
patch: validatedPatch,
});
await this.persistRuntimeOptions({
@@ -594,19 +564,11 @@ export class AcpSessionManager {
cfg: params.cfg,
sessionKey,
});
if (resolution.kind === "none") {
throw new AcpRuntimeError(
"ACP_SESSION_INIT_FAILED",
`Session is not ACP-enabled: ${sessionKey}`,
);
}
if (resolution.kind === "stale") {
throw resolution.error;
}
const resolvedMeta = requireReadySessionMeta(resolution);
const { runtime, handle } = await this.ensureRuntimeHandle({
cfg: params.cfg,
sessionKey,
meta: resolution.meta,
meta: resolvedMeta,
});
await withAcpRuntimeErrorBoundary({
run: async () =>
@@ -638,15 +600,7 @@ export class AcpSessionManager {
cfg: input.cfg,
sessionKey,
});
if (resolution.kind === "none") {
throw new AcpRuntimeError(
"ACP_SESSION_INIT_FAILED",
`Session is not ACP-enabled: ${sessionKey}`,
);
}
if (resolution.kind === "stale") {
throw resolution.error;
}
const resolvedMeta = requireReadySessionMeta(resolution);
const {
runtime,
@@ -655,7 +609,7 @@ export class AcpSessionManager {
} = await this.ensureRuntimeHandle({
cfg: input.cfg,
sessionKey,
meta: resolution.meta,
meta: resolvedMeta,
});
let handle = ensuredHandle;
const meta = ensuredMeta;
@@ -810,19 +764,11 @@ export class AcpSessionManager {
cfg: params.cfg,
sessionKey,
});
if (resolution.kind === "none") {
throw new AcpRuntimeError(
"ACP_SESSION_INIT_FAILED",
`Session is not ACP-enabled: ${sessionKey}`,
);
}
if (resolution.kind === "stale") {
throw resolution.error;
}
const resolvedMeta = requireReadySessionMeta(resolution);
const { runtime, handle } = await this.ensureRuntimeHandle({
cfg: params.cfg,
sessionKey,
meta: resolution.meta,
meta: resolvedMeta,
});
try {
await withAcpRuntimeErrorBoundary({
@@ -868,27 +814,17 @@ export class AcpSessionManager {
cfg: input.cfg,
sessionKey,
});
if (resolution.kind === "none") {
const resolutionError = resolveAcpSessionResolutionError(resolution);
if (resolutionError) {
if (input.requireAcpSession ?? true) {
throw new AcpRuntimeError(
"ACP_SESSION_INIT_FAILED",
`Session is not ACP-enabled: ${sessionKey}`,
);
}
return {
runtimeClosed: false,
metaCleared: false,
};
}
if (resolution.kind === "stale") {
if (input.requireAcpSession ?? true) {
throw resolution.error;
throw resolutionError;
}
return {
runtimeClosed: false,
metaCleared: false,
};
}
const meta = resolution.meta;
let runtimeClosed = false;
let runtimeNotice: string | undefined;
@@ -896,7 +832,7 @@ export class AcpSessionManager {
const { runtime, handle } = await this.ensureRuntimeHandle({
cfg: input.cfg,
sessionKey,
meta: resolution.meta,
meta,
});
await withAcpRuntimeErrorBoundary({
run: async () =>

View File

@@ -2,6 +2,7 @@ import type { OpenClawConfig } from "../../config/config.js";
import type { SessionAcpMeta } from "../../config/sessions/types.js";
import { normalizeAgentId, parseAgentSessionKey } from "../../routing/session-key.js";
import { ACP_ERROR_CODES, AcpRuntimeError } from "../runtime/errors.js";
import type { AcpSessionResolution } from "./manager.types.js";
export function resolveAcpAgentFromSessionKey(sessionKey: string, fallback = "main"): string {
const parsed = parseAgentSessionKey(sessionKey);
@@ -15,6 +16,29 @@ export function resolveMissingMetaError(sessionKey: string): AcpRuntimeError {
);
}
export function resolveAcpSessionResolutionError(
resolution: AcpSessionResolution,
): AcpRuntimeError | null {
if (resolution.kind === "ready") {
return null;
}
if (resolution.kind === "stale") {
return resolution.error;
}
return new AcpRuntimeError(
"ACP_SESSION_INIT_FAILED",
`Session is not ACP-enabled: ${resolution.sessionKey}`,
);
}
export function requireReadySessionMeta(resolution: AcpSessionResolution): SessionAcpMeta {
const error = resolveAcpSessionResolutionError(resolution);
if (error) {
throw error;
}
return resolution.meta;
}
export function normalizeSessionKey(sessionKey: string): string {
return sessionKey.trim();
}

View File

@@ -1,5 +1,6 @@
import { randomUUID } from "node:crypto";
import { getAcpSessionManager } from "../../../acp/control-plane/manager.js";
import { resolveAcpSessionResolutionError } from "../../../acp/control-plane/manager.utils.js";
import {
cleanupFailedAcpSpawn,
type AcpSpawnRuntimeCloseHandle,
@@ -10,7 +11,6 @@ import {
resolveAcpDispatchPolicyError,
resolveAcpDispatchPolicyMessage,
} from "../../../acp/policy.js";
import { AcpRuntimeError } from "../../../acp/runtime/errors.js";
import {
resolveAcpSessionCwd,
resolveAcpThreadSessionDetailLines,
@@ -390,24 +390,13 @@ function resolveAcpSessionForCommandOrStop(params: {
cfg: params.cfg,
sessionKey: params.sessionKey,
});
if (resolved.kind === "none") {
const error = resolveAcpSessionResolutionError(resolved);
if (error) {
return stopWithText(
collectAcpErrorText({
error: new AcpRuntimeError(
"ACP_SESSION_INIT_FAILED",
`Session is not ACP-enabled: ${params.sessionKey}`,
),
error,
fallbackCode: "ACP_SESSION_INIT_FAILED",
fallbackMessage: "Session is not ACP-enabled.",
}),
);
}
if (resolved.kind === "stale") {
return stopWithText(
collectAcpErrorText({
error: resolved.error,
fallbackCode: "ACP_SESSION_INIT_FAILED",
fallbackMessage: resolved.error.message,
fallbackMessage: error.message,
}),
);
}