const DiscordIdSchema = z
.union([z.string(), z.number()])
.transform((value, ctx) => { if (typeof value === "number") { if (!Number.isSafeInteger(value) || value < 0) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message:
`Discord ID "${String(value)}" is not a valid non-negative safe integer. ` +
`Wrap it in quotes in your config file.`,
}); return z.NEVER;
} return String(value);
} return value;
})
.pipe(z.string()); const DiscordIdListSchema = z.array(DiscordIdSchema);
export const TelegramAccountSchemaBase = z
.object({
name: z.string().optional(),
capabilities: TelegramCapabilitiesSchema.optional(),
execApprovals: z
.object({
enabled: z.boolean().optional(),
approvers: TelegramIdListSchema.optional(),
agentFilter: z.array(z.string()).optional(),
sessionFilter: z.array(z.string()).optional(),
target: z.enum(["dm", "channel", "both"]).optional(),
})
.strict()
.optional(),
markdown: MarkdownConfigSchema,
enabled: z.boolean().optional(),
commands: ProviderCommandsSchema,
customCommands: z.array(TelegramCustomCommandSchema).optional(),
configWrites: z.boolean().optional(),
dmPolicy: DmPolicySchema.optional().default("pairing"),
botToken: SecretInputSchema.optional().register(sensitive),
tokenFile: z.string().optional(),
replyToMode: ReplyToModeSchema.optional(),
groups: z.record(z.string(), TelegramGroupSchema.optional()).optional(),
allowFrom: z.array(z.union([z.string(), z.number()])).optional(),
defaultTo: z.union([z.string(), z.number()]).optional(),
groupAllowFrom: z.array(z.union([z.string(), z.number()])).optional(),
groupPolicy: GroupPolicySchema.optional().default("allowlist"),
contextVisibility: ContextVisibilityModeSchema.optional(),
historyLimit: z.number().int().min(0).optional(),
dmHistoryLimit: z.number().int().min(0).optional(),
dms: z.record(z.string(), DmConfigSchema.optional()).optional(),
direct: z.record(z.string(), TelegramDirectSchema.optional()).optional(),
textChunkLimit: z.number().int().positive().optional(),
streaming: ChannelPreviewStreamingConfigSchema.optional(),
mediaMaxMb: z.number().positive().optional(),
timeoutSeconds: z.number().int().positive().optional(),
pollingStallThresholdMs: z.number().int().min(30_000).max(600_000).optional(),
retry: RetryConfigSchema,
network: z
.object({
autoSelectFamily: z.boolean().optional(),
dnsResultOrder: z.enum(["ipv4first", "verbatim"]).optional(),
dangerouslyAllowPrivateNetwork: z
.boolean()
.optional()
.describe( "Dangerous opt-in for trusted Telegram fake-IP or transparent-proxy environments where api.telegram.org resolves to private/internal/special-use addresses during media downloads.",
),
})
.strict()
.optional(),
proxy: z.string().optional(),
webhookUrl: z
.string()
.optional()
.describe( "Public HTTPS webhook URL registered with Telegram for inbound updates. This must be internet-reachable and requires channels.telegram.webhookSecret.",
),
webhookSecret: SecretInputSchema.optional()
.describe( "Secret token sent to Telegram during webhook registration and verified on inbound webhook requests. Telegram returns this value for verification; this is not the gateway auth token and not the bot token.",
)
.register(sensitive),
webhookPath: z
.string()
.optional()
.describe( "Local webhook route path served by the gateway listener. Defaults to /telegram-webhook.",
),
webhookHost: z
.string()
.optional()
.describe( "Local bind host for the webhook listener. Defaults to 127.0.0.1; keep loopback unless you intentionally expose direct ingress.",
),
webhookPort: z
.number()
.int()
.nonnegative()
.optional()
.describe( "Local bind port for the webhook listener. Defaults to 8787; set to 0 to let the OS assign an ephemeral port.",
),
webhookCertPath: z
.string()
.optional()
.describe( "Path to the self-signed certificate (PEM) to upload to Telegram during webhook registration. Required for self-signed certs (direct IP or no domain).",
),
actions: z
.object({
reactions: z.boolean().optional(),
sendMessage: z.boolean().optional(),
poll: z.boolean().optional(),
deleteMessage: z.boolean().optional(),
editMessage: z.boolean().optional(),
sticker: z.boolean().optional(),
createForumTopic: z.boolean().optional(),
editForumTopic: z.boolean().optional(),
})
.strict()
.optional(),
threadBindings: z
.object({
enabled: z.boolean().optional(),
idleHours: z.number().nonnegative().optional(),
maxAgeHours: z.number().nonnegative().optional(),
spawnSubagentSessions: z.boolean().optional(),
spawnAcpSessions: z.boolean().optional(),
})
.strict()
.optional(),
reactionNotifications: z.enum(["off", "own", "all"]).optional(),
reactionLevel: z.enum(["off", "ack", "minimal", "extensive"]).optional(),
heartbeat: ChannelHeartbeatVisibilitySchema,
healthMonitor: ChannelHealthMonitorSchema,
linkPreview: z.boolean().optional(),
silentErrorReplies: z.boolean().optional(),
responsePrefix: z.string().optional(),
ackReaction: z.string().optional(),
errorPolicy: TelegramErrorPolicySchema,
errorCooldownMs: z.number().int().nonnegative().optional(),
apiRoot: z.string().url().optional(),
trustedLocalFileRoots: z
.array(z.string())
.optional()
.describe( "Trusted local filesystem roots for self-hosted Telegram Bot API absolute file_path values. Only absolute paths under these roots are read directly; all other absolute paths are rejected.",
),
autoTopicLabel: AutoTopicLabelSchema,
})
.strict();
export const TelegramAccountSchema = TelegramAccountSchemaBase.superRefine((value, ctx) => { // Account-level schemas skip allowFrom validation because accounts inherit // allowFrom from the parent channel config at runtime (resolveTelegramAccount // shallow-merges top-level and account values in src/telegram/accounts.ts). // Validation is enforced at the top-level TelegramConfigSchema instead.
validateTelegramCustomCommands(value, ctx);
});
export const TelegramConfigSchema = TelegramAccountSchemaBase.extend({
accounts: z.record(z.string(), TelegramAccountSchema.optional()).optional(),
defaultAccount: z.string().optional(),
}).superRefine((value, ctx) => {
requireOpenAllowFrom({
policy: value.dmPolicy,
allowFrom: value.allowFrom,
ctx,
path: ["allowFrom"],
message: 'channels.telegram.dmPolicy="open" requires channels.telegram.allowFrom to include "*"',
});
requireAllowlistAllowFrom({
policy: value.dmPolicy,
allowFrom: value.allowFrom,
ctx,
path: ["allowFrom"],
message: 'channels.telegram.dmPolicy="allowlist" requires channels.telegram.allowFrom to contain at least one sender ID',
});
validateTelegramCustomCommands(value, ctx);
if (value.accounts) { for (const [accountId, account] of Object.entries(value.accounts)) { if (!account) { continue;
} const effectivePolicy = account.dmPolicy ?? value.dmPolicy; const effectiveAllowFrom = account.allowFrom ?? value.allowFrom;
requireOpenAllowFrom({
policy: effectivePolicy,
allowFrom: effectiveAllowFrom,
ctx,
path: ["accounts", accountId, "allowFrom"],
message: 'channels.telegram.accounts.*.dmPolicy="open" requires channels.telegram.accounts.*.allowFrom (or channels.telegram.allowFrom) to include "*"',
});
requireAllowlistAllowFrom({
policy: effectivePolicy,
allowFrom: effectiveAllowFrom,
ctx,
path: ["accounts", accountId, "allowFrom"],
message: 'channels.telegram.accounts.*.dmPolicy="allowlist" requires channels.telegram.accounts.*.allowFrom (or channels.telegram.allowFrom) to contain at least one sender ID',
});
}
}
if (!value.accounts) {
validateTelegramWebhookSecretRequirements(value, ctx); return;
} for (const [accountId, account] of Object.entries(value.accounts)) { if (!account) { continue;
} if (account.enabled === false) { continue;
} const effectiveDmPolicy = account.dmPolicy ?? value.dmPolicy; const effectiveAllowFrom = Array.isArray(account.allowFrom)
? account.allowFrom
: value.allowFrom;
requireOpenAllowFrom({
policy: effectiveDmPolicy,
allowFrom: effectiveAllowFrom,
ctx,
path: ["accounts", accountId, "allowFrom"],
message: 'channels.telegram.accounts.*.dmPolicy="open" requires channels.telegram.allowFrom or channels.telegram.accounts.*.allowFrom to include "*"',
});
requireAllowlistAllowFrom({
policy: effectiveDmPolicy,
allowFrom: effectiveAllowFrom,
ctx,
path: ["accounts", accountId, "allowFrom"],
message: 'channels.telegram.accounts.*.dmPolicy="allowlist" requires channels.telegram.allowFrom or channels.telegram.accounts.*.allowFrom to contain at least one sender ID',
});
}
validateTelegramWebhookSecretRequirements(value, ctx);
});
if ((hasActivityType || hasActivityUrl) && !hasActivity) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "channels.discord.activity is required when activityType or activityUrl is set",
path: ["activity"],
});
}
if (value.activityType === 1 && !hasActivityUrl) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "channels.discord.activityUrl is required when activityType is 1 (Streaming)",
path: ["activityUrl"],
});
}
if (hasActivityUrl && value.activityType !== 1) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "channels.discord.activityType must be 1 (Streaming) when activityUrl is set",
path: ["activityType"],
});
}
const autoPresenceInterval = value.autoPresence?.intervalMs; const autoPresenceMinUpdate = value.autoPresence?.minUpdateIntervalMs; if ( typeof autoPresenceInterval === "number" && typeof autoPresenceMinUpdate === "number" &&
autoPresenceMinUpdate > autoPresenceInterval
) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "channels.discord.autoPresence.minUpdateIntervalMs must be less than or equal to channels.discord.autoPresence.intervalMs",
path: ["autoPresence", "minUpdateIntervalMs"],
});
}
// DM allowlist validation is enforced at DiscordConfigSchema so account entries // can inherit top-level allowFrom via runtime shallow merge.
});
// Account-level schemas skip allowFrom validation because accounts inherit // allowFrom from the parent channel config at runtime. // Validation is enforced at the top-level SignalConfigSchema instead.
export const SignalAccountSchema = SignalAccountSchemaBase;
export const SignalConfigSchema = SignalAccountSchemaBase.extend({
accounts: z.record(z.string(), SignalAccountSchema.optional()).optional(),
defaultAccount: z.string().optional(),
}).superRefine((value, ctx) => {
requireOpenAllowFrom({
policy: value.dmPolicy,
allowFrom: value.allowFrom,
ctx,
path: ["allowFrom"],
message: 'channels.signal.dmPolicy="open" requires channels.signal.allowFrom to include "*"',
});
requireAllowlistAllowFrom({
policy: value.dmPolicy,
allowFrom: value.allowFrom,
ctx,
path: ["allowFrom"],
message: 'channels.signal.dmPolicy="allowlist" requires channels.signal.allowFrom to contain at least one sender ID',
});
if (!value.accounts) { return;
} for (const [accountId, account] of Object.entries(value.accounts)) { if (!account) { continue;
} const effectivePolicy = account.dmPolicy ?? value.dmPolicy; const effectiveAllowFrom = account.allowFrom ?? value.allowFrom;
requireOpenAllowFrom({
policy: effectivePolicy,
allowFrom: effectiveAllowFrom,
ctx,
path: ["accounts", accountId, "allowFrom"],
message: 'channels.signal.accounts.*.dmPolicy="open" requires channels.signal.accounts.*.allowFrom (or channels.signal.allowFrom) to include "*"',
});
requireAllowlistAllowFrom({
policy: effectivePolicy,
allowFrom: effectiveAllowFrom,
ctx,
path: ["accounts", accountId, "allowFrom"],
message: 'channels.signal.accounts.*.dmPolicy="allowlist" requires channels.signal.accounts.*.allowFrom (or channels.signal.allowFrom) to contain at least one sender ID',
});
}
});
// Account-level schemas skip allowFrom validation because accounts inherit // allowFrom from the parent channel config at runtime. // Validation is enforced at the top-level IMessageConfigSchema instead.
export const IMessageAccountSchema = IMessageAccountSchemaBase;
export const IMessageConfigSchema = IMessageAccountSchemaBase.extend({
accounts: z.record(z.string(), IMessageAccountSchema.optional()).optional(),
defaultAccount: z.string().optional(),
}).superRefine((value, ctx) => {
requireOpenAllowFrom({
policy: value.dmPolicy,
allowFrom: value.allowFrom,
ctx,
path: ["allowFrom"],
message: 'channels.imessage.dmPolicy="open" requires channels.imessage.allowFrom to include "*"',
});
requireAllowlistAllowFrom({
policy: value.dmPolicy,
allowFrom: value.allowFrom,
ctx,
path: ["allowFrom"],
message: 'channels.imessage.dmPolicy="allowlist" requires channels.imessage.allowFrom to contain at least one sender ID',
});
if (!value.accounts) { return;
} for (const [accountId, account] of Object.entries(value.accounts)) { if (!account) { continue;
} const effectivePolicy = account.dmPolicy ?? value.dmPolicy; const effectiveAllowFrom = account.allowFrom ?? value.allowFrom;
requireOpenAllowFrom({
policy: effectivePolicy,
allowFrom: effectiveAllowFrom,
ctx,
path: ["accounts", accountId, "allowFrom"],
message: 'channels.imessage.accounts.*.dmPolicy="open" requires channels.imessage.accounts.*.allowFrom (or channels.imessage.allowFrom) to include "*"',
});
requireAllowlistAllowFrom({
policy: effectivePolicy,
allowFrom: effectiveAllowFrom,
ctx,
path: ["accounts", accountId, "allowFrom"],
message: 'channels.imessage.accounts.*.dmPolicy="allowlist" requires channels.imessage.accounts.*.allowFrom (or channels.imessage.allowFrom) to contain at least one sender ID',
});
}
});
// Account-level schemas skip allowFrom validation because accounts inherit // allowFrom from the parent channel config at runtime. // Validation is enforced at the top-level BlueBubblesConfigSchema instead.
export const BlueBubblesAccountSchema = BlueBubblesAccountSchemaBase;
export const BlueBubblesConfigSchema = BlueBubblesAccountSchemaBase.extend({
accounts: z.record(z.string(), BlueBubblesAccountSchema.optional()).optional(),
defaultAccount: z.string().optional(),
actions: BlueBubblesActionSchema,
}).superRefine((value, ctx) => {
requireOpenAllowFrom({
policy: value.dmPolicy,
allowFrom: value.allowFrom,
ctx,
path: ["allowFrom"],
message: 'channels.bluebubbles.dmPolicy="open" requires channels.bluebubbles.allowFrom to include "*"',
});
requireAllowlistAllowFrom({
policy: value.dmPolicy,
allowFrom: value.allowFrom,
ctx,
path: ["allowFrom"],
message: 'channels.bluebubbles.dmPolicy="allowlist" requires channels.bluebubbles.allowFrom to contain at least one sender ID',
});
if (!value.accounts) { return;
} for (const [accountId, account] of Object.entries(value.accounts)) { if (!account) { continue;
} const effectivePolicy = account.dmPolicy ?? value.dmPolicy; const effectiveAllowFrom = account.allowFrom ?? value.allowFrom;
requireOpenAllowFrom({
policy: effectivePolicy,
allowFrom: effectiveAllowFrom,
ctx,
path: ["accounts", accountId, "allowFrom"],
message: 'channels.bluebubbles.accounts.*.dmPolicy="open" requires channels.bluebubbles.accounts.*.allowFrom (or channels.bluebubbles.allowFrom) to include "*"',
});
requireAllowlistAllowFrom({
policy: effectivePolicy,
allowFrom: effectiveAllowFrom,
ctx,
path: ["accounts", accountId, "allowFrom"],
message: 'channels.bluebubbles.accounts.*.dmPolicy="allowlist" requires channels.bluebubbles.accounts.*.allowFrom (or channels.bluebubbles.allowFrom) to contain at least one sender ID',
});
}
});
export const MSTeamsConfigSchema = z
.object({
enabled: z.boolean().optional(),
capabilities: z.array(z.string()).optional(),
dangerouslyAllowNameMatching: z.boolean().optional(),
markdown: MarkdownConfigSchema,
configWrites: z.boolean().optional(),
appId: z.string().optional(),
appPassword: SecretInputSchema.optional().register(sensitive),
tenantId: z.string().optional(),
authType: z.enum(["secret", "federated"]).optional(),
certificatePath: z.string().optional(),
certificateThumbprint: z.string().optional(),
useManagedIdentity: z.boolean().optional(),
managedIdentityClientId: z.string().optional(),
webhook: z
.object({
port: z.number().int().positive().optional(),
path: z.string().optional(),
})
.strict()
.optional(),
dmPolicy: DmPolicySchema.optional().default("pairing"),
allowFrom: z.array(z.string()).optional(),
defaultTo: z.string().optional(),
groupAllowFrom: z.array(z.string()).optional(),
groupPolicy: GroupPolicySchema.optional().default("allowlist"),
contextVisibility: ContextVisibilityModeSchema.optional(),
textChunkLimit: z.number().int().positive().optional(),
chunkMode: z.enum(["length", "newline"]).optional(),
typingIndicator: z.boolean().optional(),
blockStreaming: z.boolean().optional(),
blockStreamingCoalesce: BlockStreamingCoalesceSchema.optional(),
mediaAllowHosts: z.array(z.string()).optional(),
mediaAuthAllowHosts: z.array(z.string()).optional(),
requireMention: z.boolean().optional(),
historyLimit: z.number().int().min(0).optional(),
dmHistoryLimit: z.number().int().min(0).optional(),
dms: z.record(z.string(), DmConfigSchema.optional()).optional(),
replyStyle: MSTeamsReplyStyleSchema.optional(),
teams: z.record(z.string(), MSTeamsTeamSchema.optional()).optional(), /** Max media size in MB (default: 100MB for OneDrive upload support). */
mediaMaxMb: z.number().positive().optional(), /** SharePoint site ID for file uploads in group chats/channels (e.g., "contoso.sharepoint.com,guid1,guid2") */
sharePointSiteId: z.string().optional(),
heartbeat: ChannelHeartbeatVisibilitySchema,
healthMonitor: ChannelHealthMonitorSchema,
responsePrefix: z.string().optional(),
welcomeCard: z.boolean().optional(),
promptStarters: z.array(z.string()).optional(),
groupWelcomeCard: z.boolean().optional(),
feedbackEnabled: z.boolean().optional(),
feedbackReflection: z.boolean().optional(),
feedbackReflectionCooldownMs: z.number().int().min(0).optional(),
delegatedAuth: z
.object({
enabled: z.boolean().optional(),
scopes: z.array(z.string()).optional(),
})
.strict()
.optional(),
sso: z
.object({
enabled: z.boolean().optional(),
connectionName: z.string().optional(),
})
.strict()
.optional(),
})
.strict()
.superRefine((value, ctx) => {
requireOpenAllowFrom({
policy: value.dmPolicy,
allowFrom: value.allowFrom,
ctx,
path: ["allowFrom"],
message: 'channels.msteams.dmPolicy="open" requires channels.msteams.allowFrom to include "*"',
});
requireAllowlistAllowFrom({
policy: value.dmPolicy,
allowFrom: value.allowFrom,
ctx,
path: ["allowFrom"],
message: 'channels.msteams.dmPolicy="allowlist" requires channels.msteams.allowFrom to contain at least one sender ID',
}); if (value.sso?.enabled === true && !value.sso.connectionName?.trim()) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
path: ["sso", "connectionName"],
message: "channels.msteams.sso.enabled=true requires channels.msteams.sso.connectionName to identify the Bot Framework OAuth connection",
});
}
// Federated auth fields (appId, tenantId, certificatePath, // useManagedIdentity) may come from MSTEAMS_* environment variables, // so we cannot require them in the config object itself. // Runtime validation happens in resolveMSTeamsCredentials().
});
Messung V0.5 in Prozent
¤ Dauer der Verarbeitung: 0.24 Sekunden
(vorverarbeitet am 2026-06-09)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.