Files
moltbot/src/config/zod-schema.agents.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

109 lines
2.9 KiB
TypeScript

import { z } from "zod";
import { AgentDefaultsSchema } from "./zod-schema.agent-defaults.js";
import { AgentEntrySchema } from "./zod-schema.agent-runtime.js";
import { TranscribeAudioSchema } from "./zod-schema.core.js";
export const AgentsSchema = z
.object({
defaults: z.lazy(() => AgentDefaultsSchema).optional(),
list: z.array(AgentEntrySchema).optional(),
})
.strict()
.optional();
const BindingMatchSchema = z
.object({
channel: z.string(),
accountId: z.string().optional(),
peer: z
.object({
kind: z.union([
z.literal("direct"),
z.literal("group"),
z.literal("channel"),
/** @deprecated Use `direct` instead. Kept for backward compatibility. */
z.literal("dm"),
]),
id: z.string(),
})
.strict()
.optional(),
guildId: z.string().optional(),
teamId: z.string().optional(),
roles: z.array(z.string()).optional(),
})
.strict();
const RouteBindingSchema = z
.object({
type: z.literal("route").optional(),
agentId: z.string(),
comment: z.string().optional(),
match: BindingMatchSchema,
})
.strict();
const AcpBindingSchema = z
.object({
type: z.literal("acp"),
agentId: z.string(),
comment: z.string().optional(),
match: BindingMatchSchema,
acp: z
.object({
mode: z.enum(["persistent", "oneshot"]).optional(),
label: z.string().optional(),
cwd: z.string().optional(),
backend: z.string().optional(),
})
.strict()
.optional(),
})
.strict()
.superRefine((value, ctx) => {
const peerId = value.match.peer?.id?.trim() ?? "";
if (!peerId) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
path: ["match", "peer"],
message: "ACP bindings require match.peer.id to target a concrete conversation.",
});
return;
}
const channel = value.match.channel.trim().toLowerCase();
if (channel !== "discord" && channel !== "telegram") {
ctx.addIssue({
code: z.ZodIssueCode.custom,
path: ["match", "channel"],
message: 'ACP bindings currently support only "discord" and "telegram" channels.',
});
return;
}
if (channel === "telegram" && !/^-\d+:topic:\d+$/.test(peerId)) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
path: ["match", "peer", "id"],
message:
"Telegram ACP bindings require canonical topic IDs in the form -1001234567890:topic:42.",
});
}
});
export const BindingsSchema = z.array(z.union([RouteBindingSchema, AcpBindingSchema])).optional();
export const BroadcastStrategySchema = z.enum(["parallel", "sequential"]);
export const BroadcastSchema = z
.object({
strategy: BroadcastStrategySchema.optional(),
})
.catchall(z.array(z.string()))
.optional();
export const AudioSchema = z
.object({
transcription: TranscribeAudioSchema,
})
.strict()
.optional();