@poolzin/pool-bot 2026.2.23 → 2026.2.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +29 -0
- package/dist/acp/client.js +207 -18
- package/dist/acp/secret-file.js +22 -0
- package/dist/agents/agent-scope.js +10 -0
- package/dist/agents/bash-process-registry.test-helpers.js +29 -0
- package/dist/agents/bash-tools.exec-approval-request.js +20 -0
- package/dist/agents/bash-tools.exec-host-gateway.js +230 -0
- package/dist/agents/bash-tools.exec-host-node.js +235 -0
- package/dist/agents/bash-tools.exec-types.js +1 -0
- package/dist/agents/bash-tools.process.js +224 -218
- package/dist/agents/content-blocks.js +16 -0
- package/dist/agents/model-fallback.js +96 -101
- package/dist/agents/models-config.providers.js +299 -182
- package/dist/agents/pi-embedded-payloads.js +1 -0
- package/dist/agents/pi-embedded-runner/run.overflow-compaction.fixture.js +34 -0
- package/dist/agents/skills.test-helpers.js +13 -0
- package/dist/agents/stable-stringify.js +12 -0
- package/dist/agents/subagent-registry.mocks.shared.js +12 -0
- package/dist/agents/test-helpers/assistant-message-fixtures.js +29 -0
- package/dist/agents/test-helpers/pi-tools-sandbox-context.js +27 -0
- package/dist/agents/tool-policy-shared.js +108 -0
- package/dist/agents/tools/browser-tool.js +160 -54
- package/dist/agents/tools/cron-tool.test-helpers.js +12 -0
- package/dist/agents/tools/discord-actions-moderation-shared.js +27 -0
- package/dist/agents/tools/image-tool.js +214 -99
- package/dist/agents/tools/sessions-history-tool.js +140 -108
- package/dist/agents/workspace.js +222 -46
- package/dist/auto-reply/commands-registry.js +15 -18
- package/dist/auto-reply/fallback-state.js +114 -0
- package/dist/auto-reply/model-runtime.js +68 -0
- package/dist/auto-reply/reply/agent-runner-execution.js +36 -4
- package/dist/auto-reply/reply/agent-runner.js +165 -39
- package/dist/auto-reply/reply/commands-setunset-standard.js +13 -0
- package/dist/browser/config.js +26 -0
- package/dist/browser/navigation-guard.js +31 -0
- package/dist/browser/routes/agent.act.js +431 -424
- package/dist/browser/routes/agent.shared.js +47 -3
- package/dist/browser/routes/agent.snapshot.js +122 -116
- package/dist/browser/routes/agent.storage.js +303 -297
- package/dist/browser/routes/tabs.js +154 -100
- package/dist/browser/server-lifecycle.js +37 -0
- package/dist/build-info.json +3 -3
- package/dist/channels/allow-from.js +25 -0
- package/dist/channels/plugins/account-action-gate.js +13 -0
- package/dist/channels/plugins/message-actions.js +10 -0
- package/dist/channels/telegram/api.js +18 -0
- package/dist/cli/argv.js +84 -21
- package/dist/cli/banner.js +2 -1
- package/dist/cli/exec-approvals-cli.js +92 -124
- package/dist/cli/memory-cli.js +158 -61
- package/dist/cli/nodes-cli/register.push.js +63 -0
- package/dist/cli/nodes-media-utils.js +21 -0
- package/dist/cli/plugins-cli.js +245 -61
- package/dist/cli/program/build-program.js +3 -1
- package/dist/cli/program/command-registry.js +223 -136
- package/dist/cli/program/help.js +43 -12
- package/dist/cli/route.js +1 -1
- package/dist/cli/test-runtime-capture.js +24 -0
- package/dist/commands/agent.js +163 -87
- package/dist/commands/channels.mock-harness.js +23 -0
- package/dist/commands/daemon-install-runtime-warning.js +11 -0
- package/dist/commands/onboard-helpers.js +4 -4
- package/dist/commands/sessions.test-helpers.js +61 -0
- package/dist/compat/legacy-names.js +2 -2
- package/dist/config/commands.js +3 -0
- package/dist/config/config.js +1 -1
- package/dist/config/env-substitution.js +62 -34
- package/dist/config/env-vars.js +9 -0
- package/dist/config/io.js +571 -171
- package/dist/config/merge-patch.js +50 -4
- package/dist/config/redact-snapshot.js +404 -76
- package/dist/config/schema.js +58 -570
- package/dist/config/validation.js +140 -85
- package/dist/config/zod-schema.hooks.js +40 -11
- package/dist/config/zod-schema.installs.js +20 -0
- package/dist/config/zod-schema.js +8 -7
- package/dist/control-ui/assets/{index-HRr1grwl.js → index-Dvkl4Xlx.js} +2 -1
- package/dist/control-ui/assets/{index-HRr1grwl.js.map → index-Dvkl4Xlx.js.map} +1 -1
- package/dist/control-ui/index.html +1 -1
- package/dist/daemon/cmd-argv.js +21 -0
- package/dist/daemon/cmd-set.js +58 -0
- package/dist/daemon/service-types.js +1 -0
- package/dist/discord/monitor/exec-approvals.js +357 -162
- package/dist/gateway/auth.js +38 -3
- package/dist/gateway/call.js +149 -68
- package/dist/gateway/canvas-capability.js +75 -0
- package/dist/gateway/control-plane-audit.js +28 -0
- package/dist/gateway/control-plane-rate-limit.js +53 -0
- package/dist/gateway/events.js +1 -0
- package/dist/gateway/hooks.js +109 -54
- package/dist/gateway/http-common.js +22 -0
- package/dist/gateway/method-scopes.js +169 -0
- package/dist/gateway/net.js +23 -0
- package/dist/gateway/openresponses-http.js +120 -110
- package/dist/gateway/probe-auth.js +2 -0
- package/dist/gateway/protocol/index.js +3 -2
- package/dist/gateway/protocol/schema/protocol-schemas.js +2 -0
- package/dist/gateway/protocol/schema/push.js +18 -0
- package/dist/gateway/protocol/schema.js +1 -0
- package/dist/gateway/server-http.js +236 -52
- package/dist/gateway/server-methods/agent.js +162 -24
- package/dist/gateway/server-methods/chat.js +461 -130
- package/dist/gateway/server-methods/config.js +193 -150
- package/dist/gateway/server-methods/nodes.helpers.js +12 -0
- package/dist/gateway/server-methods/nodes.js +251 -69
- package/dist/gateway/server-methods/push.js +53 -0
- package/dist/gateway/server-reload-handlers.js +2 -3
- package/dist/gateway/server-runtime-config.js +5 -0
- package/dist/gateway/server-runtime-state.js +2 -0
- package/dist/gateway/server-ws-runtime.js +1 -0
- package/dist/gateway/server.impl.js +296 -139
- package/dist/gateway/session-preview.test-helpers.js +11 -0
- package/dist/gateway/startup-auth.js +126 -0
- package/dist/gateway/test-helpers.agent-results.js +15 -0
- package/dist/gateway/test-helpers.mocks.js +37 -14
- package/dist/gateway/test-helpers.server.js +161 -77
- package/dist/hooks/bundled/session-memory/handler.js +165 -34
- package/dist/hooks/gmail-watcher-lifecycle.js +23 -0
- package/dist/infra/archive-path.js +49 -0
- package/dist/infra/device-pairing.js +148 -167
- package/dist/infra/exec-approvals-allowlist.js +19 -70
- package/dist/infra/exec-approvals-analysis.js +44 -17
- package/dist/infra/exec-safe-bin-policy.js +269 -0
- package/dist/infra/fixed-window-rate-limit.js +33 -0
- package/dist/infra/git-root.js +61 -0
- package/dist/infra/heartbeat-active-hours.js +2 -2
- package/dist/infra/heartbeat-reason.js +40 -0
- package/dist/infra/heartbeat-runner.js +72 -32
- package/dist/infra/install-source-utils.js +91 -7
- package/dist/infra/node-pairing.js +50 -105
- package/dist/infra/npm-integrity.js +45 -0
- package/dist/infra/npm-pack-install.js +40 -0
- package/dist/infra/outbound/channel-adapters.js +20 -7
- package/dist/infra/outbound/message-action-runner.js +107 -327
- package/dist/infra/outbound/message.js +59 -36
- package/dist/infra/outbound/outbound-policy.js +52 -25
- package/dist/infra/outbound/outbound-send-service.js +58 -71
- package/dist/infra/pairing-files.js +10 -0
- package/dist/infra/plain-object.js +9 -0
- package/dist/infra/push-apns.js +365 -0
- package/dist/infra/restart-sentinel.js +16 -1
- package/dist/infra/restart.js +229 -26
- package/dist/infra/scp-host.js +54 -0
- package/dist/infra/update-startup.js +86 -9
- package/dist/media/inbound-path-policy.js +114 -0
- package/dist/media/input-files.js +16 -0
- package/dist/memory/test-manager.js +8 -0
- package/dist/plugin-sdk/temp-path.js +47 -0
- package/dist/plugins/discovery.js +217 -23
- package/dist/plugins/hook-runner-global.js +16 -0
- package/dist/plugins/loader.js +192 -26
- package/dist/plugins/logger.js +8 -0
- package/dist/plugins/manifest-registry.js +3 -0
- package/dist/plugins/path-safety.js +34 -0
- package/dist/plugins/registry.js +5 -2
- package/dist/plugins/runtime/index.js +271 -206
- package/dist/providers/github-copilot-models.js +4 -1
- package/dist/security/audit-channel.js +8 -19
- package/dist/security/audit-extra.async.js +354 -182
- package/dist/security/audit-extra.js +11 -1
- package/dist/security/audit-extra.sync.js +340 -33
- package/dist/security/audit-fs.js +31 -13
- package/dist/security/audit.js +145 -371
- package/dist/security/dm-policy-shared.js +24 -0
- package/dist/security/external-content.js +20 -8
- package/dist/security/fix.js +49 -85
- package/dist/security/scan-paths.js +20 -0
- package/dist/security/secret-equal.js +3 -7
- package/dist/security/windows-acl.js +30 -15
- package/dist/shared/node-list-parse.js +13 -0
- package/dist/shared/operator-scope-compat.js +37 -0
- package/dist/shared/text-chunking.js +29 -0
- package/dist/slack/blocks.test-helpers.js +31 -0
- package/dist/slack/monitor/mrkdwn.js +8 -0
- package/dist/telegram/bot-message-dispatch.js +366 -164
- package/dist/telegram/draft-stream.js +30 -7
- package/dist/telegram/reasoning-lane-coordinator.js +128 -0
- package/dist/terminal/prompt-select-styled.js +9 -0
- package/dist/test-utils/command-runner.js +6 -0
- package/dist/test-utils/internal-hook-event-payload.js +10 -0
- package/dist/test-utils/model-auth-mock.js +12 -0
- package/dist/test-utils/provider-usage-fetch.js +14 -0
- package/dist/test-utils/temp-home.js +33 -0
- package/dist/tui/components/chat-log.js +9 -0
- package/dist/tui/tui-command-handlers.js +36 -27
- package/dist/tui/tui-event-handlers.js +122 -32
- package/dist/tui/tui.js +181 -45
- package/dist/utils/mask-api-key.js +10 -0
- package/dist/utils/run-with-concurrency.js +39 -0
- package/dist/web/media.js +4 -0
- package/docs/tools/slash-commands.md +5 -1
- package/extensions/bluebubbles/package.json +1 -1
- package/extensions/copilot-proxy/package.json +1 -1
- package/extensions/diagnostics-otel/package.json +1 -1
- package/extensions/discord/package.json +1 -1
- package/extensions/feishu/package.json +1 -1
- package/extensions/feishu/src/external-keys.ts +19 -0
- package/extensions/google-antigravity-auth/package.json +1 -1
- package/extensions/google-gemini-cli-auth/package.json +1 -1
- package/extensions/googlechat/package.json +1 -1
- package/extensions/imessage/package.json +1 -1
- package/extensions/irc/package.json +1 -1
- package/extensions/line/package.json +1 -1
- package/extensions/llm-task/package.json +1 -1
- package/extensions/lobster/package.json +1 -1
- package/extensions/lobster/src/windows-spawn.ts +193 -0
- package/extensions/matrix/CHANGELOG.md +5 -0
- package/extensions/matrix/package.json +1 -1
- package/extensions/matrix/src/matrix/actions/limits.ts +6 -0
- package/extensions/mattermost/package.json +1 -1
- package/extensions/mattermost/src/mattermost/reactions.test-helpers.ts +83 -0
- package/extensions/memory-core/package.json +1 -1
- package/extensions/memory-lancedb/package.json +1 -1
- package/extensions/minimax-portal-auth/package.json +1 -1
- package/extensions/msteams/CHANGELOG.md +5 -0
- package/extensions/msteams/package.json +1 -1
- package/extensions/nextcloud-talk/package.json +1 -1
- package/extensions/nostr/CHANGELOG.md +5 -0
- package/extensions/nostr/package.json +1 -1
- package/extensions/open-prose/package.json +1 -1
- package/extensions/openai-codex-auth/package.json +1 -1
- package/extensions/signal/package.json +1 -1
- package/extensions/slack/package.json +1 -1
- package/extensions/telegram/package.json +1 -1
- package/extensions/tlon/package.json +1 -1
- package/extensions/twitch/CHANGELOG.md +5 -0
- package/extensions/twitch/package.json +1 -1
- package/extensions/voice-call/CHANGELOG.md +5 -0
- package/extensions/voice-call/package.json +1 -1
- package/extensions/whatsapp/package.json +1 -1
- package/extensions/zalo/CHANGELOG.md +5 -0
- package/extensions/zalo/package.json +1 -1
- package/extensions/zalouser/CHANGELOG.md +5 -0
- package/extensions/zalouser/package.json +1 -1
- package/package.json +1 -1
|
@@ -10,11 +10,14 @@ import { logAckFailure, logTypingFailure } from "../channels/logging.js";
|
|
|
10
10
|
import { createReplyPrefixOptions } from "../channels/reply-prefix.js";
|
|
11
11
|
import { createTypingCallbacks } from "../channels/typing.js";
|
|
12
12
|
import { resolveMarkdownTableMode } from "../config/markdown-tables.js";
|
|
13
|
+
import { loadSessionStore, resolveStorePath } from "../config/sessions.js";
|
|
13
14
|
import { danger, logVerbose } from "../globals.js";
|
|
14
15
|
import { getAgentScopedMediaLocalRoots } from "../media/local-roots.js";
|
|
15
16
|
import { deliverReplies } from "./bot/delivery.js";
|
|
16
17
|
import { resolveTelegramDraftStreamingChunking } from "./draft-chunking.js";
|
|
17
18
|
import { createTelegramDraftStream } from "./draft-stream.js";
|
|
19
|
+
import { renderTelegramHtmlText } from "./format.js";
|
|
20
|
+
import { createTelegramReasoningStepState, splitTelegramReasoningText, } from "./reasoning-lane-coordinator.js";
|
|
18
21
|
import { editMessageTelegram } from "./send.js";
|
|
19
22
|
import { cacheSticker, describeStickerImage } from "./sticker-cache.js";
|
|
20
23
|
const EMPTY_RESPONSE_FALLBACK = "No response generated. Please try again.";
|
|
@@ -34,118 +37,194 @@ async function resolveStickerVisionSupport(cfg, agentId) {
|
|
|
34
37
|
return false;
|
|
35
38
|
}
|
|
36
39
|
}
|
|
40
|
+
function resolveTelegramReasoningLevel(params) {
|
|
41
|
+
const { cfg, sessionKey, agentId } = params;
|
|
42
|
+
if (!sessionKey) {
|
|
43
|
+
return "off";
|
|
44
|
+
}
|
|
45
|
+
try {
|
|
46
|
+
const storePath = resolveStorePath(cfg.session?.store, { agentId });
|
|
47
|
+
const store = loadSessionStore(storePath, { skipCache: true });
|
|
48
|
+
const entry = store[sessionKey.toLowerCase()] ?? store[sessionKey];
|
|
49
|
+
const level = entry?.reasoningLevel;
|
|
50
|
+
if (level === "on" || level === "stream") {
|
|
51
|
+
return level;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
// Fall through to default.
|
|
56
|
+
}
|
|
57
|
+
return "off";
|
|
58
|
+
}
|
|
37
59
|
export const dispatchTelegramMessage = async ({ context, bot, cfg, runtime, replyToMode, streamMode, textLimit, telegramCfg, opts, }) => {
|
|
38
60
|
const { ctxPayload, msg, chatId, isGroup, threadSpec, historyKey, historyLimit, groupHistories, route, skillFilter, sendTyping, sendRecordVoice, ackReactionPromise, reactionApi, removeAckAfterReply, } = context;
|
|
39
61
|
const draftMaxChars = Math.min(textLimit, 4096);
|
|
62
|
+
const tableMode = resolveMarkdownTableMode({
|
|
63
|
+
cfg,
|
|
64
|
+
channel: "telegram",
|
|
65
|
+
accountId: route.accountId,
|
|
66
|
+
});
|
|
67
|
+
const renderDraftPreview = (text) => ({
|
|
68
|
+
text: renderTelegramHtmlText(text, { tableMode }),
|
|
69
|
+
parseMode: "HTML",
|
|
70
|
+
});
|
|
40
71
|
const accountBlockStreamingEnabled = typeof telegramCfg.blockStreaming === "boolean"
|
|
41
72
|
? telegramCfg.blockStreaming
|
|
42
73
|
: cfg.agents?.defaults?.blockStreamingDefault === "on";
|
|
43
|
-
const
|
|
74
|
+
const resolvedReasoningLevel = resolveTelegramReasoningLevel({
|
|
75
|
+
cfg,
|
|
76
|
+
sessionKey: ctxPayload.SessionKey,
|
|
77
|
+
agentId: route.agentId,
|
|
78
|
+
});
|
|
79
|
+
const forceBlockStreamingForReasoning = resolvedReasoningLevel === "on";
|
|
80
|
+
const streamReasoningDraft = resolvedReasoningLevel === "stream";
|
|
81
|
+
const canStreamAnswerDraft = streamMode !== "off" && !accountBlockStreamingEnabled && !forceBlockStreamingForReasoning;
|
|
82
|
+
const canStreamReasoningDraft = canStreamAnswerDraft || streamReasoningDraft;
|
|
44
83
|
const draftReplyToMessageId = replyToMode !== "off" && typeof msg.message_id === "number" ? msg.message_id : undefined;
|
|
45
|
-
const
|
|
46
|
-
? createTelegramDraftStream({
|
|
47
|
-
api: bot.api,
|
|
48
|
-
chatId,
|
|
49
|
-
maxChars: draftMaxChars,
|
|
50
|
-
thread: threadSpec,
|
|
51
|
-
replyToMessageId: draftReplyToMessageId,
|
|
52
|
-
minInitialChars: DRAFT_MIN_INITIAL_CHARS,
|
|
53
|
-
log: logVerbose,
|
|
54
|
-
warn: logVerbose,
|
|
55
|
-
})
|
|
56
|
-
: undefined;
|
|
57
|
-
const draftChunking = draftStream && streamMode === "block"
|
|
58
|
-
? resolveTelegramDraftStreamingChunking(cfg, route.accountId)
|
|
59
|
-
: undefined;
|
|
60
|
-
const shouldSplitPreviewMessages = streamMode === "block";
|
|
61
|
-
const draftChunker = draftChunking ? new EmbeddedBlockChunker(draftChunking) : undefined;
|
|
84
|
+
const draftMinInitialChars = streamMode === "partial" || streamReasoningDraft ? 1 : DRAFT_MIN_INITIAL_CHARS;
|
|
62
85
|
const mediaLocalRoots = getAgentScopedMediaLocalRoots(cfg, route.agentId);
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
86
|
+
const createDraftLane = (enabled) => {
|
|
87
|
+
const stream = enabled
|
|
88
|
+
? createTelegramDraftStream({
|
|
89
|
+
api: bot.api,
|
|
90
|
+
chatId,
|
|
91
|
+
maxChars: draftMaxChars,
|
|
92
|
+
thread: threadSpec,
|
|
93
|
+
replyToMessageId: draftReplyToMessageId,
|
|
94
|
+
minInitialChars: draftMinInitialChars,
|
|
95
|
+
renderText: renderDraftPreview,
|
|
96
|
+
log: logVerbose,
|
|
97
|
+
warn: logVerbose,
|
|
98
|
+
})
|
|
99
|
+
: undefined;
|
|
100
|
+
const chunker = stream && streamMode === "block"
|
|
101
|
+
? new EmbeddedBlockChunker(resolveTelegramDraftStreamingChunking(cfg, route.accountId))
|
|
102
|
+
: undefined;
|
|
103
|
+
return {
|
|
104
|
+
stream,
|
|
105
|
+
lastPartialText: "",
|
|
106
|
+
draftText: "",
|
|
107
|
+
hasStreamedMessage: false,
|
|
108
|
+
chunker,
|
|
109
|
+
};
|
|
110
|
+
};
|
|
111
|
+
const lanes = {
|
|
112
|
+
answer: createDraftLane(canStreamAnswerDraft),
|
|
113
|
+
reasoning: createDraftLane(canStreamReasoningDraft),
|
|
114
|
+
};
|
|
115
|
+
const answerLane = lanes.answer;
|
|
116
|
+
const reasoningLane = lanes.reasoning;
|
|
117
|
+
let splitReasoningOnNextStream = false;
|
|
118
|
+
const reasoningStepState = createTelegramReasoningStepState();
|
|
119
|
+
const splitTextIntoLaneSegments = (text) => {
|
|
120
|
+
const split = splitTelegramReasoningText(text);
|
|
121
|
+
const segments = [];
|
|
122
|
+
if (split.reasoningText) {
|
|
123
|
+
segments.push({ lane: "reasoning", text: split.reasoningText });
|
|
124
|
+
}
|
|
125
|
+
if (split.answerText) {
|
|
126
|
+
segments.push({ lane: "answer", text: split.answerText });
|
|
127
|
+
}
|
|
128
|
+
return segments;
|
|
129
|
+
};
|
|
130
|
+
const resetDraftLaneState = (lane) => {
|
|
131
|
+
lane.lastPartialText = "";
|
|
132
|
+
lane.draftText = "";
|
|
133
|
+
lane.hasStreamedMessage = false;
|
|
134
|
+
lane.chunker?.reset();
|
|
135
|
+
};
|
|
136
|
+
const updateDraftFromPartial = (lane, text) => {
|
|
137
|
+
const laneStream = lane.stream;
|
|
138
|
+
if (!laneStream || !text) {
|
|
68
139
|
return;
|
|
69
140
|
}
|
|
70
|
-
if (text === lastPartialText) {
|
|
141
|
+
if (text === lane.lastPartialText) {
|
|
71
142
|
return;
|
|
72
143
|
}
|
|
73
144
|
// Mark that we've received streaming content (for forceNewMessage decision).
|
|
74
|
-
hasStreamedMessage = true;
|
|
145
|
+
lane.hasStreamedMessage = true;
|
|
75
146
|
if (streamMode === "partial") {
|
|
76
147
|
// Some providers briefly emit a shorter prefix snapshot (for example
|
|
77
148
|
// "Sure." -> "Sure" -> "Sure."). Keep the longer preview to avoid
|
|
78
149
|
// visible punctuation flicker.
|
|
79
|
-
if (lastPartialText &&
|
|
80
|
-
lastPartialText.startsWith(text) &&
|
|
81
|
-
text.length < lastPartialText.length) {
|
|
150
|
+
if (lane.lastPartialText &&
|
|
151
|
+
lane.lastPartialText.startsWith(text) &&
|
|
152
|
+
text.length < lane.lastPartialText.length) {
|
|
82
153
|
return;
|
|
83
154
|
}
|
|
84
|
-
lastPartialText = text;
|
|
85
|
-
|
|
155
|
+
lane.lastPartialText = text;
|
|
156
|
+
laneStream.update(text);
|
|
86
157
|
return;
|
|
87
158
|
}
|
|
88
159
|
let delta = text;
|
|
89
|
-
if (text.startsWith(lastPartialText)) {
|
|
90
|
-
delta = text.slice(lastPartialText.length);
|
|
160
|
+
if (text.startsWith(lane.lastPartialText)) {
|
|
161
|
+
delta = text.slice(lane.lastPartialText.length);
|
|
91
162
|
}
|
|
92
163
|
else {
|
|
93
164
|
// Streaming buffer reset (or non-monotonic stream). Start fresh.
|
|
94
|
-
|
|
95
|
-
draftText = "";
|
|
165
|
+
lane.chunker?.reset();
|
|
166
|
+
lane.draftText = "";
|
|
96
167
|
}
|
|
97
|
-
lastPartialText = text;
|
|
168
|
+
lane.lastPartialText = text;
|
|
98
169
|
if (!delta) {
|
|
99
170
|
return;
|
|
100
171
|
}
|
|
101
|
-
if (!
|
|
102
|
-
draftText = text;
|
|
103
|
-
|
|
172
|
+
if (!lane.chunker) {
|
|
173
|
+
lane.draftText = text;
|
|
174
|
+
laneStream.update(lane.draftText);
|
|
104
175
|
return;
|
|
105
176
|
}
|
|
106
|
-
|
|
107
|
-
|
|
177
|
+
lane.chunker.append(delta);
|
|
178
|
+
lane.chunker.drain({
|
|
108
179
|
force: false,
|
|
109
180
|
emit: (chunk) => {
|
|
110
|
-
draftText += chunk;
|
|
111
|
-
|
|
181
|
+
lane.draftText += chunk;
|
|
182
|
+
laneStream.update(lane.draftText);
|
|
112
183
|
},
|
|
113
184
|
});
|
|
114
185
|
};
|
|
115
|
-
const
|
|
116
|
-
|
|
186
|
+
const ingestDraftLaneSegments = (text) => {
|
|
187
|
+
for (const segment of splitTextIntoLaneSegments(text)) {
|
|
188
|
+
if (segment.lane === "reasoning") {
|
|
189
|
+
reasoningStepState.noteReasoningHint();
|
|
190
|
+
reasoningStepState.noteReasoningDelivered();
|
|
191
|
+
}
|
|
192
|
+
updateDraftFromPartial(lanes[segment.lane], segment.text);
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
const flushDraftLane = async (lane) => {
|
|
196
|
+
if (!lane.stream) {
|
|
117
197
|
return;
|
|
118
198
|
}
|
|
119
|
-
if (
|
|
120
|
-
|
|
199
|
+
if (lane.chunker?.hasBuffered()) {
|
|
200
|
+
lane.chunker.drain({
|
|
121
201
|
force: true,
|
|
122
202
|
emit: (chunk) => {
|
|
123
|
-
draftText += chunk;
|
|
203
|
+
lane.draftText += chunk;
|
|
124
204
|
},
|
|
125
205
|
});
|
|
126
|
-
|
|
127
|
-
if (draftText) {
|
|
128
|
-
|
|
206
|
+
lane.chunker.reset();
|
|
207
|
+
if (lane.draftText) {
|
|
208
|
+
lane.stream.update(lane.draftText);
|
|
129
209
|
}
|
|
130
210
|
}
|
|
131
|
-
await
|
|
211
|
+
await lane.stream.flush();
|
|
132
212
|
};
|
|
133
|
-
const disableBlockStreaming =
|
|
134
|
-
?
|
|
135
|
-
:
|
|
136
|
-
?
|
|
137
|
-
:
|
|
213
|
+
const disableBlockStreaming = streamMode === "off"
|
|
214
|
+
? true
|
|
215
|
+
: forceBlockStreamingForReasoning
|
|
216
|
+
? false
|
|
217
|
+
: typeof telegramCfg.blockStreaming === "boolean"
|
|
218
|
+
? !telegramCfg.blockStreaming
|
|
219
|
+
: canStreamAnswerDraft
|
|
220
|
+
? true
|
|
221
|
+
: undefined;
|
|
138
222
|
const { onModelSelected, ...prefixOptions } = createReplyPrefixOptions({
|
|
139
223
|
cfg,
|
|
140
224
|
agentId: route.agentId,
|
|
141
225
|
channel: "telegram",
|
|
142
226
|
accountId: route.accountId,
|
|
143
227
|
});
|
|
144
|
-
const tableMode = resolveMarkdownTableMode({
|
|
145
|
-
cfg,
|
|
146
|
-
channel: "telegram",
|
|
147
|
-
accountId: route.accountId,
|
|
148
|
-
});
|
|
149
228
|
const chunkMode = resolveChunkMode(cfg, "telegram", route.accountId);
|
|
150
229
|
// Handle uncached stickers: get a dedicated vision description before dispatch
|
|
151
230
|
// This ensures we cache a raw description rather than a conversational response
|
|
@@ -205,8 +284,12 @@ export const dispatchTelegramMessage = async ({ context, bot, cfg, runtime, repl
|
|
|
205
284
|
const deliveryState = {
|
|
206
285
|
delivered: false,
|
|
207
286
|
skippedNonSilent: 0,
|
|
287
|
+
failedNonSilent: 0,
|
|
288
|
+
};
|
|
289
|
+
const finalizedPreviewByLane = {
|
|
290
|
+
answer: false,
|
|
291
|
+
reasoning: false,
|
|
208
292
|
};
|
|
209
|
-
let finalizedViaPreviewMessage = false;
|
|
210
293
|
const clearGroupHistory = () => {
|
|
211
294
|
if (isGroup && historyKey) {
|
|
212
295
|
clearHistoryEntriesIfEnabled({ historyMap: groupHistories, historyKey, limit: historyLimit });
|
|
@@ -226,6 +309,114 @@ export const dispatchTelegramMessage = async ({ context, bot, cfg, runtime, repl
|
|
|
226
309
|
linkPreview: telegramCfg.linkPreview,
|
|
227
310
|
replyQuoteText,
|
|
228
311
|
};
|
|
312
|
+
const getLanePreviewText = (lane) => streamMode === "block" ? lane.draftText : lane.lastPartialText;
|
|
313
|
+
const tryUpdatePreviewForLane = async (params) => {
|
|
314
|
+
const { lane, laneName, text, previewButtons, stopBeforeEdit = false, updateLaneSnapshot = false, skipRegressive, context, } = params;
|
|
315
|
+
if (!lane.stream) {
|
|
316
|
+
return false;
|
|
317
|
+
}
|
|
318
|
+
const hadPreviewMessage = typeof lane.stream.messageId() === "number";
|
|
319
|
+
if (stopBeforeEdit) {
|
|
320
|
+
await lane.stream.stop();
|
|
321
|
+
}
|
|
322
|
+
const previewMessageId = lane.stream.messageId();
|
|
323
|
+
if (typeof previewMessageId !== "number") {
|
|
324
|
+
return false;
|
|
325
|
+
}
|
|
326
|
+
const currentPreviewText = getLanePreviewText(lane);
|
|
327
|
+
const shouldSkipRegressive = Boolean(currentPreviewText) &&
|
|
328
|
+
currentPreviewText.startsWith(text) &&
|
|
329
|
+
text.length < currentPreviewText.length &&
|
|
330
|
+
(skipRegressive === "always" || hadPreviewMessage);
|
|
331
|
+
if (shouldSkipRegressive) {
|
|
332
|
+
// Avoid regressive punctuation/wording flicker from occasional shorter finals.
|
|
333
|
+
deliveryState.delivered = true;
|
|
334
|
+
return true;
|
|
335
|
+
}
|
|
336
|
+
try {
|
|
337
|
+
await editMessageTelegram(chatId, previewMessageId, text, {
|
|
338
|
+
api: bot.api,
|
|
339
|
+
cfg,
|
|
340
|
+
accountId: route.accountId,
|
|
341
|
+
linkPreview: telegramCfg.linkPreview,
|
|
342
|
+
buttons: previewButtons,
|
|
343
|
+
});
|
|
344
|
+
if (updateLaneSnapshot) {
|
|
345
|
+
lane.lastPartialText = text;
|
|
346
|
+
lane.draftText = text;
|
|
347
|
+
}
|
|
348
|
+
deliveryState.delivered = true;
|
|
349
|
+
return true;
|
|
350
|
+
}
|
|
351
|
+
catch (err) {
|
|
352
|
+
logVerbose(`telegram: ${laneName} preview ${context} edit failed; falling back to standard send (${String(err)})`);
|
|
353
|
+
return false;
|
|
354
|
+
}
|
|
355
|
+
};
|
|
356
|
+
const applyTextToPayload = (payload, text) => {
|
|
357
|
+
if (payload.text === text) {
|
|
358
|
+
return payload;
|
|
359
|
+
}
|
|
360
|
+
return { ...payload, text };
|
|
361
|
+
};
|
|
362
|
+
const sendPayload = async (payload) => {
|
|
363
|
+
const result = await deliverReplies({
|
|
364
|
+
...deliveryBaseOptions,
|
|
365
|
+
replies: [payload],
|
|
366
|
+
onVoiceRecording: sendRecordVoice,
|
|
367
|
+
});
|
|
368
|
+
if (result.delivered) {
|
|
369
|
+
deliveryState.delivered = true;
|
|
370
|
+
}
|
|
371
|
+
return result.delivered;
|
|
372
|
+
};
|
|
373
|
+
const deliverLaneText = async (params) => {
|
|
374
|
+
const { laneName, text, payload, infoKind, previewButtons, allowPreviewUpdateForNonFinal = false, } = params;
|
|
375
|
+
const lane = lanes[laneName];
|
|
376
|
+
const hasMedia = Boolean(payload.mediaUrl) || (payload.mediaUrls?.length ?? 0) > 0;
|
|
377
|
+
const canEditViaPreview = !hasMedia && text.length > 0 && text.length <= draftMaxChars && !payload.isError;
|
|
378
|
+
if (infoKind === "final") {
|
|
379
|
+
if (canEditViaPreview && !finalizedPreviewByLane[laneName]) {
|
|
380
|
+
await flushDraftLane(lane);
|
|
381
|
+
const finalized = await tryUpdatePreviewForLane({
|
|
382
|
+
lane,
|
|
383
|
+
laneName,
|
|
384
|
+
text,
|
|
385
|
+
previewButtons,
|
|
386
|
+
stopBeforeEdit: true,
|
|
387
|
+
skipRegressive: "existingOnly",
|
|
388
|
+
context: "final",
|
|
389
|
+
});
|
|
390
|
+
if (finalized) {
|
|
391
|
+
finalizedPreviewByLane[laneName] = true;
|
|
392
|
+
return "preview-finalized";
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
else if (!hasMedia && !payload.isError && text.length > draftMaxChars) {
|
|
396
|
+
logVerbose(`telegram: preview final too long for edit (${text.length} > ${draftMaxChars}); falling back to standard send`);
|
|
397
|
+
}
|
|
398
|
+
await lane.stream?.stop();
|
|
399
|
+
const delivered = await sendPayload(applyTextToPayload(payload, text));
|
|
400
|
+
return delivered ? "sent" : "skipped";
|
|
401
|
+
}
|
|
402
|
+
if (allowPreviewUpdateForNonFinal && canEditViaPreview) {
|
|
403
|
+
const updated = await tryUpdatePreviewForLane({
|
|
404
|
+
lane,
|
|
405
|
+
laneName,
|
|
406
|
+
text,
|
|
407
|
+
previewButtons,
|
|
408
|
+
stopBeforeEdit: false,
|
|
409
|
+
updateLaneSnapshot: true,
|
|
410
|
+
skipRegressive: "always",
|
|
411
|
+
context: "update",
|
|
412
|
+
});
|
|
413
|
+
if (updated) {
|
|
414
|
+
return "preview-updated";
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
const delivered = await sendPayload(applyTextToPayload(payload, text));
|
|
418
|
+
return delivered ? "sent" : "skipped";
|
|
419
|
+
};
|
|
229
420
|
let queuedFinal = false;
|
|
230
421
|
try {
|
|
231
422
|
({ queuedFinal } = await dispatchReplyWithBufferedBlockDispatcher({
|
|
@@ -234,91 +425,74 @@ export const dispatchTelegramMessage = async ({ context, bot, cfg, runtime, repl
|
|
|
234
425
|
dispatcherOptions: {
|
|
235
426
|
...prefixOptions,
|
|
236
427
|
deliver: async (payload, info) => {
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
const
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
let draftStoppedForPreviewEdit = false;
|
|
245
|
-
// Skip preview edit for error payloads to avoid overwriting previous content
|
|
246
|
-
const canFinalizeViaPreviewEdit = !finalizedViaPreviewMessage &&
|
|
247
|
-
!hasMedia &&
|
|
248
|
-
typeof finalText === "string" &&
|
|
249
|
-
finalText.length > 0 &&
|
|
250
|
-
typeof previewMessageId === "number" &&
|
|
251
|
-
finalText.length <= draftMaxChars &&
|
|
252
|
-
!payload.isError;
|
|
253
|
-
if (canFinalizeViaPreviewEdit) {
|
|
254
|
-
await draftStream?.stop();
|
|
255
|
-
draftStoppedForPreviewEdit = true;
|
|
256
|
-
if (currentPreviewText &&
|
|
257
|
-
currentPreviewText.startsWith(finalText) &&
|
|
258
|
-
finalText.length < currentPreviewText.length) {
|
|
259
|
-
// Ignore regressive final edits (e.g., "Okay." -> "Ok"), which
|
|
260
|
-
// can appear transiently in some provider streams.
|
|
261
|
-
return;
|
|
262
|
-
}
|
|
263
|
-
try {
|
|
264
|
-
await editMessageTelegram(chatId, previewMessageId, finalText, {
|
|
265
|
-
api: bot.api,
|
|
266
|
-
cfg,
|
|
267
|
-
accountId: route.accountId,
|
|
268
|
-
linkPreview: telegramCfg.linkPreview,
|
|
269
|
-
buttons: previewButtons,
|
|
270
|
-
});
|
|
271
|
-
finalizedViaPreviewMessage = true;
|
|
272
|
-
deliveryState.delivered = true;
|
|
273
|
-
return;
|
|
274
|
-
}
|
|
275
|
-
catch (err) {
|
|
276
|
-
logVerbose(`telegram: preview final edit failed; falling back to standard send (${String(err)})`);
|
|
277
|
-
}
|
|
428
|
+
const previewButtons = payload.channelData?.telegram?.buttons;
|
|
429
|
+
const segments = splitTextIntoLaneSegments(payload.text);
|
|
430
|
+
const hasMedia = Boolean(payload.mediaUrl) || (payload.mediaUrls?.length ?? 0) > 0;
|
|
431
|
+
const flushBufferedFinalAnswer = async () => {
|
|
432
|
+
const buffered = reasoningStepState.takeBufferedFinalAnswer();
|
|
433
|
+
if (!buffered) {
|
|
434
|
+
return;
|
|
278
435
|
}
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
436
|
+
const bufferedButtons = buffered.payload.channelData?.telegram?.buttons;
|
|
437
|
+
await deliverLaneText({
|
|
438
|
+
laneName: "answer",
|
|
439
|
+
text: buffered.text,
|
|
440
|
+
payload: buffered.payload,
|
|
441
|
+
infoKind: "final",
|
|
442
|
+
previewButtons: bufferedButtons,
|
|
443
|
+
});
|
|
444
|
+
reasoningStepState.resetForNextStep();
|
|
445
|
+
};
|
|
446
|
+
for (const segment of segments) {
|
|
447
|
+
if (segment.lane === "answer" &&
|
|
448
|
+
info.kind === "final" &&
|
|
449
|
+
reasoningStepState.shouldBufferFinalAnswer()) {
|
|
450
|
+
reasoningStepState.bufferFinalAnswer({ payload, text: segment.text });
|
|
451
|
+
continue;
|
|
284
452
|
}
|
|
285
|
-
if (
|
|
286
|
-
|
|
453
|
+
if (segment.lane === "reasoning") {
|
|
454
|
+
reasoningStepState.noteReasoningHint();
|
|
287
455
|
}
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
await
|
|
300
|
-
api: bot.api,
|
|
301
|
-
cfg,
|
|
302
|
-
accountId: route.accountId,
|
|
303
|
-
linkPreview: telegramCfg.linkPreview,
|
|
304
|
-
buttons: previewButtons,
|
|
305
|
-
});
|
|
306
|
-
finalizedViaPreviewMessage = true;
|
|
307
|
-
deliveryState.delivered = true;
|
|
308
|
-
return;
|
|
456
|
+
const result = await deliverLaneText({
|
|
457
|
+
laneName: segment.lane,
|
|
458
|
+
text: segment.text,
|
|
459
|
+
payload,
|
|
460
|
+
infoKind: info.kind,
|
|
461
|
+
previewButtons,
|
|
462
|
+
allowPreviewUpdateForNonFinal: segment.lane === "reasoning",
|
|
463
|
+
});
|
|
464
|
+
if (segment.lane === "reasoning") {
|
|
465
|
+
if (result !== "skipped") {
|
|
466
|
+
reasoningStepState.noteReasoningDelivered();
|
|
467
|
+
await flushBufferedFinalAnswer();
|
|
309
468
|
}
|
|
310
|
-
|
|
311
|
-
|
|
469
|
+
continue;
|
|
470
|
+
}
|
|
471
|
+
if (info.kind === "final") {
|
|
472
|
+
if (reasoningLane.hasStreamedMessage) {
|
|
473
|
+
finalizedPreviewByLane.reasoning = true;
|
|
312
474
|
}
|
|
475
|
+
reasoningStepState.resetForNextStep();
|
|
313
476
|
}
|
|
314
477
|
}
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
478
|
+
if (segments.length > 0) {
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
if (info.kind === "final") {
|
|
482
|
+
await answerLane.stream?.stop();
|
|
483
|
+
await reasoningLane.stream?.stop();
|
|
484
|
+
reasoningStepState.resetForNextStep();
|
|
485
|
+
}
|
|
486
|
+
const canSendAsIs = hasMedia || typeof payload.text !== "string" || payload.text.length > 0;
|
|
487
|
+
if (!canSendAsIs) {
|
|
488
|
+
if (info.kind === "final") {
|
|
489
|
+
await flushBufferedFinalAnswer();
|
|
490
|
+
}
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
493
|
+
await sendPayload(payload);
|
|
494
|
+
if (info.kind === "final") {
|
|
495
|
+
await flushBufferedFinalAnswer();
|
|
322
496
|
}
|
|
323
497
|
},
|
|
324
498
|
onSkip: (_payload, info) => {
|
|
@@ -327,6 +501,7 @@ export const dispatchTelegramMessage = async ({ context, bot, cfg, runtime, repl
|
|
|
327
501
|
}
|
|
328
502
|
},
|
|
329
503
|
onError: (err, info) => {
|
|
504
|
+
deliveryState.failedNonSilent += 1;
|
|
330
505
|
runtime.error?.(danger(`telegram ${info.kind} reply failed: ${String(err)}`));
|
|
331
506
|
},
|
|
332
507
|
onReplyStart: createTypingCallbacks({
|
|
@@ -344,30 +519,36 @@ export const dispatchTelegramMessage = async ({ context, bot, cfg, runtime, repl
|
|
|
344
519
|
replyOptions: {
|
|
345
520
|
skillFilter,
|
|
346
521
|
disableBlockStreaming,
|
|
347
|
-
onPartialReply:
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
522
|
+
onPartialReply: answerLane.stream || reasoningLane.stream
|
|
523
|
+
? (payload) => ingestDraftLaneSegments(payload.text)
|
|
524
|
+
: undefined,
|
|
525
|
+
onReasoningStream: reasoningLane.stream
|
|
526
|
+
? (payload) => {
|
|
527
|
+
// Split between reasoning blocks only when the next reasoning
|
|
528
|
+
// stream starts. Splitting at reasoning-end can orphan the active
|
|
529
|
+
// preview and cause duplicate reasoning sends on reasoning final.
|
|
530
|
+
if (splitReasoningOnNextStream) {
|
|
531
|
+
reasoningLane.stream?.forceNewMessage();
|
|
532
|
+
resetDraftLaneState(reasoningLane);
|
|
533
|
+
splitReasoningOnNextStream = false;
|
|
356
534
|
}
|
|
357
|
-
|
|
358
|
-
draftText = "";
|
|
359
|
-
draftChunker?.reset();
|
|
535
|
+
ingestDraftLaneSegments(payload.text);
|
|
360
536
|
}
|
|
361
537
|
: undefined,
|
|
362
|
-
|
|
538
|
+
onAssistantMessageStart: answerLane.stream
|
|
363
539
|
? () => {
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
540
|
+
reasoningStepState.resetForNextStep();
|
|
541
|
+
// Keep answer blocks separated in block mode; partial mode keeps one answer lane.
|
|
542
|
+
if (streamMode === "block" && answerLane.hasStreamedMessage) {
|
|
543
|
+
answerLane.stream?.forceNewMessage();
|
|
367
544
|
}
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
545
|
+
resetDraftLaneState(answerLane);
|
|
546
|
+
}
|
|
547
|
+
: undefined,
|
|
548
|
+
onReasoningEnd: reasoningLane.stream
|
|
549
|
+
? () => {
|
|
550
|
+
// Split when/if a later reasoning block begins.
|
|
551
|
+
splitReasoningOnNextStream = reasoningLane.hasStreamedMessage;
|
|
371
552
|
}
|
|
372
553
|
: undefined,
|
|
373
554
|
onModelSelected,
|
|
@@ -375,14 +556,35 @@ export const dispatchTelegramMessage = async ({ context, bot, cfg, runtime, repl
|
|
|
375
556
|
}));
|
|
376
557
|
}
|
|
377
558
|
finally {
|
|
378
|
-
// Must stop() first to flush debounced content before clear() wipes state
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
559
|
+
// Must stop() first to flush debounced content before clear() wipes state.
|
|
560
|
+
const streamCleanupStates = new Map();
|
|
561
|
+
const lanesToCleanup = [
|
|
562
|
+
{ laneName: "answer", lane: answerLane },
|
|
563
|
+
{ laneName: "reasoning", lane: reasoningLane },
|
|
564
|
+
];
|
|
565
|
+
for (const laneState of lanesToCleanup) {
|
|
566
|
+
const stream = laneState.lane.stream;
|
|
567
|
+
if (!stream) {
|
|
568
|
+
continue;
|
|
569
|
+
}
|
|
570
|
+
const shouldClear = !finalizedPreviewByLane[laneState.laneName];
|
|
571
|
+
const existing = streamCleanupStates.get(stream);
|
|
572
|
+
if (!existing) {
|
|
573
|
+
streamCleanupStates.set(stream, { shouldClear });
|
|
574
|
+
continue;
|
|
575
|
+
}
|
|
576
|
+
existing.shouldClear = existing.shouldClear && shouldClear;
|
|
577
|
+
}
|
|
578
|
+
for (const [stream, cleanupState] of streamCleanupStates) {
|
|
579
|
+
await stream.stop();
|
|
580
|
+
if (cleanupState.shouldClear) {
|
|
581
|
+
await stream.clear();
|
|
582
|
+
}
|
|
382
583
|
}
|
|
383
584
|
}
|
|
384
585
|
let sentFallback = false;
|
|
385
|
-
if (!deliveryState.delivered &&
|
|
586
|
+
if (!deliveryState.delivered &&
|
|
587
|
+
(deliveryState.skippedNonSilent > 0 || deliveryState.failedNonSilent > 0)) {
|
|
386
588
|
const result = await deliverReplies({
|
|
387
589
|
replies: [{ text: EMPTY_RESPONSE_FALLBACK }],
|
|
388
590
|
...deliveryBaseOptions,
|