mirror of
https://github.com/moltbot/moltbot.git
synced 2026-04-26 16:06:16 +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>
109 lines
2.9 KiB
TypeScript
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();
|