Files
openclaw/src/acp/runtime/registry.ts
Peter Steinberger 77f1359612 refactor: extract media and ACP core packages (#88534)
* refactor: extract media and acp core packages

* refactor: remove relocated media and acp sources

* build: wire new core packages into dependency checks

* test: alias new core packages in vitest

* build: keep media sniffer runtime dependency

* docs: refresh plugin sdk api baseline

* fix: keep normalized proposal queries non-empty

* test: keep channel timer tests isolated

* fix: keep rebased plugin checks green

* fix: preserve sms numeric allowlist entries

* test: harden exec foreground timeout failure

* test: remove duplicate skill workshop assertion

* fix: remove channel config lint suppression

* test: refresh lint suppression allowlist
2026-05-31 11:30:33 +01:00

121 lines
3.6 KiB
TypeScript

import type { AcpRuntime } from "@openclaw/acp-core/runtime/types";
import { normalizeOptionalLowercaseString } from "@openclaw/normalization-core/string-coerce";
import { resolveGlobalSingleton } from "../../shared/global-singleton.js";
import { AcpRuntimeError } from "./errors.js";
export type AcpRuntimeBackend = {
id: string;
runtime: AcpRuntime;
healthy?: () => boolean;
};
type AcpRuntimeRegistryGlobalState = {
backendsById: Map<string, AcpRuntimeBackend>;
};
const ACP_RUNTIME_REGISTRY_STATE_KEY = Symbol.for("openclaw.acpRuntimeRegistryState");
function resolveAcpRuntimeRegistryGlobalState(): AcpRuntimeRegistryGlobalState {
const processStore = process as NodeJS.Process & Record<PropertyKey, unknown>;
const existing = processStore[ACP_RUNTIME_REGISTRY_STATE_KEY];
if (existing) {
return existing as AcpRuntimeRegistryGlobalState;
}
const created = resolveGlobalSingleton<AcpRuntimeRegistryGlobalState>(
ACP_RUNTIME_REGISTRY_STATE_KEY,
() => ({
backendsById: new Map<string, AcpRuntimeBackend>(),
}),
);
// ACP runtime backends are registered from bundled plugin code and read from
// core/test code. In Vitest and Jiti, those can run in different globalThis
// contexts while still sharing one Node process.
processStore[ACP_RUNTIME_REGISTRY_STATE_KEY] = created;
return created;
}
const ACP_BACKENDS_BY_ID = resolveAcpRuntimeRegistryGlobalState().backendsById;
function isBackendHealthy(backend: AcpRuntimeBackend): boolean {
if (!backend.healthy) {
return true;
}
try {
return backend.healthy();
} catch {
return false;
}
}
export function registerAcpRuntimeBackend(backend: AcpRuntimeBackend): void {
const id = normalizeOptionalLowercaseString(backend.id) || "";
if (!id) {
throw new Error("ACP runtime backend id is required");
}
if (!backend.runtime) {
throw new Error(`ACP runtime backend "${id}" is missing runtime implementation`);
}
ACP_BACKENDS_BY_ID.set(id, {
...backend,
id,
});
}
export function unregisterAcpRuntimeBackend(id: string): void {
const normalized = normalizeOptionalLowercaseString(id) || "";
if (!normalized) {
return;
}
ACP_BACKENDS_BY_ID.delete(normalized);
}
export function getAcpRuntimeBackend(id?: string): AcpRuntimeBackend | null {
const normalized = normalizeOptionalLowercaseString(id) || "";
if (normalized) {
return ACP_BACKENDS_BY_ID.get(normalized) ?? null;
}
if (ACP_BACKENDS_BY_ID.size === 0) {
return null;
}
for (const backend of ACP_BACKENDS_BY_ID.values()) {
if (isBackendHealthy(backend)) {
return backend;
}
}
return ACP_BACKENDS_BY_ID.values().next().value ?? null;
}
export function requireAcpRuntimeBackend(id?: string): AcpRuntimeBackend {
const normalized = normalizeOptionalLowercaseString(id) || "";
const backend = getAcpRuntimeBackend(normalized || undefined);
if (!backend) {
throw new AcpRuntimeError(
"ACP_BACKEND_MISSING",
"ACP runtime backend is not configured. Install and enable the acpx runtime plugin.",
);
}
if (!isBackendHealthy(backend)) {
throw new AcpRuntimeError(
"ACP_BACKEND_UNAVAILABLE",
"ACP runtime backend is currently unavailable. Try again in a moment.",
);
}
if (normalized && backend.id !== normalized) {
throw new AcpRuntimeError(
"ACP_BACKEND_MISSING",
`ACP runtime backend "${normalized}" is not registered.`,
);
}
return backend;
}
export const testing = {
resetAcpRuntimeBackendsForTests() {
ACP_BACKENDS_BY_ID.clear();
},
getAcpRuntimeRegistryGlobalStateForTests() {
return resolveAcpRuntimeRegistryGlobalState();
},
};
export { testing as __testing };