mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-04 23:50:20 +00:00
refactor: re-duplicate plugin config helpers
This commit is contained in:
@@ -118,22 +118,6 @@ type PluginBindingGlobalState = {
|
||||
approvalsLoaded: boolean;
|
||||
};
|
||||
|
||||
type PluginConversationBindingState = {
|
||||
ref: ConversationRef;
|
||||
record:
|
||||
| {
|
||||
bindingId: string;
|
||||
conversation: ConversationRef;
|
||||
boundAt: number;
|
||||
metadata?: Record<string, unknown>;
|
||||
targetSessionKey: string;
|
||||
}
|
||||
| null
|
||||
| undefined;
|
||||
binding: PluginConversationBinding | null;
|
||||
isLegacyForeignBinding: boolean;
|
||||
};
|
||||
|
||||
const pluginBindingGlobalStateKey = Symbol.for("openclaw.plugins.binding.global-state");
|
||||
const pluginBindingGlobalState = resolveGlobalSingleton<PluginBindingGlobalState>(
|
||||
pluginBindingGlobalStateKey,
|
||||
@@ -233,42 +217,6 @@ function buildPluginBindingSessionKey(params: {
|
||||
return `${PLUGIN_BINDING_SESSION_PREFIX}:${params.pluginId}:${hash}`;
|
||||
}
|
||||
|
||||
function buildPluginBindingIdentity(params: PluginBindingIdentity): PluginBindingIdentity {
|
||||
return {
|
||||
pluginId: params.pluginId,
|
||||
pluginName: params.pluginName,
|
||||
pluginRoot: params.pluginRoot,
|
||||
};
|
||||
}
|
||||
|
||||
function logPluginBindingLifecycleEvent(params: {
|
||||
event:
|
||||
| "migrating legacy record"
|
||||
| "auto-refresh"
|
||||
| "auto-approved"
|
||||
| "requested"
|
||||
| "detached"
|
||||
| "denied"
|
||||
| "approved";
|
||||
pluginId: string;
|
||||
pluginRoot: string;
|
||||
channel: string;
|
||||
accountId: string;
|
||||
conversationId: string;
|
||||
decision?: PluginBindingApprovalDecision;
|
||||
}): void {
|
||||
const parts = [
|
||||
`plugin binding ${params.event}`,
|
||||
`plugin=${params.pluginId}`,
|
||||
`root=${params.pluginRoot}`,
|
||||
...(params.decision ? [`decision=${params.decision}`] : []),
|
||||
`channel=${params.channel}`,
|
||||
`account=${params.accountId}`,
|
||||
`conversation=${params.conversationId}`,
|
||||
];
|
||||
log.info(parts.join(" "));
|
||||
}
|
||||
|
||||
function isLegacyPluginBindingRecord(params: {
|
||||
record:
|
||||
| {
|
||||
@@ -484,89 +432,6 @@ export function toPluginConversationBinding(
|
||||
};
|
||||
}
|
||||
|
||||
function withConversationBindingContext(
|
||||
binding: PluginConversationBinding,
|
||||
conversation: PluginBindingConversation,
|
||||
): PluginConversationBinding {
|
||||
return {
|
||||
...binding,
|
||||
parentConversationId: conversation.parentConversationId,
|
||||
threadId: conversation.threadId,
|
||||
};
|
||||
}
|
||||
|
||||
function resolvePluginConversationBindingState(params: {
|
||||
conversation: PluginBindingConversation;
|
||||
}): PluginConversationBindingState {
|
||||
const ref = toConversationRef(params.conversation);
|
||||
const record = resolveConversationBindingRecord(ref);
|
||||
const binding = toPluginConversationBinding(record);
|
||||
return {
|
||||
ref,
|
||||
record,
|
||||
binding,
|
||||
isLegacyForeignBinding: isLegacyPluginBindingRecord({ record }),
|
||||
};
|
||||
}
|
||||
|
||||
function resolveOwnedPluginConversationBinding(params: {
|
||||
pluginRoot: string;
|
||||
conversation: PluginBindingConversation;
|
||||
}): PluginConversationBinding | null {
|
||||
const state = resolvePluginConversationBindingState({
|
||||
conversation: params.conversation,
|
||||
});
|
||||
if (!state.binding || state.binding.pluginRoot !== params.pluginRoot) {
|
||||
return null;
|
||||
}
|
||||
return withConversationBindingContext(state.binding, params.conversation);
|
||||
}
|
||||
|
||||
function bindConversationFromIdentity(params: {
|
||||
identity: PluginBindingIdentity;
|
||||
conversation: PluginBindingConversation;
|
||||
summary?: string;
|
||||
detachHint?: string;
|
||||
}): Promise<PluginConversationBinding> {
|
||||
return bindConversationNow({
|
||||
identity: buildPluginBindingIdentity(params.identity),
|
||||
conversation: params.conversation,
|
||||
summary: params.summary,
|
||||
detachHint: params.detachHint,
|
||||
});
|
||||
}
|
||||
|
||||
function bindConversationFromRequest(
|
||||
request: Pick<
|
||||
PendingPluginBindingRequest,
|
||||
"pluginId" | "pluginName" | "pluginRoot" | "conversation" | "summary" | "detachHint"
|
||||
>,
|
||||
): Promise<PluginConversationBinding> {
|
||||
return bindConversationFromIdentity({
|
||||
identity: buildPluginBindingIdentity(request),
|
||||
conversation: request.conversation,
|
||||
summary: request.summary,
|
||||
detachHint: request.detachHint,
|
||||
});
|
||||
}
|
||||
|
||||
function buildApprovalEntryFromRequest(
|
||||
request: Pick<
|
||||
PendingPluginBindingRequest,
|
||||
"pluginRoot" | "pluginId" | "pluginName" | "conversation"
|
||||
>,
|
||||
approvedAt = Date.now(),
|
||||
): PluginBindingApprovalEntry {
|
||||
return {
|
||||
pluginRoot: request.pluginRoot,
|
||||
pluginId: request.pluginId,
|
||||
pluginName: request.pluginName,
|
||||
channel: request.conversation.channel,
|
||||
accountId: request.conversation.accountId,
|
||||
approvedAt,
|
||||
};
|
||||
}
|
||||
|
||||
async function bindConversationNow(params: {
|
||||
identity: PluginBindingIdentity;
|
||||
conversation: PluginBindingConversation;
|
||||
@@ -597,7 +462,11 @@ async function bindConversationNow(params: {
|
||||
if (!binding) {
|
||||
throw new Error("plugin binding was created without plugin metadata");
|
||||
}
|
||||
return withConversationBindingContext(binding, params.conversation);
|
||||
return {
|
||||
...binding,
|
||||
parentConversationId: params.conversation.parentConversationId,
|
||||
threadId: params.conversation.threadId,
|
||||
};
|
||||
}
|
||||
|
||||
function buildApprovalMessage(request: PendingPluginBindingRequest): string {
|
||||
@@ -726,19 +595,17 @@ export async function requestPluginConversationBinding(params: {
|
||||
binding: PluginConversationBindingRequestParams | undefined;
|
||||
}): Promise<PluginConversationBindingRequestResult> {
|
||||
const conversation = normalizeConversation(params.conversation);
|
||||
const state = resolvePluginConversationBindingState({
|
||||
conversation,
|
||||
const ref = toConversationRef(conversation);
|
||||
const existing = resolveConversationBindingRecord(ref);
|
||||
const existingPluginBinding = toPluginConversationBinding(existing);
|
||||
const existingLegacyPluginBinding = isLegacyPluginBindingRecord({
|
||||
record: existing,
|
||||
});
|
||||
if (state.record && !state.binding) {
|
||||
if (state.isLegacyForeignBinding) {
|
||||
logPluginBindingLifecycleEvent({
|
||||
event: "migrating legacy record",
|
||||
pluginId: params.pluginId,
|
||||
pluginRoot: params.pluginRoot,
|
||||
channel: state.ref.channel,
|
||||
accountId: state.ref.accountId,
|
||||
conversationId: state.ref.conversationId,
|
||||
});
|
||||
if (existing && !existingPluginBinding) {
|
||||
if (existingLegacyPluginBinding) {
|
||||
log.info(
|
||||
`plugin binding migrating legacy record plugin=${params.pluginId} root=${params.pluginRoot} channel=${ref.channel} account=${ref.accountId} conversation=${ref.conversationId}`,
|
||||
);
|
||||
} else {
|
||||
return {
|
||||
status: "error",
|
||||
@@ -747,52 +614,50 @@ export async function requestPluginConversationBinding(params: {
|
||||
};
|
||||
}
|
||||
}
|
||||
if (state.binding && state.binding.pluginRoot !== params.pluginRoot) {
|
||||
if (existingPluginBinding && existingPluginBinding.pluginRoot !== params.pluginRoot) {
|
||||
return {
|
||||
status: "error",
|
||||
message: `This conversation is already bound by plugin "${state.binding.pluginName ?? state.binding.pluginId}".`,
|
||||
message: `This conversation is already bound by plugin "${existingPluginBinding.pluginName ?? existingPluginBinding.pluginId}".`,
|
||||
};
|
||||
}
|
||||
|
||||
if (state.binding && state.binding.pluginRoot === params.pluginRoot) {
|
||||
const rebound = await bindConversationFromIdentity({
|
||||
identity: buildPluginBindingIdentity(params),
|
||||
if (existingPluginBinding && existingPluginBinding.pluginRoot === params.pluginRoot) {
|
||||
const rebound = await bindConversationNow({
|
||||
identity: {
|
||||
pluginId: params.pluginId,
|
||||
pluginName: params.pluginName,
|
||||
pluginRoot: params.pluginRoot,
|
||||
},
|
||||
conversation,
|
||||
summary: params.binding?.summary,
|
||||
detachHint: params.binding?.detachHint,
|
||||
});
|
||||
logPluginBindingLifecycleEvent({
|
||||
event: "auto-refresh",
|
||||
pluginId: params.pluginId,
|
||||
pluginRoot: params.pluginRoot,
|
||||
channel: state.ref.channel,
|
||||
accountId: state.ref.accountId,
|
||||
conversationId: state.ref.conversationId,
|
||||
});
|
||||
log.info(
|
||||
`plugin binding auto-refresh plugin=${params.pluginId} root=${params.pluginRoot} channel=${ref.channel} account=${ref.accountId} conversation=${ref.conversationId}`,
|
||||
);
|
||||
return { status: "bound", binding: rebound };
|
||||
}
|
||||
|
||||
if (
|
||||
hasPersistentApproval({
|
||||
pluginRoot: params.pluginRoot,
|
||||
channel: state.ref.channel,
|
||||
accountId: state.ref.accountId,
|
||||
channel: ref.channel,
|
||||
accountId: ref.accountId,
|
||||
})
|
||||
) {
|
||||
const bound = await bindConversationFromIdentity({
|
||||
identity: buildPluginBindingIdentity(params),
|
||||
const bound = await bindConversationNow({
|
||||
identity: {
|
||||
pluginId: params.pluginId,
|
||||
pluginName: params.pluginName,
|
||||
pluginRoot: params.pluginRoot,
|
||||
},
|
||||
conversation,
|
||||
summary: params.binding?.summary,
|
||||
detachHint: params.binding?.detachHint,
|
||||
});
|
||||
logPluginBindingLifecycleEvent({
|
||||
event: "auto-approved",
|
||||
pluginId: params.pluginId,
|
||||
pluginRoot: params.pluginRoot,
|
||||
channel: state.ref.channel,
|
||||
accountId: state.ref.accountId,
|
||||
conversationId: state.ref.conversationId,
|
||||
});
|
||||
log.info(
|
||||
`plugin binding auto-approved plugin=${params.pluginId} root=${params.pluginRoot} channel=${ref.channel} account=${ref.accountId} conversation=${ref.conversationId}`,
|
||||
);
|
||||
return { status: "bound", binding: bound };
|
||||
}
|
||||
|
||||
@@ -808,14 +673,9 @@ export async function requestPluginConversationBinding(params: {
|
||||
detachHint: params.binding?.detachHint?.trim() || undefined,
|
||||
};
|
||||
pendingRequests.set(request.id, request);
|
||||
logPluginBindingLifecycleEvent({
|
||||
event: "requested",
|
||||
pluginId: params.pluginId,
|
||||
pluginRoot: params.pluginRoot,
|
||||
channel: state.ref.channel,
|
||||
accountId: state.ref.accountId,
|
||||
conversationId: state.ref.conversationId,
|
||||
});
|
||||
log.info(
|
||||
`plugin binding requested plugin=${params.pluginId} root=${params.pluginRoot} channel=${ref.channel} account=${ref.accountId} conversation=${ref.conversationId}`,
|
||||
);
|
||||
return {
|
||||
status: "pending",
|
||||
approvalId: request.id,
|
||||
@@ -827,29 +687,35 @@ export async function getCurrentPluginConversationBinding(params: {
|
||||
pluginRoot: string;
|
||||
conversation: PluginBindingConversation;
|
||||
}): Promise<PluginConversationBinding | null> {
|
||||
return resolveOwnedPluginConversationBinding(params);
|
||||
const record = resolveConversationBindingRecord(toConversationRef(params.conversation));
|
||||
const binding = toPluginConversationBinding(record);
|
||||
if (!binding || binding.pluginRoot !== params.pluginRoot) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
...binding,
|
||||
parentConversationId: params.conversation.parentConversationId,
|
||||
threadId: params.conversation.threadId,
|
||||
};
|
||||
}
|
||||
|
||||
export async function detachPluginConversationBinding(params: {
|
||||
pluginRoot: string;
|
||||
conversation: PluginBindingConversation;
|
||||
}): Promise<{ removed: boolean }> {
|
||||
const binding = resolveOwnedPluginConversationBinding(params);
|
||||
if (!binding) {
|
||||
const ref = toConversationRef(params.conversation);
|
||||
const record = resolveConversationBindingRecord(ref);
|
||||
const binding = toPluginConversationBinding(record);
|
||||
if (!binding || binding.pluginRoot !== params.pluginRoot) {
|
||||
return { removed: false };
|
||||
}
|
||||
await unbindConversationBindingRecord({
|
||||
bindingId: binding.bindingId,
|
||||
reason: "plugin-detach",
|
||||
});
|
||||
logPluginBindingLifecycleEvent({
|
||||
event: "detached",
|
||||
pluginId: binding.pluginId,
|
||||
pluginRoot: binding.pluginRoot,
|
||||
channel: binding.channel,
|
||||
accountId: binding.accountId,
|
||||
conversationId: binding.conversationId,
|
||||
});
|
||||
log.info(
|
||||
`plugin binding detached plugin=${binding.pluginId} root=${binding.pluginRoot} channel=${binding.channel} account=${binding.accountId} conversation=${binding.conversationId}`,
|
||||
);
|
||||
return { removed: true };
|
||||
}
|
||||
|
||||
@@ -876,29 +742,34 @@ export async function resolvePluginConversationBindingApproval(params: {
|
||||
decision: "deny",
|
||||
request,
|
||||
});
|
||||
logPluginBindingLifecycleEvent({
|
||||
event: "denied",
|
||||
pluginId: request.pluginId,
|
||||
pluginRoot: request.pluginRoot,
|
||||
channel: request.conversation.channel,
|
||||
accountId: request.conversation.accountId,
|
||||
conversationId: request.conversation.conversationId,
|
||||
});
|
||||
log.info(
|
||||
`plugin binding denied plugin=${request.pluginId} root=${request.pluginRoot} channel=${request.conversation.channel} account=${request.conversation.accountId} conversation=${request.conversation.conversationId}`,
|
||||
);
|
||||
return { status: "denied", request };
|
||||
}
|
||||
if (params.decision === "allow-always") {
|
||||
await addPersistentApproval(buildApprovalEntryFromRequest(request));
|
||||
await addPersistentApproval({
|
||||
pluginRoot: request.pluginRoot,
|
||||
pluginId: request.pluginId,
|
||||
pluginName: request.pluginName,
|
||||
channel: request.conversation.channel,
|
||||
accountId: request.conversation.accountId,
|
||||
approvedAt: Date.now(),
|
||||
});
|
||||
}
|
||||
const binding = await bindConversationFromRequest(request);
|
||||
logPluginBindingLifecycleEvent({
|
||||
event: "approved",
|
||||
pluginId: request.pluginId,
|
||||
pluginRoot: request.pluginRoot,
|
||||
decision: params.decision,
|
||||
channel: request.conversation.channel,
|
||||
accountId: request.conversation.accountId,
|
||||
conversationId: request.conversation.conversationId,
|
||||
const binding = await bindConversationNow({
|
||||
identity: {
|
||||
pluginId: request.pluginId,
|
||||
pluginName: request.pluginName,
|
||||
pluginRoot: request.pluginRoot,
|
||||
},
|
||||
conversation: request.conversation,
|
||||
summary: request.summary,
|
||||
detachHint: request.detachHint,
|
||||
});
|
||||
log.info(
|
||||
`plugin binding approved plugin=${request.pluginId} root=${request.pluginRoot} decision=${params.decision} channel=${request.conversation.channel} account=${request.conversation.accountId} conversation=${request.conversation.conversationId}`,
|
||||
);
|
||||
dispatchPluginConversationBindingResolved({
|
||||
status: "approved",
|
||||
binding,
|
||||
|
||||
Reference in New Issue
Block a user