Files
openclaw/extensions/acpx/index.ts
Peter Steinberger 05fb55eb7b feat: discover OpenCode and Pi sessions on paired nodes (#106941)
* feat(sessions): add opencode and pi catalogs

* fix(sessions): satisfy catalog lint

* fix(sessions): validate pi storage paths

* fix(sessions): index growing pi transcripts

* refactor(sessions): split pi path resolution

* docs(sessions): refresh catalog map

* refactor(sessions): privatize pi catalog internals

* fix(sessions): validate node catalog payloads

* fix(sessions): harden external catalog discovery

* fix(sessions): normalize catalog transcript data

* refactor(gateway): keep node scope gate compact

* fix(opencode): reject option-like session ids

* fix(sessions): align external id validation
2026-07-13 21:45:53 -07:00

69 lines
2.4 KiB
TypeScript

/**
* ACPX runtime plugin entry. It registers the embedded ACP backend service and
* wires reply-dispatch hooks into the plugin SDK runtime.
*/
import { tryDispatchAcpReplyHook } from "openclaw/plugin-sdk/acp-runtime-backend";
import { finiteSecondsToTimerSafeMilliseconds } from "openclaw/plugin-sdk/number-runtime";
import { createAcpxRuntimeService } from "./register.runtime.js";
import type {
OpenClawPluginApi,
PluginHookReplyDispatchContext,
PluginHookReplyDispatchEvent,
PluginHookReplyDispatchResult,
} from "./runtime-api.js";
import { DEFAULT_ACPX_TIMEOUT_SECONDS } from "./src/config-schema.js";
import { registerPiSessionCatalog } from "./src/pi-session-catalog-plugin.js";
function resolveReplyDispatchTimeoutMs(pluginConfig?: Record<string, unknown>): number {
const timeoutSeconds = pluginConfig?.timeoutSeconds;
const resolvedSeconds =
typeof timeoutSeconds === "number" && Number.isFinite(timeoutSeconds) && timeoutSeconds > 0
? timeoutSeconds
: DEFAULT_ACPX_TIMEOUT_SECONDS;
return finiteSecondsToTimerSafeMilliseconds(resolvedSeconds) ?? 1;
}
async function tryDispatchAcpReplyHookWithTimeout(
event: PluginHookReplyDispatchEvent,
ctx: PluginHookReplyDispatchContext,
timeoutMs: number,
): Promise<PluginHookReplyDispatchResult | void> {
const timeoutController = new AbortController();
const timeout = setTimeout(() => timeoutController.abort(), timeoutMs);
timeout.unref?.();
const abortSignal = ctx.abortSignal
? AbortSignal.any([ctx.abortSignal, timeoutController.signal])
: timeoutController.signal;
try {
return await tryDispatchAcpReplyHook(event, {
...ctx,
abortSignal,
});
} finally {
clearTimeout(timeout);
}
}
const plugin = {
id: "acpx",
name: "ACPX Runtime",
description: "Embedded ACP runtime backend with plugin-owned session and transport management.",
register(api: OpenClawPluginApi) {
const replyDispatchTimeoutMs = resolveReplyDispatchTimeoutMs(api.pluginConfig);
registerPiSessionCatalog(api);
api.registerService(
createAcpxRuntimeService({
pluginConfig: api.pluginConfig,
openKeyedStore: (options) => api.runtime.state.openKeyedStore(options),
}),
);
api.on(
"reply_dispatch",
(event, ctx) => tryDispatchAcpReplyHookWithTimeout(event, ctx, replyDispatchTimeoutMs),
{ timeoutMs: replyDispatchTimeoutMs },
);
},
};
export default plugin;