mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-07 17:01:22 +00:00
* docs: add ACP persistent binding experiment plan * docs: align ACP persistent binding spec to channel-local config * docs: scope Telegram ACP bindings to forum topics only * docs: lock bound /new and /reset behavior to in-place ACP reset * ACP: add persistent discord/telegram conversation bindings * ACP: fix persistent binding reuse and discord thread parent context * docs: document channel-specific persistent ACP bindings * ACP: split persistent bindings and share conversation id helpers * ACP: defer configured binding init until preflight passes * ACP: fix discord thread parent fallback and explicit disable inheritance * ACP: keep bound /new and /reset in-place * ACP: honor configured bindings in native command flows * ACP: avoid configured fallback after runtime bind failure * docs: refine ACP bindings experiment config examples * acp: cut over to typed top-level persistent bindings * ACP bindings: harden reset recovery and native command auth * Docs: add ACP bound command auth proposal * Tests: normalize i18n registry zh-CN assertion encoding * ACP bindings: address review findings for reset and fallback routing * ACP reset: gate hooks on success and preserve /new arguments * ACP bindings: fix auth and binding-priority review findings * Telegram ACP: gate ensure on auth and accepted messages * ACP bindings: fix session-key precedence and unavailable handling * ACP reset/native commands: honor fallback targets and abort on bootstrap failure * Config schema: validate ACP binding channel and Telegram topic IDs * Discord ACP: apply configured DM bindings to native commands * ACP reset tails: dispatch through ACP after command handling * ACP tails/native reset auth: fix target dispatch and restore full auth * ACP reset detection: fallback to active ACP keys for DM contexts * Tests: type runTurn mock input in ACP dispatch test * ACP: dedup binding route bootstrap and reset target resolution * reply: align ACP reset hooks with bound session key * docs: replace personal discord ids with placeholders * fix: add changelog entry for ACP persistent bindings (#34873) (thanks @dutifulbob) --------- Co-authored-by: Onur <2453968+osolmaz@users.noreply.github.com>
94 lines
2.8 KiB
TypeScript
94 lines
2.8 KiB
TypeScript
import { callGateway } from "../../../gateway/call.js";
|
|
import { resolveEffectiveResetTargetSessionKey } from "../acp-reset-target.js";
|
|
import { resolveRequesterSessionKey } from "../commands-subagents/shared.js";
|
|
import type { HandleCommandsParams } from "../commands-types.js";
|
|
import { resolveAcpCommandBindingContext } from "./context.js";
|
|
import { SESSION_ID_RE } from "./shared.js";
|
|
|
|
async function resolveSessionKeyByToken(token: string): Promise<string | null> {
|
|
const trimmed = token.trim();
|
|
if (!trimmed) {
|
|
return null;
|
|
}
|
|
const attempts: Array<Record<string, string>> = [{ key: trimmed }];
|
|
if (SESSION_ID_RE.test(trimmed)) {
|
|
attempts.push({ sessionId: trimmed });
|
|
}
|
|
attempts.push({ label: trimmed });
|
|
|
|
for (const params of attempts) {
|
|
try {
|
|
const resolved = await callGateway<{ key?: string }>({
|
|
method: "sessions.resolve",
|
|
params,
|
|
timeoutMs: 8_000,
|
|
});
|
|
const key = typeof resolved?.key === "string" ? resolved.key.trim() : "";
|
|
if (key) {
|
|
return key;
|
|
}
|
|
} catch {
|
|
// Try next resolver strategy.
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
export function resolveBoundAcpThreadSessionKey(params: HandleCommandsParams): string | undefined {
|
|
const commandTargetSessionKey =
|
|
typeof params.ctx.CommandTargetSessionKey === "string"
|
|
? params.ctx.CommandTargetSessionKey.trim()
|
|
: "";
|
|
const activeSessionKey = commandTargetSessionKey || params.sessionKey.trim();
|
|
const bindingContext = resolveAcpCommandBindingContext(params);
|
|
return resolveEffectiveResetTargetSessionKey({
|
|
cfg: params.cfg,
|
|
channel: bindingContext.channel,
|
|
accountId: bindingContext.accountId,
|
|
conversationId: bindingContext.conversationId,
|
|
parentConversationId: bindingContext.parentConversationId,
|
|
activeSessionKey,
|
|
allowNonAcpBindingSessionKey: true,
|
|
skipConfiguredFallbackWhenActiveSessionNonAcp: false,
|
|
});
|
|
}
|
|
|
|
export async function resolveAcpTargetSessionKey(params: {
|
|
commandParams: HandleCommandsParams;
|
|
token?: string;
|
|
}): Promise<{ ok: true; sessionKey: string } | { ok: false; error: string }> {
|
|
const token = params.token?.trim() || "";
|
|
if (token) {
|
|
const resolved = await resolveSessionKeyByToken(token);
|
|
if (!resolved) {
|
|
return {
|
|
ok: false,
|
|
error: `Unable to resolve session target: ${token}`,
|
|
};
|
|
}
|
|
return { ok: true, sessionKey: resolved };
|
|
}
|
|
|
|
const threadBound = resolveBoundAcpThreadSessionKey(params.commandParams);
|
|
if (threadBound) {
|
|
return {
|
|
ok: true,
|
|
sessionKey: threadBound,
|
|
};
|
|
}
|
|
|
|
const fallback = resolveRequesterSessionKey(params.commandParams, {
|
|
preferCommandTarget: true,
|
|
});
|
|
if (!fallback) {
|
|
return {
|
|
ok: false,
|
|
error: "Missing session key.",
|
|
};
|
|
}
|
|
return {
|
|
ok: true,
|
|
sessionKey: fallback,
|
|
};
|
|
}
|