mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-27 12:56:01 -06:00
refactor: extract ACP close session flow
Refactor ACP close-session ownership by extracting the runtime close/recovery lifecycle into `manager.close-session.ts`. Verification: - `pnpm test src/acp/control-plane/manager.test.ts src/acp/control-plane/manager.runtime-config.test.ts src/acp/control-plane/manager.runtime-handles.test.ts` - `pnpm tsgo:prod` - `pnpm check:test-types` - `node scripts/run-oxlint.mjs src/acp/control-plane/manager.core.ts src/acp/control-plane/manager.close-session.ts` - `pnpm format:check src/acp/control-plane/manager.core.ts src/acp/control-plane/manager.close-session.ts` - `git diff --check` - `.agents/skills/autoreview/scripts/autoreview --mode local` - GitHub PR checks for #88744 passed Real behavior proof: Behavior addressed: ACP close-session ownership moved out of `AcpSessionManager` without changing close/recovery behavior. Real environment tested: Local OpenClaw checkout, Node/pnpm repo toolchain, GitHub Actions PR CI. Exact steps or command run after this patch: Focused ACP manager tests covering close-session behavior, runtime config, and runtime handles, plus prod/test type checks, lint, format, diff check, autoreview, and PR CI. Evidence after fix: All listed local commands passed, autoreview reported no accepted/actionable findings, and GitHub PR checks passed. Observed result after fix: `manager.core.ts` dropped from 1149 LOC to 1038 LOC while close-session runtime lifecycle handling lives in `manager.close-session.ts`. What was not tested: Live ACP backend close/recovery against a real external ACP provider.
This commit is contained in:
committed by
GitHub
parent
465a5456fe
commit
2f449285b9
@@ -0,0 +1,154 @@
|
||||
import {
|
||||
identityHasStableSessionId,
|
||||
resolveSessionIdentityFromMeta,
|
||||
} from "@openclaw/acp-core/runtime/session-identity";
|
||||
import type { AcpRuntime, AcpRuntimeHandle } from "@openclaw/acp-core/runtime/types";
|
||||
import type { OpenClawConfig } from "../../config/types.openclaw.js";
|
||||
import { toAcpRuntimeError, withAcpRuntimeErrorBoundary } from "../runtime/errors.js";
|
||||
import type { ManagerRuntimeHandleCache } from "./manager.runtime-handle-cache.js";
|
||||
import {
|
||||
discardPersistedManagerRuntimeState,
|
||||
isRecoverableManagerAcpxExitError,
|
||||
tryPrepareFreshManagerRuntimeSession,
|
||||
} from "./manager.runtime-resume-state.js";
|
||||
import type {
|
||||
AcpCloseSessionInput,
|
||||
AcpCloseSessionResult,
|
||||
AcpSessionManagerDeps,
|
||||
AcpSessionResolution,
|
||||
SessionAcpMeta,
|
||||
WriteManagerSessionMeta,
|
||||
} from "./manager.types.js";
|
||||
import { requireReadySessionMeta, resolveAcpSessionResolutionError } from "./manager.utils.js";
|
||||
|
||||
type EnsureRuntimeHandle = (params: {
|
||||
cfg: OpenClawConfig;
|
||||
sessionKey: string;
|
||||
meta: SessionAcpMeta;
|
||||
}) => Promise<{ runtime: AcpRuntime; handle: AcpRuntimeHandle; meta: SessionAcpMeta }>;
|
||||
|
||||
export async function runManagerCloseSession(params: {
|
||||
input: AcpCloseSessionInput;
|
||||
sessionKey: string;
|
||||
deps: Pick<AcpSessionManagerDeps, "getRuntimeBackend">;
|
||||
runtimeHandles: ManagerRuntimeHandleCache;
|
||||
resolveSession: (params: { cfg: OpenClawConfig; sessionKey: string }) => AcpSessionResolution;
|
||||
ensureRuntimeHandle: EnsureRuntimeHandle;
|
||||
writeSessionMeta: WriteManagerSessionMeta;
|
||||
}): Promise<AcpCloseSessionResult> {
|
||||
const { input, sessionKey } = params;
|
||||
const resolution = params.resolveSession({
|
||||
cfg: input.cfg,
|
||||
sessionKey,
|
||||
});
|
||||
const resolutionError = resolveAcpSessionResolutionError(resolution);
|
||||
if (resolutionError) {
|
||||
if (input.requireAcpSession ?? true) {
|
||||
throw resolutionError;
|
||||
}
|
||||
return {
|
||||
runtimeClosed: false,
|
||||
metaCleared: false,
|
||||
};
|
||||
}
|
||||
const meta = requireReadySessionMeta(resolution);
|
||||
const currentIdentity = resolveSessionIdentityFromMeta(meta);
|
||||
const shouldSkipRuntimeClose =
|
||||
input.discardPersistentState &&
|
||||
currentIdentity != null &&
|
||||
!identityHasStableSessionId(currentIdentity);
|
||||
|
||||
let runtimeClosed = false;
|
||||
let runtimeNotice: string | undefined;
|
||||
if (shouldSkipRuntimeClose) {
|
||||
await tryPrepareFreshManagerRuntimeSession({
|
||||
deps: params.deps,
|
||||
cfg: input.cfg,
|
||||
meta,
|
||||
sessionKey,
|
||||
logPrefix: "acp close fast-reset",
|
||||
});
|
||||
params.runtimeHandles.clear(sessionKey);
|
||||
} else {
|
||||
try {
|
||||
const { runtime: ensuredRuntime, handle } = await params.ensureRuntimeHandle({
|
||||
cfg: input.cfg,
|
||||
sessionKey,
|
||||
meta,
|
||||
});
|
||||
await withAcpRuntimeErrorBoundary({
|
||||
run: async () =>
|
||||
await ensuredRuntime.close({
|
||||
handle,
|
||||
reason: input.reason,
|
||||
discardPersistentState: input.discardPersistentState,
|
||||
}),
|
||||
fallbackCode: "ACP_TURN_FAILED",
|
||||
fallbackMessage: "ACP close failed before completion.",
|
||||
});
|
||||
runtimeClosed = true;
|
||||
params.runtimeHandles.clear(sessionKey);
|
||||
} catch (error) {
|
||||
const acpError = toAcpRuntimeError({
|
||||
error,
|
||||
fallbackCode: "ACP_TURN_FAILED",
|
||||
fallbackMessage: "ACP close failed before completion.",
|
||||
});
|
||||
if (
|
||||
input.allowBackendUnavailable &&
|
||||
(acpError.code === "ACP_BACKEND_MISSING" ||
|
||||
acpError.code === "ACP_BACKEND_UNAVAILABLE" ||
|
||||
(input.discardPersistentState && acpError.code === "ACP_SESSION_INIT_FAILED") ||
|
||||
(input.discardPersistentState && acpError.code === "ACP_BACKEND_UNSUPPORTED_CONTROL") ||
|
||||
isRecoverableManagerAcpxExitError(acpError.message))
|
||||
) {
|
||||
if (input.discardPersistentState) {
|
||||
await tryPrepareFreshManagerRuntimeSession({
|
||||
deps: params.deps,
|
||||
cfg: input.cfg,
|
||||
meta,
|
||||
sessionKey,
|
||||
logPrefix: "acp close recovery",
|
||||
missingBackendError: acpError,
|
||||
});
|
||||
}
|
||||
// Treat unavailable backends as terminal for this cached handle so it
|
||||
// cannot continue counting against maxConcurrentSessions.
|
||||
params.runtimeHandles.clear(sessionKey);
|
||||
runtimeNotice = acpError.message;
|
||||
} else {
|
||||
throw acpError;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let metaCleared = false;
|
||||
if (input.discardPersistentState && !input.clearMeta) {
|
||||
await discardPersistedManagerRuntimeState({
|
||||
cfg: input.cfg,
|
||||
sessionKey,
|
||||
writeSessionMeta: params.writeSessionMeta,
|
||||
});
|
||||
}
|
||||
|
||||
if (input.clearMeta) {
|
||||
await params.writeSessionMeta({
|
||||
cfg: input.cfg,
|
||||
sessionKey,
|
||||
mutate: (_current, entry) => {
|
||||
if (!entry) {
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
failOnError: true,
|
||||
});
|
||||
metaCleared = true;
|
||||
}
|
||||
|
||||
return {
|
||||
runtimeClosed,
|
||||
runtimeNotice,
|
||||
metaCleared,
|
||||
};
|
||||
}
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
toAcpRuntimeError,
|
||||
withAcpRuntimeErrorBoundary,
|
||||
} from "../runtime/errors.js";
|
||||
import { runManagerCloseSession } from "./manager.close-session.js";
|
||||
import { reconcileManagerRuntimeSessionIdentifiers } from "./manager.identity-reconcile.js";
|
||||
import {
|
||||
applyManagerRuntimeControls,
|
||||
@@ -29,11 +30,6 @@ import {
|
||||
} from "./manager.runtime-controls.js";
|
||||
import { ManagerRuntimeHandleCache } from "./manager.runtime-handle-cache.js";
|
||||
import { ensureManagerRuntimeHandle } from "./manager.runtime-handle-ensure.js";
|
||||
import {
|
||||
discardPersistedManagerRuntimeState,
|
||||
isRecoverableManagerAcpxExitError,
|
||||
tryPrepareFreshManagerRuntimeSession,
|
||||
} from "./manager.runtime-resume-state.js";
|
||||
import { runManagerTurn } from "./manager.turn-runner.js";
|
||||
import {
|
||||
type AcpCloseSessionInput,
|
||||
@@ -58,7 +54,6 @@ import {
|
||||
normalizeAcpErrorCode,
|
||||
normalizeActorKey,
|
||||
requireReadySessionMeta,
|
||||
resolveAcpSessionResolutionError,
|
||||
resolveMissingMetaError,
|
||||
} from "./manager.utils.js";
|
||||
import {
|
||||
@@ -738,125 +733,19 @@ export class AcpSessionManager {
|
||||
throw new AcpRuntimeError("ACP_SESSION_INIT_FAILED", "ACP session key is required.");
|
||||
}
|
||||
await this.evictIdleRuntimeHandles(input.cfg);
|
||||
return await this.withSessionActor(sessionKey, async () => {
|
||||
const resolution = this.resolveSession({
|
||||
cfg: input.cfg,
|
||||
sessionKey,
|
||||
});
|
||||
const resolutionError = resolveAcpSessionResolutionError(resolution);
|
||||
if (resolutionError) {
|
||||
if (input.requireAcpSession ?? true) {
|
||||
throw resolutionError;
|
||||
}
|
||||
return {
|
||||
runtimeClosed: false,
|
||||
metaCleared: false,
|
||||
};
|
||||
}
|
||||
const meta = requireReadySessionMeta(resolution);
|
||||
const currentIdentity = resolveSessionIdentityFromMeta(meta);
|
||||
const shouldSkipRuntimeClose =
|
||||
input.discardPersistentState &&
|
||||
currentIdentity != null &&
|
||||
!identityHasStableSessionId(currentIdentity);
|
||||
|
||||
let runtimeClosed = false;
|
||||
let runtimeNotice: string | undefined;
|
||||
if (shouldSkipRuntimeClose) {
|
||||
if (input.discardPersistentState) {
|
||||
await tryPrepareFreshManagerRuntimeSession({
|
||||
deps: this.deps,
|
||||
cfg: input.cfg,
|
||||
meta,
|
||||
sessionKey,
|
||||
logPrefix: "acp close fast-reset",
|
||||
});
|
||||
}
|
||||
this.runtimeHandles.clear(sessionKey);
|
||||
} else {
|
||||
try {
|
||||
const { runtime: ensuredRuntime, handle } = await this.ensureRuntimeHandle({
|
||||
cfg: input.cfg,
|
||||
sessionKey,
|
||||
meta,
|
||||
});
|
||||
await withAcpRuntimeErrorBoundary({
|
||||
run: async () =>
|
||||
await ensuredRuntime.close({
|
||||
handle,
|
||||
reason: input.reason,
|
||||
discardPersistentState: input.discardPersistentState,
|
||||
}),
|
||||
fallbackCode: "ACP_TURN_FAILED",
|
||||
fallbackMessage: "ACP close failed before completion.",
|
||||
});
|
||||
runtimeClosed = true;
|
||||
this.runtimeHandles.clear(sessionKey);
|
||||
} catch (error) {
|
||||
const acpError = toAcpRuntimeError({
|
||||
error,
|
||||
fallbackCode: "ACP_TURN_FAILED",
|
||||
fallbackMessage: "ACP close failed before completion.",
|
||||
});
|
||||
if (
|
||||
input.allowBackendUnavailable &&
|
||||
(acpError.code === "ACP_BACKEND_MISSING" ||
|
||||
acpError.code === "ACP_BACKEND_UNAVAILABLE" ||
|
||||
(input.discardPersistentState && acpError.code === "ACP_SESSION_INIT_FAILED") ||
|
||||
(input.discardPersistentState &&
|
||||
acpError.code === "ACP_BACKEND_UNSUPPORTED_CONTROL") ||
|
||||
isRecoverableManagerAcpxExitError(acpError.message))
|
||||
) {
|
||||
if (input.discardPersistentState) {
|
||||
await tryPrepareFreshManagerRuntimeSession({
|
||||
deps: this.deps,
|
||||
cfg: input.cfg,
|
||||
meta,
|
||||
sessionKey,
|
||||
logPrefix: "acp close recovery",
|
||||
missingBackendError: acpError,
|
||||
});
|
||||
}
|
||||
// Treat unavailable backends as terminal for this cached handle so it
|
||||
// cannot continue counting against maxConcurrentSessions.
|
||||
this.runtimeHandles.clear(sessionKey);
|
||||
runtimeNotice = acpError.message;
|
||||
} else {
|
||||
throw acpError;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let metaCleared = false;
|
||||
if (input.discardPersistentState && !input.clearMeta) {
|
||||
await discardPersistedManagerRuntimeState({
|
||||
cfg: input.cfg,
|
||||
return await this.withSessionActor(
|
||||
sessionKey,
|
||||
async () =>
|
||||
await runManagerCloseSession({
|
||||
input,
|
||||
sessionKey,
|
||||
writeSessionMeta: async (writeParams) => await this.writeSessionMeta(writeParams),
|
||||
});
|
||||
}
|
||||
|
||||
if (input.clearMeta) {
|
||||
await this.writeSessionMeta({
|
||||
cfg: input.cfg,
|
||||
sessionKey,
|
||||
mutate: (_current, entry) => {
|
||||
if (!entry) {
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
failOnError: true,
|
||||
});
|
||||
metaCleared = true;
|
||||
}
|
||||
|
||||
return {
|
||||
runtimeClosed,
|
||||
runtimeNotice,
|
||||
metaCleared,
|
||||
};
|
||||
});
|
||||
deps: this.deps,
|
||||
runtimeHandles: this.runtimeHandles,
|
||||
resolveSession: this.resolveSession.bind(this),
|
||||
ensureRuntimeHandle: this.ensureRuntimeHandle.bind(this),
|
||||
writeSessionMeta: this.writeSessionMeta.bind(this),
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
private async ensureRuntimeHandle(params: {
|
||||
|
||||
Reference in New Issue
Block a user