mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-21 18:08:05 -06:00
a4110c31b3
* refactor: adopt canonical async serialization * style: satisfy dedupe parameter lint * style: resolve dedupe timestamp internally
103 lines
3.7 KiB
TypeScript
103 lines
3.7 KiB
TypeScript
import crypto from "node:crypto";
|
|
import {
|
|
createConversationBindingRecord,
|
|
resolveConversationBindingRecord,
|
|
unbindConversationBindingRecord,
|
|
} from "../bindings/records.js";
|
|
import { createSubsystemLogger } from "../logging/subsystem.js";
|
|
import { KeyedAsyncQueue } from "../plugin-sdk/keyed-async-queue.js";
|
|
import { INTERNAL_MESSAGE_CHANNEL } from "../utils/message-channel-constants.js";
|
|
import { bindConversationNow, buildPluginBindingIdentity } from "./conversation-binding.js";
|
|
import type {
|
|
PluginConversationBinding,
|
|
PluginConversationBindingRequestParams,
|
|
} from "./conversation-binding.types.js";
|
|
|
|
const log = createSubsystemLogger("plugins/binding");
|
|
|
|
// Serializes bind+finalize+rollback per session so a failing older attempt
|
|
// can never unbind or restore over a newer successful one (all session binds
|
|
// go through this in-process seam).
|
|
const pluginSessionBindQueue = new KeyedAsyncQueue();
|
|
|
|
/** Binds a plugin-owned runtime to one authenticated Control UI session. */
|
|
export async function bindPluginSessionConversation(params: {
|
|
pluginId: string;
|
|
pluginName?: string;
|
|
pluginRoot: string;
|
|
sessionKey: string;
|
|
binding: PluginConversationBindingRequestParams;
|
|
afterBind?: () => Promise<void>;
|
|
}): Promise<PluginConversationBinding> {
|
|
const sessionKey = params.sessionKey.trim();
|
|
if (!sessionKey) {
|
|
throw new Error("session key is required for a plugin session binding");
|
|
}
|
|
return await pluginSessionBindQueue.enqueue(sessionKey, async () =>
|
|
bindPluginSessionConversationExclusive({ ...params, sessionKey }),
|
|
);
|
|
}
|
|
|
|
async function bindPluginSessionConversationExclusive(params: {
|
|
pluginId: string;
|
|
pluginName?: string;
|
|
pluginRoot: string;
|
|
sessionKey: string;
|
|
binding: PluginConversationBindingRequestParams;
|
|
afterBind?: () => Promise<void>;
|
|
}): Promise<PluginConversationBinding> {
|
|
const sessionKey = params.sessionKey;
|
|
const conversation = {
|
|
channel: INTERNAL_MESSAGE_CHANNEL,
|
|
accountId: "default",
|
|
conversationId: sessionKey,
|
|
};
|
|
const previous = resolveConversationBindingRecord(conversation);
|
|
const bindingAttemptId = crypto.randomUUID();
|
|
const binding = await bindConversationNow({
|
|
identity: buildPluginBindingIdentity(params),
|
|
conversation,
|
|
targetSessionKey: sessionKey,
|
|
summary: params.binding.summary,
|
|
detachHint: params.binding.detachHint,
|
|
data: params.binding.data,
|
|
bindingAttemptId,
|
|
});
|
|
try {
|
|
await params.afterBind?.();
|
|
return binding;
|
|
} catch (error) {
|
|
const current = resolveConversationBindingRecord(conversation);
|
|
if (current?.metadata?.bindingAttemptId !== bindingAttemptId) {
|
|
throw error;
|
|
}
|
|
try {
|
|
await unbindConversationBindingRecord({
|
|
bindingId: current.bindingId,
|
|
reason: "plugin-session-bind-rollback",
|
|
});
|
|
if (previous && (previous.expiresAt === undefined || previous.expiresAt > Date.now())) {
|
|
await createConversationBindingRecord({
|
|
targetSessionKey: previous.targetSessionKey,
|
|
targetKind: previous.targetKind,
|
|
conversation: previous.conversation,
|
|
placement: "current",
|
|
metadata: previous.metadata,
|
|
...(previous.expiresAt === undefined
|
|
? {}
|
|
: { ttlMs: Math.max(1, previous.expiresAt - Date.now()) }),
|
|
});
|
|
}
|
|
} catch (rollbackError) {
|
|
// The finalize failure is superseded by the rollback failure on the
|
|
// throw path; keep it observable for diagnosis.
|
|
log.warn("plugin session binding finalization failed before rollback", { error });
|
|
throw new Error(
|
|
"plugin session binding finalization failed and its previous binding could not be restored",
|
|
{ cause: rollbackError },
|
|
);
|
|
}
|
|
throw error;
|
|
}
|
|
}
|