import type { OpenClawConfig } from "../config/config.js"; import type { SessionAcpMeta } from "../config/sessions/types.js"; import { logVerbose } from "../globals.js"; import { getAcpSessionManager } from "./control-plane/manager.js"; import { resolveAcpAgentFromSessionKey } from "./control-plane/manager.utils.js"; import { resolveConfiguredAcpBindingSpecBySessionKey } from "./persistent-bindings.resolve.js"; import { buildConfiguredAcpSessionKey, normalizeText, type ConfiguredAcpBindingSpec, type ResolvedConfiguredAcpBinding, } from "./persistent-bindings.types.js"; import { readAcpSessionEntry } from "./runtime/session-meta.js"; function sessionMatchesConfiguredBinding(params: { cfg: OpenClawConfig; spec: ConfiguredAcpBindingSpec; meta: SessionAcpMeta; }): boolean { const desiredAgent = (params.spec.acpAgentId ?? params.spec.agentId).trim().toLowerCase(); const currentAgent = (params.meta.agent ?? "").trim().toLowerCase(); if (!currentAgent || currentAgent !== desiredAgent) { return false; } if (params.meta.mode !== params.spec.mode) { return false; } const desiredBackend = params.spec.backend?.trim() || params.cfg.acp?.backend?.trim() || ""; if (desiredBackend) { const currentBackend = (params.meta.backend ?? "").trim(); if (!currentBackend || currentBackend !== desiredBackend) { return false; } } const desiredCwd = params.spec.cwd?.trim(); if (desiredCwd !== undefined) { const currentCwd = (params.meta.runtimeOptions?.cwd ?? params.meta.cwd ?? "").trim(); if (desiredCwd !== currentCwd) { return false; } } return true; } export async function ensureConfiguredAcpBindingSession(params: { cfg: OpenClawConfig; spec: ConfiguredAcpBindingSpec; }): Promise<{ ok: true; sessionKey: string } | { ok: false; sessionKey: string; error: string }> { const sessionKey = buildConfiguredAcpSessionKey(params.spec); const acpManager = getAcpSessionManager(); try { const resolution = acpManager.resolveSession({ cfg: params.cfg, sessionKey, }); if ( resolution.kind === "ready" && sessionMatchesConfiguredBinding({ cfg: params.cfg, spec: params.spec, meta: resolution.meta, }) ) { return { ok: true, sessionKey, }; } if (resolution.kind !== "none") { await acpManager.closeSession({ cfg: params.cfg, sessionKey, reason: "config-binding-reconfigure", clearMeta: false, allowBackendUnavailable: true, requireAcpSession: false, }); } await acpManager.initializeSession({ cfg: params.cfg, sessionKey, agent: params.spec.acpAgentId ?? params.spec.agentId, mode: params.spec.mode, cwd: params.spec.cwd, backendId: params.spec.backend, }); return { ok: true, sessionKey, }; } catch (error) { const message = error instanceof Error ? error.message : String(error); logVerbose( `acp-configured-binding: failed ensuring ${params.spec.channel}:${params.spec.accountId}:${params.spec.conversationId} -> ${sessionKey}: ${message}`, ); return { ok: false, sessionKey, error: message, }; } } export async function ensureConfiguredAcpBindingReady(params: { cfg: OpenClawConfig; configuredBinding: ResolvedConfiguredAcpBinding | null; }): Promise<{ ok: true } | { ok: false; error: string }> { if (!params.configuredBinding) { return { ok: true }; } const ensured = await ensureConfiguredAcpBindingSession({ cfg: params.cfg, spec: params.configuredBinding.spec, }); if (ensured.ok) { return { ok: true }; } return { ok: false, error: ensured.error ?? "unknown error", }; } export async function resetAcpSessionInPlace(params: { cfg: OpenClawConfig; sessionKey: string; reason: "new" | "reset"; }): Promise<{ ok: true } | { ok: false; skipped?: boolean; error?: string }> { const sessionKey = params.sessionKey.trim(); if (!sessionKey) { return { ok: false, skipped: true, }; } const meta = readAcpSessionEntry({ cfg: params.cfg, sessionKey, })?.acp; const configuredBinding = !meta || !normalizeText(meta.agent) ? resolveConfiguredAcpBindingSpecBySessionKey({ cfg: params.cfg, sessionKey, }) : null; if (!meta) { if (configuredBinding) { const ensured = await ensureConfiguredAcpBindingSession({ cfg: params.cfg, spec: configuredBinding, }); if (ensured.ok) { return { ok: true }; } return { ok: false, error: ensured.error, }; } return { ok: false, skipped: true, }; } const acpManager = getAcpSessionManager(); const agent = normalizeText(meta.agent) ?? configuredBinding?.acpAgentId ?? configuredBinding?.agentId ?? resolveAcpAgentFromSessionKey(sessionKey, "main"); const mode = meta.mode === "oneshot" ? "oneshot" : "persistent"; const runtimeOptions = { ...meta.runtimeOptions }; const cwd = normalizeText(runtimeOptions.cwd ?? meta.cwd); try { await acpManager.closeSession({ cfg: params.cfg, sessionKey, reason: `${params.reason}-in-place-reset`, clearMeta: false, allowBackendUnavailable: true, requireAcpSession: false, }); await acpManager.initializeSession({ cfg: params.cfg, sessionKey, agent, mode, cwd, backendId: normalizeText(meta.backend) ?? normalizeText(params.cfg.acp?.backend), }); const runtimeOptionsPatch = Object.fromEntries( Object.entries(runtimeOptions).filter(([, value]) => value !== undefined), ) as SessionAcpMeta["runtimeOptions"]; if (runtimeOptionsPatch && Object.keys(runtimeOptionsPatch).length > 0) { await acpManager.updateSessionRuntimeOptions({ cfg: params.cfg, sessionKey, patch: runtimeOptionsPatch, }); } return { ok: true }; } catch (error) { const message = error instanceof Error ? error.message : String(error); logVerbose(`acp-configured-binding: failed reset for ${sessionKey}: ${message}`); return { ok: false, error: message, }; } }