Files
openclaw/src/telegram/bot-message-context.acp-bindings.test.ts
Bob 6a705a37f2 ACP: add persistent Discord channel and Telegram topic bindings (#34873)
* 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>
2026-03-05 09:38:12 +01:00

137 lines
4.5 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from "vitest";
const ensureConfiguredAcpBindingSessionMock = vi.hoisted(() => vi.fn());
const resolveConfiguredAcpBindingRecordMock = vi.hoisted(() => vi.fn());
vi.mock("../acp/persistent-bindings.js", () => ({
ensureConfiguredAcpBindingSession: (...args: unknown[]) =>
ensureConfiguredAcpBindingSessionMock(...args),
resolveConfiguredAcpBindingRecord: (...args: unknown[]) =>
resolveConfiguredAcpBindingRecordMock(...args),
}));
import { buildTelegramMessageContextForTest } from "./bot-message-context.test-harness.js";
function createConfiguredTelegramBinding() {
return {
spec: {
channel: "telegram",
accountId: "work",
conversationId: "-1001234567890:topic:42",
parentConversationId: "-1001234567890",
agentId: "codex",
mode: "persistent",
},
record: {
bindingId: "config:acp:telegram:work:-1001234567890:topic:42",
targetSessionKey: "agent:codex:acp:binding:telegram:work:abc123",
targetKind: "session",
conversation: {
channel: "telegram",
accountId: "work",
conversationId: "-1001234567890:topic:42",
parentConversationId: "-1001234567890",
},
status: "active",
boundAt: 0,
metadata: {
source: "config",
mode: "persistent",
agentId: "codex",
},
},
} as const;
}
describe("buildTelegramMessageContext ACP configured bindings", () => {
beforeEach(() => {
ensureConfiguredAcpBindingSessionMock.mockReset();
resolveConfiguredAcpBindingRecordMock.mockReset();
resolveConfiguredAcpBindingRecordMock.mockReturnValue(createConfiguredTelegramBinding());
ensureConfiguredAcpBindingSessionMock.mockResolvedValue({
ok: true,
sessionKey: "agent:codex:acp:binding:telegram:work:abc123",
});
});
it("treats configured topic bindings as explicit route matches on non-default accounts", async () => {
const ctx = await buildTelegramMessageContextForTest({
accountId: "work",
message: {
chat: { id: -1001234567890, type: "supergroup", title: "OpenClaw", is_forum: true },
message_thread_id: 42,
text: "hello",
},
});
expect(ctx).not.toBeNull();
expect(ctx?.route.accountId).toBe("work");
expect(ctx?.route.matchedBy).toBe("binding.channel");
expect(ctx?.route.sessionKey).toBe("agent:codex:acp:binding:telegram:work:abc123");
expect(ensureConfiguredAcpBindingSessionMock).toHaveBeenCalledTimes(1);
});
it("skips ACP session initialization when topic access is denied", async () => {
const ctx = await buildTelegramMessageContextForTest({
accountId: "work",
message: {
chat: { id: -1001234567890, type: "supergroup", title: "OpenClaw", is_forum: true },
message_thread_id: 42,
text: "hello",
},
resolveTelegramGroupConfig: () => ({
groupConfig: { requireMention: false },
topicConfig: { enabled: false },
}),
});
expect(ctx).toBeNull();
expect(resolveConfiguredAcpBindingRecordMock).toHaveBeenCalledTimes(1);
expect(ensureConfiguredAcpBindingSessionMock).not.toHaveBeenCalled();
});
it("defers ACP session initialization for unauthorized control commands", async () => {
const ctx = await buildTelegramMessageContextForTest({
accountId: "work",
message: {
chat: { id: -1001234567890, type: "supergroup", title: "OpenClaw", is_forum: true },
message_thread_id: 42,
text: "/new",
},
cfg: {
channels: {
telegram: {},
},
commands: {
useAccessGroups: true,
},
},
});
expect(ctx).toBeNull();
expect(resolveConfiguredAcpBindingRecordMock).toHaveBeenCalledTimes(1);
expect(ensureConfiguredAcpBindingSessionMock).not.toHaveBeenCalled();
});
it("drops inbound processing when configured ACP binding initialization fails", async () => {
ensureConfiguredAcpBindingSessionMock.mockResolvedValue({
ok: false,
sessionKey: "agent:codex:acp:binding:telegram:work:abc123",
error: "gateway unavailable",
});
const ctx = await buildTelegramMessageContextForTest({
accountId: "work",
message: {
chat: { id: -1001234567890, type: "supergroup", title: "OpenClaw", is_forum: true },
message_thread_id: 42,
text: "hello",
},
});
expect(ctx).toBeNull();
expect(resolveConfiguredAcpBindingRecordMock).toHaveBeenCalledTimes(1);
expect(ensureConfiguredAcpBindingSessionMock).toHaveBeenCalledTimes(1);
});
});