2026-01-23 22:51:37 +00:00
|
|
|
import type { ImageContent } from "@mariozechner/pi-ai";
|
2026-01-06 03:05:11 +00:00
|
|
|
import type { TypingController } from "./reply/typing.js";
|
|
|
|
|
|
2026-01-08 19:30:24 +01:00
|
|
|
export type BlockReplyContext = {
|
|
|
|
|
abortSignal?: AbortSignal;
|
|
|
|
|
timeoutMs?: number;
|
|
|
|
|
};
|
|
|
|
|
|
feat: add dynamic template variables to messages.responsePrefix (#923)
Adds support for template variables in `messages.responsePrefix` that
resolve dynamically at runtime with the actual model used (including
after fallback).
Supported variables (case-insensitive):
- {model} - short model name (e.g., "claude-opus-4-5", "gpt-4o")
- {modelFull} - full model identifier (e.g., "anthropic/claude-opus-4-5")
- {provider} - provider name (e.g., "anthropic", "openai")
- {thinkingLevel} or {think} - thinking level ("high", "low", "off")
- {identity.name} or {identityName} - agent identity name
Example: "[{model} | think:{thinkingLevel}]" → "[claude-opus-4-5 | think:high]"
Variables show the actual model used after fallback, not the intended
model. Unresolved variables remain as literal text.
Implementation:
- New module: src/auto-reply/reply/response-prefix-template.ts
- Template interpolation in normalize-reply.ts via context provider
- onModelSelected callback in agent-runner-execution.ts
- Updated all 6 provider message handlers (web, signal, discord,
telegram, slack, imessage)
- 27 unit tests covering all variables and edge cases
- Documentation in docs/gateway/configuration.md and JSDoc
Fixes #923
2026-01-14 23:05:08 -05:00
|
|
|
/** Context passed to onModelSelected callback with actual model used. */
|
|
|
|
|
export type ModelSelectedContext = {
|
|
|
|
|
provider: string;
|
|
|
|
|
model: string;
|
|
|
|
|
thinkLevel: string | undefined;
|
|
|
|
|
};
|
|
|
|
|
|
2025-11-26 02:03:51 +01:00
|
|
|
export type GetReplyOptions = {
|
2026-01-23 22:51:37 +00:00
|
|
|
/** Override run id for agent events (defaults to random UUID). */
|
|
|
|
|
runId?: string;
|
|
|
|
|
/** Abort signal for the underlying agent run. */
|
|
|
|
|
abortSignal?: AbortSignal;
|
|
|
|
|
/** Optional inbound images (used for webchat attachments). */
|
|
|
|
|
images?: ImageContent[];
|
|
|
|
|
/** Notifies when an agent run actually starts (useful for webchat command handling). */
|
|
|
|
|
onAgentRunStart?: (runId: string) => void;
|
2025-11-26 02:03:51 +01:00
|
|
|
onReplyStart?: () => Promise<void> | void;
|
fix: comprehensive BlueBubbles and channel cleanup (#11093)
* feat(bluebubbles): auto-strip markdown from outbound messages (#7402)
* fix(security): add timeout to webhook body reading (#6762)
Adds 30-second timeout to readBody() in voice-call, bluebubbles, and nostr
webhook handlers. Prevents Slow-Loris DoS (CWE-400, CVSS 7.5).
Merged with existing maxBytes protection in voice-call.
* fix(security): unify Error objects and lint fixes in webhook timeouts (#6762)
* fix: prevent plugins from auto-enabling without user consent (#3961)
Changes default plugin enabled state from true to false in enablePluginEntry().
Preserves existing enabled:true values. Fixes #3932.
* fix: apply hierarchical mediaMaxMb config to all channels (#8749)
Generalizes resolveAttachmentMaxBytes() to use account → channel → global
config resolution for all channels, not just BlueBubbles. Fixes #7847.
* fix(bluebubbles): sanitize attachment filenames against header injection (#10333)
Strip ", \r, \n, and \\ from filenames after path.basename() to prevent
multipart Content-Disposition header injection (CWE-93, CVSS 5.4).
Also adds sanitization to setGroupIconBlueBubbles which had zero filename
sanitization.
* fix(lint): exclude extensions/ from Oxlint preflight check (#9313)
Extensions use PluginRuntime|null patterns that trigger
no-redundant-type-constituents because PluginRuntime resolves to any.
Excluding extensions/ from Oxlint unblocks user upgrades.
Re-applies the approach from closed PR #10087.
* fix(bluebubbles): add tempGuid to createNewChatWithMessage payload (#7745)
Non-Private-API mode (AppleScript) requires tempGuid in send payloads.
The main sendMessageBlueBubbles already had it, but createNewChatWithMessage
was missing it, causing 400 errors for new chat creation without Private API.
* fix: send stop-typing signal when run ends with NO_REPLY (#8785)
Adds onCleanup callback to the typing controller that fires when the
controller is cleaned up while typing was active (e.g., after NO_REPLY).
Channels using createTypingCallbacks automatically get stop-typing on
cleanup. This prevents the typing indicator from lingering in group chats
when the agent decides not to reply.
* fix(telegram): deduplicate skill commands in multi-agent setup (#5717)
Two fixes:
1. Skip duplicate workspace dirs when listing skill commands across agents.
Multiple agents sharing the same workspace would produce duplicate commands
with _2, _3 suffixes.
2. Clear stale commands via deleteMyCommands before registering new ones.
Commands from deleted skills now get cleaned up on restart.
* fix: add size limits to unbounded in-memory caches (#4948)
Adds max-size caps with oldest-entry eviction to prevent OOM in
long-running deployments:
- BlueBubbles serverInfoCache: 64 entries (already has TTL)
- Google Chat authCache: 32 entries
- Matrix directRoomCache: 1024 entries
- Discord presenceCache: 5000 entries per account
* fix: address review concerns (#11093)
- Chain deleteMyCommands → setMyCommands to prevent race condition (#5717)
- Rename enablePluginEntry to registerPluginEntry (now sets enabled: false)
- Add Slow-Loris timeout test for readJsonBody (#6023)
2026-02-07 05:00:55 -08:00
|
|
|
/** Called when the typing controller cleans up (e.g., run ended with NO_REPLY). */
|
|
|
|
|
onTypingCleanup?: () => void;
|
2026-01-06 03:05:11 +00:00
|
|
|
onTypingController?: (typing: TypingController) => void;
|
2025-12-03 00:40:19 +00:00
|
|
|
isHeartbeat?: boolean;
|
2025-12-03 09:21:31 +00:00
|
|
|
onPartialReply?: (payload: ReplyPayload) => Promise<void> | void;
|
2026-01-07 11:08:11 +01:00
|
|
|
onReasoningStream?: (payload: ReplyPayload) => Promise<void> | void;
|
2026-01-14 14:31:43 +00:00
|
|
|
onBlockReply?: (payload: ReplyPayload, context?: BlockReplyContext) => Promise<void> | void;
|
2025-12-20 13:47:07 +00:00
|
|
|
onToolResult?: (payload: ReplyPayload) => Promise<void> | void;
|
feat: add dynamic template variables to messages.responsePrefix (#923)
Adds support for template variables in `messages.responsePrefix` that
resolve dynamically at runtime with the actual model used (including
after fallback).
Supported variables (case-insensitive):
- {model} - short model name (e.g., "claude-opus-4-5", "gpt-4o")
- {modelFull} - full model identifier (e.g., "anthropic/claude-opus-4-5")
- {provider} - provider name (e.g., "anthropic", "openai")
- {thinkingLevel} or {think} - thinking level ("high", "low", "off")
- {identity.name} or {identityName} - agent identity name
Example: "[{model} | think:{thinkingLevel}]" → "[claude-opus-4-5 | think:high]"
Variables show the actual model used after fallback, not the intended
model. Unresolved variables remain as literal text.
Implementation:
- New module: src/auto-reply/reply/response-prefix-template.ts
- Template interpolation in normalize-reply.ts via context provider
- onModelSelected callback in agent-runner-execution.ts
- Updated all 6 provider message handlers (web, signal, discord,
telegram, slack, imessage)
- 27 unit tests covering all variables and edge cases
- Documentation in docs/gateway/configuration.md and JSDoc
Fixes #923
2026-01-14 23:05:08 -05:00
|
|
|
/** Called when the actual model is selected (including after fallback).
|
|
|
|
|
* Use this to get model/provider/thinkLevel for responsePrefix template interpolation. */
|
|
|
|
|
onModelSelected?: (ctx: ModelSelectedContext) => void;
|
2026-01-07 11:08:11 +01:00
|
|
|
disableBlockStreaming?: boolean;
|
2026-01-08 19:30:24 +01:00
|
|
|
/** Timeout for block reply delivery (ms). */
|
|
|
|
|
blockReplyTimeoutMs?: number;
|
2026-01-07 11:22:55 +01:00
|
|
|
/** If provided, only load these skills for this session (empty = no skills). */
|
|
|
|
|
skillFilter?: string[];
|
2026-01-08 16:04:52 -08:00
|
|
|
/** Mutable ref to track if a reply was sent (for Slack "first" threading mode). */
|
|
|
|
|
hasRepliedRef?: { value: boolean };
|
2025-11-26 02:03:51 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export type ReplyPayload = {
|
|
|
|
|
text?: string;
|
|
|
|
|
mediaUrl?: string;
|
|
|
|
|
mediaUrls?: string[];
|
2026-01-02 23:18:41 +01:00
|
|
|
replyToId?: string;
|
2026-01-09 14:59:36 +01:00
|
|
|
replyToTag?: boolean;
|
2026-01-10 03:01:04 +01:00
|
|
|
/** True when [[reply_to_current]] was present but not yet mapped to a message id. */
|
|
|
|
|
replyToCurrent?: boolean;
|
2026-01-04 22:12:29 +01:00
|
|
|
/** Send audio as voice message (bubble) instead of audio file. Defaults to false. */
|
|
|
|
|
audioAsVoice?: boolean;
|
2026-01-06 22:43:29 +01:00
|
|
|
isError?: boolean;
|
2026-01-25 07:22:36 -05:00
|
|
|
/** Channel-specific payload data (per-channel envelope). */
|
|
|
|
|
channelData?: Record<string, unknown>;
|
2025-11-26 02:03:51 +01:00
|
|
|
};
|