| import type { OpenClawConfig } from "../../config/config.js"; |
| import { |
| canonicalizeMainSessionAlias, |
| resolveMainSessionKey, |
| } from "../../config/sessions/main-session.js"; |
| import type { SessionAcpMeta } from "../../config/sessions/types.js"; |
| import { |
| normalizeAgentId, |
| normalizeMainKey, |
| 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); |
| return normalizeAgentId(parsed?.agentId ?? fallback); |
| } |
|
|
| export function resolveMissingMetaError(sessionKey: string): AcpRuntimeError { |
| return new AcpRuntimeError( |
| "ACP_SESSION_INIT_FAILED", |
| `ACP metadata is missing for ${sessionKey}. Recreate this ACP session with /acp spawn and rebind the thread.`, |
| ); |
| } |
|
|
| 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 { |
| if (resolution.kind === "ready") { |
| return resolution.meta; |
| } |
| throw resolveAcpSessionResolutionError(resolution); |
| } |
|
|
| export function normalizeSessionKey(sessionKey: string): string { |
| return sessionKey.trim(); |
| } |
|
|
| export function canonicalizeAcpSessionKey(params: { |
| cfg: OpenClawConfig; |
| sessionKey: string; |
| }): string { |
| const normalized = normalizeSessionKey(params.sessionKey); |
| if (!normalized) { |
| return ""; |
| } |
| const lowered = normalized.toLowerCase(); |
| if (lowered === "global" || lowered === "unknown") { |
| return lowered; |
| } |
| const parsed = parseAgentSessionKey(lowered); |
| if (parsed) { |
| return canonicalizeMainSessionAlias({ |
| cfg: params.cfg, |
| agentId: parsed.agentId, |
| sessionKey: lowered, |
| }); |
| } |
| const mainKey = normalizeMainKey(params.cfg.session?.mainKey); |
| if (lowered === "main" || lowered === mainKey) { |
| return resolveMainSessionKey(params.cfg); |
| } |
| return lowered; |
| } |
|
|
| export function normalizeActorKey(sessionKey: string): string { |
| return sessionKey.trim().toLowerCase(); |
| } |
|
|
| export function normalizeAcpErrorCode(code: string | undefined): AcpRuntimeError["code"] { |
| if (!code) { |
| return "ACP_TURN_FAILED"; |
| } |
| const normalized = code.trim().toUpperCase(); |
| for (const allowed of ACP_ERROR_CODES) { |
| if (allowed === normalized) { |
| return allowed; |
| } |
| } |
| return "ACP_TURN_FAILED"; |
| } |
|
|
| export function createUnsupportedControlError(params: { |
| backend: string; |
| control: string; |
| }): AcpRuntimeError { |
| return new AcpRuntimeError( |
| "ACP_BACKEND_UNSUPPORTED_CONTROL", |
| `ACP backend "${params.backend}" does not support ${params.control}.`, |
| ); |
| } |
|
|
| export function resolveRuntimeIdleTtlMs(cfg: OpenClawConfig): number { |
| const ttlMinutes = cfg.acp?.runtime?.ttlMinutes; |
| if (typeof ttlMinutes !== "number" || !Number.isFinite(ttlMinutes) || ttlMinutes <= 0) { |
| return 0; |
| } |
| return Math.round(ttlMinutes * 60 * 1000); |
| } |
|
|
| export function hasLegacyAcpIdentityProjection(meta: SessionAcpMeta): boolean { |
| const raw = meta as Record<string, unknown>; |
| return ( |
| Object.hasOwn(raw, "backendSessionId") || |
| Object.hasOwn(raw, "agentSessionId") || |
| Object.hasOwn(raw, "sessionIdsProvisional") |
| ); |
| } |
|
|