@poolzin/pool-bot 2026.3.25 → 2026.3.26

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.
Files changed (271) hide show
  1. package/dist/agents/model-fallback.js +5 -4
  2. package/dist/agents/tools/common.js +16 -201
  3. package/dist/auto-reply/auto-reply/reply/agent-runner-execution.js +502 -0
  4. package/dist/auto-reply/auto-reply/reply/agent-runner-helpers.js +65 -0
  5. package/dist/auto-reply/auto-reply/reply/agent-runner-memory.js +160 -0
  6. package/dist/auto-reply/auto-reply/reply/agent-runner-payloads.js +85 -0
  7. package/dist/auto-reply/auto-reply/reply/agent-runner-utils.js +101 -0
  8. package/dist/auto-reply/auto-reply/reply/bash-command.js +338 -0
  9. package/dist/auto-reply/auto-reply/reply/block-streaming.js +91 -0
  10. package/dist/auto-reply/auto-reply/reply/commands-approve.js +88 -0
  11. package/dist/auto-reply/auto-reply/reply/commands-bash.js +26 -0
  12. package/dist/auto-reply/auto-reply/reply/commands-compact.js +107 -0
  13. package/dist/auto-reply/auto-reply/reply/commands-config.js +241 -0
  14. package/dist/auto-reply/auto-reply/reply/commands-context-report.js +295 -0
  15. package/dist/auto-reply/auto-reply/reply/commands-context.js +30 -0
  16. package/dist/auto-reply/auto-reply/reply/commands-core.js +151 -0
  17. package/dist/auto-reply/auto-reply/reply/commands-export-session.js +163 -0
  18. package/dist/auto-reply/auto-reply/reply/commands-info.js +184 -0
  19. package/dist/auto-reply/auto-reply/reply/commands-models.js +299 -0
  20. package/dist/auto-reply/auto-reply/reply/commands-plugin.js +35 -0
  21. package/dist/auto-reply/auto-reply/reply/commands-ptt.js +171 -0
  22. package/dist/auto-reply/auto-reply/reply/commands-setunset-standard.js +13 -0
  23. package/dist/auto-reply/auto-reply/reply/commands-setunset.js +73 -0
  24. package/dist/auto-reply/auto-reply/reply/commands-slash-parse.js +31 -0
  25. package/dist/auto-reply/auto-reply/reply/commands-status.js +178 -0
  26. package/dist/auto-reply/auto-reply/reply/commands-subagents.js +73 -0
  27. package/dist/auto-reply/auto-reply/reply/commands-system-prompt.js +117 -0
  28. package/dist/auto-reply/auto-reply/reply/commands-tts.js +231 -0
  29. package/dist/auto-reply/auto-reply/reply/directive-handling.impl.js +380 -0
  30. package/dist/auto-reply/auto-reply/reply/followup-runner.js +227 -0
  31. package/dist/auto-reply/auto-reply/reply/get-reply-directives-apply.js +201 -0
  32. package/dist/auto-reply/auto-reply/reply/get-reply-directives-utils.js +54 -0
  33. package/dist/auto-reply/auto-reply/reply/get-reply-directives.js +332 -0
  34. package/dist/auto-reply/auto-reply/reply/get-reply-inline-actions.js +258 -0
  35. package/dist/auto-reply/auto-reply/reply/get-reply-run.js +297 -0
  36. package/dist/auto-reply/auto-reply/reply/groups.js +102 -0
  37. package/dist/auto-reply/auto-reply/reply/mentions.js +129 -0
  38. package/dist/auto-reply/auto-reply/reply/reply-delivery.js +92 -0
  39. package/dist/auto-reply/auto-reply/reply/reply-directives.js +30 -0
  40. package/dist/auto-reply/auto-reply/reply/reply-dispatcher.js +152 -0
  41. package/dist/auto-reply/auto-reply/reply/reply-elevated.js +166 -0
  42. package/dist/auto-reply/auto-reply/reply/reply-inline.js +28 -0
  43. package/dist/auto-reply/auto-reply/reply/reply-payloads.js +114 -0
  44. package/dist/auto-reply/auto-reply/reply/reply-reference.js +36 -0
  45. package/dist/auto-reply/auto-reply/reply/reply-tags.js +13 -0
  46. package/dist/auto-reply/auto-reply/reply/reply-threading.js +41 -0
  47. package/dist/auto-reply/auto-reply/reply/session-updates.js +233 -0
  48. package/dist/auto-reply/auto-reply/reply/stage-sandbox-media.js +146 -0
  49. package/dist/build-info.json +3 -3
  50. package/dist/canvas-host/a2ui/.bundle.hash +1 -1
  51. package/dist/canvas-host/a2ui/a2ui.bundle.js +2 -17772
  52. package/dist/canvas-host/a2ui/index.html +1 -307
  53. package/dist/channels/channels/directory-config.js +185 -0
  54. package/dist/channels/channels/discord/handle-action.guild-admin.js +332 -0
  55. package/dist/channels/channels/discord/handle-action.js +165 -0
  56. package/dist/channels/channels/discord.js +413 -0
  57. package/dist/channels/channels/dock.js +436 -0
  58. package/dist/channels/channels/index.js +51 -0
  59. package/dist/channels/channels/plugins/outbound/discord.js +101 -0
  60. package/dist/channels/channels/whatsapp.js +17 -0
  61. package/dist/channels/plugins/types.js +1 -1
  62. package/dist/channels/run-state-machine.js +7 -0
  63. package/dist/commands/models/auth.js +47 -1
  64. package/dist/commands-subagents/action-agents.js +44 -0
  65. package/dist/commands-subagents/action-focus.js +64 -0
  66. package/dist/commands-subagents/action-help.js +4 -0
  67. package/dist/commands-subagents/action-info.js +45 -0
  68. package/dist/commands-subagents/action-kill.js +60 -0
  69. package/dist/commands-subagents/action-list.js +44 -0
  70. package/dist/commands-subagents/action-log.js +29 -0
  71. package/dist/commands-subagents/action-send.js +119 -0
  72. package/dist/commands-subagents/action-spawn.js +52 -0
  73. package/dist/commands-subagents/action-unfocus.js +30 -0
  74. package/dist/commands-subagents/shared.js +303 -0
  75. package/dist/config/config.js +1 -8
  76. package/dist/config/types.secrets.js +61 -0
  77. package/dist/control-ui/assets/{index-D7shnQwQ.js → index-umCsvrWy.js} +884 -741
  78. package/dist/control-ui/assets/index-umCsvrWy.js.map +1 -0
  79. package/dist/control-ui/assets/pt-BR-DedEVAvY.js +2 -0
  80. package/dist/control-ui/assets/pt-BR-DedEVAvY.js.map +1 -0
  81. package/dist/control-ui/assets/zh-CN-CDzeklK-.js +2 -0
  82. package/dist/control-ui/assets/zh-CN-CDzeklK-.js.map +1 -0
  83. package/dist/control-ui/assets/zh-TW-BJCRYNWH.js +2 -0
  84. package/dist/control-ui/assets/zh-TW-BJCRYNWH.js.map +1 -0
  85. package/dist/control-ui/index.html +1 -1
  86. package/dist/gateway/method-scopes.js +9 -1
  87. package/dist/gateway/node-pending-work.js +142 -0
  88. package/dist/gateway/protocol/index.js +5 -1
  89. package/dist/gateway/protocol/schema/nodes.js +18 -0
  90. package/dist/gateway/server-methods/nodes-pending.js +96 -0
  91. package/dist/gateway/server-methods-list.js +4 -0
  92. package/dist/gateway/server-methods.js +2 -0
  93. package/dist/imessage/channel.js +253 -0
  94. package/dist/imessage/monitor/echo-cache.js +70 -0
  95. package/dist/imessage/monitor/loop-rate-limiter.js +51 -0
  96. package/dist/imessage/monitor/reflection-guard.js +50 -0
  97. package/dist/imessage/monitor/sanitize-outbound.js +25 -0
  98. package/dist/imessage/monitor/self-chat-cache.js +75 -0
  99. package/dist/imessage/runtime.js +3 -0
  100. package/dist/infra/exec-approval-reply.js +7 -0
  101. package/dist/infra/tmp-openclaw-dir.js +84 -0
  102. package/dist/pairing/pairing-challenge.js +15 -0
  103. package/dist/plugin-sdk/account-id.d.ts +1 -0
  104. package/dist/plugin-sdk/agent-media-payload.d.ts +12 -0
  105. package/dist/plugin-sdk/allow-from.d.ts +27 -0
  106. package/dist/plugin-sdk/command-auth.d.ts +25 -0
  107. package/dist/plugin-sdk/command-auth.js +3 -1
  108. package/dist/plugin-sdk/config-paths.d.ts +6 -0
  109. package/dist/plugin-sdk/file-lock.d.ts +16 -0
  110. package/dist/plugin-sdk/index.d.ts +428 -0
  111. package/dist/plugin-sdk/index.js +237 -103
  112. package/dist/plugin-sdk/json-store.d.ts +5 -0
  113. package/dist/plugin-sdk/keyed-async-queue.d.ts +12 -0
  114. package/dist/plugin-sdk/onboarding.d.ts +11 -0
  115. package/dist/plugin-sdk/provider-auth-result.d.ts +14 -0
  116. package/dist/plugin-sdk/slack-message-actions.d.ts +11 -0
  117. package/dist/plugin-sdk/status-helpers.d.ts +25 -0
  118. package/dist/plugin-sdk/temp-path.d.ts +12 -0
  119. package/dist/plugin-sdk/text-chunking.d.ts +1 -0
  120. package/dist/plugin-sdk/tool-send.d.ts +4 -0
  121. package/dist/plugin-sdk/webhook-path.d.ts +6 -0
  122. package/dist/plugin-sdk/webhook-targets.d.ts +23 -0
  123. package/dist/plugin-sdk/windows-spawn.d.ts +39 -0
  124. package/dist/plugin-sdk-internal/accounts.js +6 -0
  125. package/dist/plugin-sdk-internal/discord.js +23 -0
  126. package/dist/plugin-sdk-internal/imessage.js +13 -0
  127. package/dist/plugin-sdk-internal/setup.js +9 -0
  128. package/dist/plugin-sdk-internal/signal.js +13 -0
  129. package/dist/plugin-sdk-internal/slack.js +22 -0
  130. package/dist/plugin-sdk-internal/telegram.js +32 -0
  131. package/dist/plugin-sdk-internal/whatsapp.js +29 -0
  132. package/dist/routing/session-key.js +4 -185
  133. package/dist/shared/pid-alive.js +2 -61
  134. package/dist/shared/process-scoped-map.js +5 -7
  135. package/dist/signal/channel.js +264 -0
  136. package/dist/signal/monitor/access-policy.js +60 -0
  137. package/dist/signal/runtime.js +3 -0
  138. package/dist/slack/account-inspect.js +135 -0
  139. package/dist/slack/blocks-input.js +7 -38
  140. package/dist/slack/channel.js +394 -0
  141. package/dist/slack/interactive-replies.js +28 -0
  142. package/dist/slack/monitor/channel-type.js +31 -0
  143. package/dist/slack/monitor/dm-auth.js +49 -0
  144. package/dist/slack/monitor/events/interactions.modal.js +137 -0
  145. package/dist/slack/monitor/events/message-subtype-handlers.js +68 -0
  146. package/dist/slack/monitor/events/system-event-context.js +29 -0
  147. package/dist/slack/monitor/events/system-event-test-harness.js +41 -0
  148. package/dist/slack/monitor/external-arg-menu-store.js +46 -0
  149. package/dist/slack/monitor/message-handler/prepare-content.js +69 -0
  150. package/dist/slack/monitor/message-handler/prepare-thread-context.js +91 -0
  151. package/dist/slack/monitor/message-handler/prepare.test-helpers.js +55 -0
  152. package/dist/slack/monitor/reconnect-policy.js +78 -0
  153. package/dist/slack/monitor/slash-commands.runtime.js +1 -0
  154. package/dist/slack/monitor/slash-dispatch.runtime.js +9 -0
  155. package/dist/slack/monitor/slash-skill-commands.runtime.js +1 -0
  156. package/dist/slack/resolve-allowlist-common.js +36 -0
  157. package/dist/slack/runtime.js +3 -0
  158. package/dist/slack/sent-thread-cache.js +61 -0
  159. package/dist/slack/truncate.js +10 -0
  160. package/dist/telegram/account-inspect.js +175 -0
  161. package/dist/telegram/allow-from.js +10 -0
  162. package/dist/telegram/api-fetch.js +18 -0
  163. package/dist/telegram/approval-buttons.js +30 -0
  164. package/dist/telegram/audit-membership-runtime.js +61 -0
  165. package/dist/telegram/bot/delivery.replies.js +508 -0
  166. package/dist/telegram/bot/delivery.resolve-media.js +227 -0
  167. package/dist/telegram/bot/delivery.send.js +132 -0
  168. package/dist/telegram/bot/reply-threading.js +46 -0
  169. package/dist/telegram/bot-message-context.body.js +186 -0
  170. package/dist/telegram/bot-message-context.session.js +207 -0
  171. package/dist/telegram/bot-message-context.types.js +1 -0
  172. package/dist/telegram/bot-native-commands.test-helpers.js +117 -0
  173. package/dist/telegram/bot.media.e2e-harness.js +81 -0
  174. package/dist/telegram/bot.media.test-utils.js +81 -0
  175. package/dist/telegram/channel-actions.js +225 -0
  176. package/dist/telegram/channel.js +515 -0
  177. package/dist/telegram/conversation-route.js +107 -0
  178. package/dist/telegram/delivery.js +2 -0
  179. package/dist/telegram/delivery.replies.js +508 -0
  180. package/dist/telegram/dm-access.js +86 -0
  181. package/dist/telegram/draft-stream.test-helpers.js +62 -0
  182. package/dist/telegram/exec-approvals-handler.js +281 -0
  183. package/dist/telegram/exec-approvals.js +62 -0
  184. package/dist/telegram/forum-service-message.js +22 -0
  185. package/dist/telegram/group-config-helpers.js +10 -0
  186. package/dist/telegram/lane-delivery-state.js +19 -0
  187. package/dist/telegram/lane-delivery-text-deliverer.js +357 -0
  188. package/dist/telegram/lane-delivery.js +2 -0
  189. package/dist/telegram/normalize.js +37 -0
  190. package/dist/telegram/onboarding.js +192 -0
  191. package/dist/telegram/outbound-adapter.js +100 -0
  192. package/dist/telegram/polling-session.js +275 -0
  193. package/dist/telegram/runtime.js +3 -0
  194. package/dist/telegram/sendchataction-401-backoff.js +71 -0
  195. package/dist/telegram/sequential-key.js +46 -0
  196. package/dist/telegram/status-issues.js +105 -0
  197. package/dist/telegram/target-writeback.js +165 -0
  198. package/dist/telegram/thread-bindings.js +560 -0
  199. package/dist/utils.js +10 -276
  200. package/dist/wizard/prompts.js +5 -5
  201. package/extensions/feishu/src/policy.ts +1 -1
  202. package/extensions/firecrawl/index.test.ts +82 -0
  203. package/extensions/firecrawl/index.ts +20 -0
  204. package/extensions/firecrawl/openclaw.plugin.json +8 -0
  205. package/extensions/firecrawl/package.json +12 -0
  206. package/extensions/firecrawl/src/config.ts +159 -0
  207. package/extensions/firecrawl/src/firecrawl-client.ts +446 -0
  208. package/extensions/firecrawl/src/firecrawl-scrape-tool.ts +89 -0
  209. package/extensions/firecrawl/src/firecrawl-search-provider.ts +63 -0
  210. package/extensions/firecrawl/src/firecrawl-search-tool.ts +76 -0
  211. package/package.json +1 -1
  212. package/dist/.buildstamp +0 -1
  213. package/dist/acp/bindings-store.js +0 -209
  214. package/dist/acp/control-plane/runtime-cache.js +0 -54
  215. package/dist/acp/control-plane/runtime-options.js +0 -215
  216. package/dist/acp/control-plane/session-actor-queue.js +0 -36
  217. package/dist/acp/index.js +0 -2
  218. package/dist/acp/runtime/errors.js +0 -47
  219. package/dist/acp/runtime/registry.js +0 -86
  220. package/dist/acp/secret-file.js +0 -22
  221. package/dist/agents/auth-profiles.resolve-auth-profile-order.fixtures.js +0 -23
  222. package/dist/agents/bash-process-registry.test-helpers.js +0 -29
  223. package/dist/agents/bash-tools.exec-approval-request.js +0 -20
  224. package/dist/agents/bash-tools.exec-host-gateway.js +0 -240
  225. package/dist/agents/bash-tools.exec-host-node.js +0 -235
  226. package/dist/agents/checkpoint-manager.js +0 -290
  227. package/dist/agents/claude-cli-runner.js +0 -3
  228. package/dist/agents/error-classifier.js +0 -251
  229. package/dist/agents/live-model-filter.js +0 -84
  230. package/dist/agents/nvidia-models.js +0 -228
  231. package/dist/agents/pi-embedded-runner/run.overflow-compaction.fixture.js +0 -34
  232. package/dist/agents/pi-embedded-runner/run.overflow-compaction.mocks.shared.js +0 -156
  233. package/dist/agents/pi-embedded-subscribe.handlers.tools.media.test-helpers.js +0 -30
  234. package/dist/agents/provider/config-loader.js +0 -76
  235. package/dist/agents/provider/index.js +0 -15
  236. package/dist/agents/provider/models-dev.js +0 -129
  237. package/dist/agents/provider/session-binding.js +0 -376
  238. package/dist/agents/queued-file-writer.js +0 -22
  239. package/dist/agents/skills/bundled-context.js +0 -23
  240. package/dist/agents/skills/security.js +0 -211
  241. package/dist/agents/skills/tools-dir.js +0 -9
  242. package/dist/agents/skills-install-download.js +0 -290
  243. package/dist/agents/skills-install-output.js +0 -30
  244. package/dist/agents/skills-install.download-test-utils.js +0 -36
  245. package/dist/agents/skills.test-helpers.js +0 -13
  246. package/dist/agents/subagent-announce-reliability.js +0 -160
  247. package/dist/agents/subagent-registry.mocks.shared.js +0 -12
  248. package/dist/agents/test-helpers/assistant-message-fixtures.js +0 -29
  249. package/dist/agents/test-helpers/fast-coding-tools.js +0 -1
  250. package/dist/agents/test-helpers/fast-core-tools.js +0 -8
  251. package/dist/agents/test-helpers/fast-tool-stubs.js +0 -18
  252. package/dist/agents/test-helpers/host-sandbox-fs-bridge.js +0 -74
  253. package/dist/agents/test-helpers/pi-tools-sandbox-context.js +0 -27
  254. package/dist/agents/tool-display-common.js +0 -915
  255. package/dist/agents/tool-policy-shared.js +0 -108
  256. package/dist/agents/tool-policy.conformance.js +0 -14
  257. package/dist/agents/tool-result-truncation.js +0 -299
  258. package/dist/agents/tools/cron-tool.test-helpers.js +0 -12
  259. package/dist/agents/tools/discord-actions-moderation-shared.js +0 -27
  260. package/dist/agents/tools/discord-actions-presence.js +0 -78
  261. package/dist/control-ui/assets/index-D7shnQwQ.js.map +0 -1
  262. package/dist/discord/discord-improvements.js +0 -167
  263. package/dist/discord/index.js +0 -2
  264. package/dist/hooks/bundled/boot-md/HOOK.md +0 -19
  265. package/dist/hooks/bundled/command-logger/HOOK.md +0 -122
  266. package/dist/hooks/bundled/session-memory/HOOK.md +0 -86
  267. package/dist/hooks/bundled/soul-evil/HOOK.md +0 -71
  268. package/dist/whatsapp/normalize.js +0 -66
  269. package/dist/whatsapp/resolve-outbound-target.js +0 -42
  270. /package/dist/{acp/runtime/types.js → auto-reply/auto-reply/reply/commands-types.js} +0 -0
  271. /package/dist/{agents/pi-embedded-payloads.js → slack/account-surface-fields.js} +0 -0
@@ -0,0 +1,297 @@
1
+ import crypto from "node:crypto";
2
+ import { abortEmbeddedPiRun, isEmbeddedPiRunActive, isEmbeddedPiRunStreaming, resolveEmbeddedSessionLane, } from "../../agents/pi-embedded.js";
3
+ import { resolveSessionAuthProfileOverride } from "../../agents/auth-profiles/session-override.js";
4
+ import { resolveGroupSessionKey, resolveSessionFilePath, updateSessionStore, } from "../../config/sessions.js";
5
+ import { logVerbose } from "../../globals.js";
6
+ import { clearCommandLane, getQueueSize } from "../../process/command-queue.js";
7
+ import { normalizeMainKey } from "../../routing/session-key.js";
8
+ import { isReasoningTagProvider } from "../../utils/provider-utils.js";
9
+ import { hasControlCommand } from "../command-detection.js";
10
+ import { buildInboundMediaNote } from "../media-note.js";
11
+ import { formatXHighModelHint, normalizeThinkLevel, supportsXHighThinking, } from "../thinking.js";
12
+ import { SILENT_REPLY_TOKEN } from "../tokens.js";
13
+ import { runReplyAgent } from "./agent-runner.js";
14
+ import { applySessionHints } from "./body.js";
15
+ import { routeReply } from "./route-reply.js";
16
+ import { buildGroupIntro } from "./groups.js";
17
+ import { resolveQueueSettings } from "./queue.js";
18
+ import { ensureSkillSnapshot, prependSystemEvents } from "./session-updates.js";
19
+ import { resolveTypingMode } from "./typing-mode.js";
20
+ import { buildInboundMetaSystemPrompt, buildInboundUserContextPrefix } from "./inbound-meta.js";
21
+ import { appendUntrustedContext } from "./untrusted-context.js";
22
+ const BARE_SESSION_RESET_PROMPT = "A new session was started via /new or /reset. Greet the user in your configured persona, if one is provided. Be yourself - use your defined voice, mannerisms, and mood. Keep it to 1-3 sentences and ask what they want to do. If the runtime model differs from default_model in the system prompt, mention the default model. Do not mention internal steps, files, tools, or reasoning.";
23
+ export async function runPreparedReply(params) {
24
+ const { ctx, sessionCtx, cfg, agentId, agentDir, agentCfg, sessionCfg, commandAuthorized, command, commandSource, allowTextCommands, directives, defaultActivation, elevatedEnabled, elevatedAllowed, blockStreamingEnabled, blockReplyChunking, resolvedBlockStreamingBreak, modelState, provider, model, perMessageQueueMode, perMessageQueueOptions, typing, opts, defaultProvider, defaultModel, timeoutMs, isNewSession, resetTriggered, systemSent, sessionKey, sessionId, storePath, workspaceDir, sessionStore, } = params;
25
+ let { sessionEntry, resolvedThinkLevel, resolvedVerboseLevel, resolvedReasoningLevel, resolvedElevatedLevel, execOverrides, abortedLastRun, } = params;
26
+ let currentSystemSent = systemSent;
27
+ const isFirstTurnInSession = isNewSession || !currentSystemSent;
28
+ const isGroupChat = sessionCtx.ChatType === "group";
29
+ const wasMentioned = ctx.WasMentioned === true;
30
+ const isHeartbeat = opts?.isHeartbeat === true;
31
+ const typingMode = resolveTypingMode({
32
+ configured: sessionCfg?.typingMode ?? agentCfg?.typingMode,
33
+ isGroupChat,
34
+ wasMentioned,
35
+ isHeartbeat,
36
+ });
37
+ const shouldInjectGroupIntro = Boolean(isGroupChat && (isFirstTurnInSession || sessionEntry?.groupActivationNeedsSystemIntro));
38
+ const groupIntro = shouldInjectGroupIntro
39
+ ? buildGroupIntro({
40
+ cfg,
41
+ sessionCtx,
42
+ sessionEntry,
43
+ defaultActivation,
44
+ silentToken: SILENT_REPLY_TOKEN,
45
+ })
46
+ : "";
47
+ const groupSystemPrompt = sessionCtx.GroupSystemPrompt?.trim() ?? "";
48
+ const inboundMetaPrompt = buildInboundMetaSystemPrompt(isNewSession ? sessionCtx : { ...sessionCtx, ThreadStarterBody: undefined });
49
+ const extraSystemPrompt = [inboundMetaPrompt, groupIntro, groupSystemPrompt]
50
+ .filter(Boolean)
51
+ .join("\n\n");
52
+ const baseBody = sessionCtx.BodyStripped ?? sessionCtx.Body ?? "";
53
+ // Use CommandBody/RawBody for bare reset detection (clean message without structural context).
54
+ const rawBodyTrimmed = (ctx.CommandBody ?? ctx.RawBody ?? ctx.Body ?? "").trim();
55
+ const baseBodyTrimmedRaw = baseBody.trim();
56
+ if (allowTextCommands &&
57
+ (!commandAuthorized || !command.isAuthorizedSender) &&
58
+ !baseBodyTrimmedRaw &&
59
+ hasControlCommand(commandSource, cfg)) {
60
+ typing.cleanup();
61
+ return undefined;
62
+ }
63
+ const isBareNewOrReset = rawBodyTrimmed === "/new" || rawBodyTrimmed === "/reset";
64
+ const isBareSessionReset = isNewSession &&
65
+ ((baseBodyTrimmedRaw.length === 0 && rawBodyTrimmed.length > 0) || isBareNewOrReset);
66
+ const baseBodyFinal = isBareSessionReset ? BARE_SESSION_RESET_PROMPT : baseBody;
67
+ const inboundUserContext = buildInboundUserContextPrefix(isNewSession ? sessionCtx : { ...sessionCtx, ThreadStarterBody: undefined });
68
+ const baseBodyForPrompt = isBareSessionReset
69
+ ? baseBodyFinal
70
+ : [inboundUserContext, baseBodyFinal].filter(Boolean).join("\n\n");
71
+ const baseBodyTrimmed = baseBodyForPrompt.trim();
72
+ if (!baseBodyTrimmed) {
73
+ await typing.onReplyStart();
74
+ logVerbose("Inbound body empty after normalization; skipping agent run");
75
+ typing.cleanup();
76
+ return {
77
+ text: "I didn't receive any text in your message. Please resend or add a caption.",
78
+ };
79
+ }
80
+ let prefixedBodyBase = await applySessionHints({
81
+ baseBody: baseBodyForPrompt,
82
+ abortedLastRun,
83
+ sessionEntry,
84
+ sessionStore,
85
+ sessionKey,
86
+ storePath,
87
+ abortKey: command.abortKey,
88
+ messageId: sessionCtx.MessageSid,
89
+ });
90
+ const isGroupSession = sessionEntry?.chatType === "group" || sessionEntry?.chatType === "channel";
91
+ const isMainSession = !isGroupSession && sessionKey === normalizeMainKey(sessionCfg?.mainKey);
92
+ prefixedBodyBase = await prependSystemEvents({
93
+ cfg,
94
+ sessionKey,
95
+ isMainSession,
96
+ isNewSession,
97
+ prefixedBodyBase,
98
+ });
99
+ prefixedBodyBase = appendUntrustedContext(prefixedBodyBase, sessionCtx.UntrustedContext);
100
+ const threadStarterBody = ctx.ThreadStarterBody?.trim();
101
+ const threadStarterNote = isNewSession && threadStarterBody
102
+ ? `[Thread starter - for context]\n${threadStarterBody}`
103
+ : undefined;
104
+ const skillResult = await ensureSkillSnapshot({
105
+ sessionEntry,
106
+ sessionStore,
107
+ sessionKey,
108
+ storePath,
109
+ sessionId,
110
+ isFirstTurnInSession,
111
+ workspaceDir,
112
+ cfg,
113
+ skillFilter: opts?.skillFilter,
114
+ });
115
+ sessionEntry = skillResult.sessionEntry ?? sessionEntry;
116
+ currentSystemSent = skillResult.systemSent;
117
+ const skillsSnapshot = skillResult.skillsSnapshot;
118
+ const prefixedBody = [threadStarterNote, prefixedBodyBase].filter(Boolean).join("\n\n");
119
+ const mediaNote = buildInboundMediaNote(ctx);
120
+ const mediaReplyHint = mediaNote
121
+ ? "To send an image back, prefer the message tool (media/path/filePath). If you must inline, use MEDIA:https://example.com/image.jpg (spaces ok, quote if needed) or a safe relative path like MEDIA:./image.jpg. Avoid absolute paths (MEDIA:/...) and ~ paths — they are blocked for security. Keep caption in the text body."
122
+ : undefined;
123
+ let prefixedCommandBody = mediaNote
124
+ ? [mediaNote, mediaReplyHint, prefixedBody ?? ""].filter(Boolean).join("\n").trim()
125
+ : prefixedBody;
126
+ if (!resolvedThinkLevel && prefixedCommandBody) {
127
+ const parts = prefixedCommandBody.split(/\s+/);
128
+ const maybeLevel = normalizeThinkLevel(parts[0]);
129
+ if (maybeLevel && (maybeLevel !== "xhigh" || supportsXHighThinking(provider, model))) {
130
+ resolvedThinkLevel = maybeLevel;
131
+ prefixedCommandBody = parts.slice(1).join(" ").trim();
132
+ }
133
+ }
134
+ if (!resolvedThinkLevel) {
135
+ resolvedThinkLevel = await modelState.resolveDefaultThinkingLevel();
136
+ }
137
+ if (resolvedThinkLevel === "xhigh" && !supportsXHighThinking(provider, model)) {
138
+ const explicitThink = directives.hasThinkDirective && directives.thinkLevel !== undefined;
139
+ if (explicitThink) {
140
+ typing.cleanup();
141
+ return {
142
+ text: `Thinking level "xhigh" is only supported for ${formatXHighModelHint()}. Use /think high or switch to one of those models.`,
143
+ };
144
+ }
145
+ resolvedThinkLevel = "high";
146
+ if (sessionEntry && sessionStore && sessionKey && sessionEntry.thinkingLevel === "xhigh") {
147
+ sessionEntry.thinkingLevel = "high";
148
+ sessionEntry.updatedAt = Date.now();
149
+ sessionStore[sessionKey] = sessionEntry;
150
+ if (storePath) {
151
+ await updateSessionStore(storePath, (store) => {
152
+ store[sessionKey] = sessionEntry;
153
+ });
154
+ }
155
+ }
156
+ }
157
+ if (resetTriggered && command.isAuthorizedSender) {
158
+ const channel = ctx.OriginatingChannel || command.channel;
159
+ const to = ctx.OriginatingTo || command.from || command.to;
160
+ if (channel && to) {
161
+ const modelLabel = `${provider}/${model}`;
162
+ const defaultLabel = `${defaultProvider}/${defaultModel}`;
163
+ const text = modelLabel === defaultLabel
164
+ ? `✅ New session started · model: ${modelLabel}`
165
+ : `✅ New session started · model: ${modelLabel} (default: ${defaultLabel})`;
166
+ await routeReply({
167
+ payload: { text },
168
+ channel,
169
+ to,
170
+ sessionKey,
171
+ accountId: ctx.AccountId,
172
+ threadId: ctx.MessageThreadId,
173
+ cfg,
174
+ });
175
+ }
176
+ }
177
+ const sessionIdFinal = sessionId ?? crypto.randomUUID();
178
+ const sessionFile = resolveSessionFilePath(sessionIdFinal, sessionEntry);
179
+ const queueBodyBase = [threadStarterNote, baseBodyFinal].filter(Boolean).join("\n\n");
180
+ const queueMessageId = sessionCtx.MessageSid?.trim();
181
+ const queueMessageIdHint = queueMessageId ? `[message_id: ${queueMessageId}]` : "";
182
+ const queueBodyWithId = queueMessageIdHint
183
+ ? `${queueBodyBase}\n${queueMessageIdHint}`
184
+ : queueBodyBase;
185
+ const queuedBody = mediaNote
186
+ ? [mediaNote, mediaReplyHint, queueBodyWithId].filter(Boolean).join("\n").trim()
187
+ : queueBodyWithId;
188
+ const resolvedQueue = resolveQueueSettings({
189
+ cfg,
190
+ channel: sessionCtx.Provider,
191
+ sessionEntry,
192
+ inlineMode: perMessageQueueMode,
193
+ inlineOptions: perMessageQueueOptions,
194
+ });
195
+ const sessionLaneKey = resolveEmbeddedSessionLane(sessionKey ?? sessionIdFinal);
196
+ const laneSize = getQueueSize(sessionLaneKey);
197
+ if (resolvedQueue.mode === "interrupt" && laneSize > 0) {
198
+ const cleared = clearCommandLane(sessionLaneKey);
199
+ const aborted = abortEmbeddedPiRun(sessionIdFinal);
200
+ logVerbose(`Interrupting ${sessionLaneKey} (cleared ${cleared}, aborted=${aborted})`);
201
+ }
202
+ const queueKey = sessionKey ?? sessionIdFinal;
203
+ const isActive = isEmbeddedPiRunActive(sessionIdFinal);
204
+ const isStreaming = isEmbeddedPiRunStreaming(sessionIdFinal);
205
+ const shouldSteer = resolvedQueue.mode === "steer" || resolvedQueue.mode === "steer-backlog";
206
+ const shouldFollowup = resolvedQueue.mode === "followup" ||
207
+ resolvedQueue.mode === "collect" ||
208
+ resolvedQueue.mode === "steer-backlog";
209
+ const authProfileId = await resolveSessionAuthProfileOverride({
210
+ cfg,
211
+ provider,
212
+ agentDir,
213
+ sessionEntry,
214
+ sessionStore,
215
+ sessionKey,
216
+ storePath,
217
+ isNewSession,
218
+ });
219
+ const authProfileIdSource = sessionEntry?.authProfileOverrideSource;
220
+ const followupRun = {
221
+ prompt: queuedBody,
222
+ messageId: sessionCtx.MessageSidFull ?? sessionCtx.MessageSid,
223
+ summaryLine: baseBodyTrimmedRaw,
224
+ enqueuedAt: Date.now(),
225
+ // Originating channel for reply routing.
226
+ originatingChannel: ctx.OriginatingChannel,
227
+ originatingTo: ctx.OriginatingTo,
228
+ originatingAccountId: ctx.AccountId,
229
+ originatingThreadId: ctx.MessageThreadId,
230
+ originatingChatType: ctx.ChatType,
231
+ run: {
232
+ agentId,
233
+ agentDir,
234
+ sessionId: sessionIdFinal,
235
+ sessionKey,
236
+ messageProvider: sessionCtx.Provider?.trim().toLowerCase() || undefined,
237
+ agentAccountId: sessionCtx.AccountId,
238
+ groupId: resolveGroupSessionKey(sessionCtx)?.id ?? undefined,
239
+ groupChannel: sessionCtx.GroupChannel?.trim() ?? sessionCtx.GroupSubject?.trim(),
240
+ groupSpace: sessionCtx.GroupSpace?.trim() ?? undefined,
241
+ senderId: sessionCtx.SenderId?.trim() || undefined,
242
+ senderName: sessionCtx.SenderName?.trim() || undefined,
243
+ senderUsername: sessionCtx.SenderUsername?.trim() || undefined,
244
+ senderE164: sessionCtx.SenderE164?.trim() || undefined,
245
+ senderIsOwner: command.senderIsOwner,
246
+ sessionFile,
247
+ workspaceDir,
248
+ config: cfg,
249
+ skillsSnapshot,
250
+ provider,
251
+ model,
252
+ authProfileId,
253
+ authProfileIdSource,
254
+ thinkLevel: resolvedThinkLevel,
255
+ verboseLevel: resolvedVerboseLevel,
256
+ reasoningLevel: resolvedReasoningLevel,
257
+ elevatedLevel: resolvedElevatedLevel,
258
+ execOverrides,
259
+ bashElevated: {
260
+ enabled: elevatedEnabled,
261
+ allowed: elevatedAllowed,
262
+ defaultLevel: resolvedElevatedLevel ?? "off",
263
+ },
264
+ timeoutMs,
265
+ blockReplyBreak: resolvedBlockStreamingBreak,
266
+ ownerNumbers: command.ownerList.length > 0 ? command.ownerList : undefined,
267
+ extraSystemPrompt: extraSystemPrompt || undefined,
268
+ ...(isReasoningTagProvider(provider) ? { enforceFinalTag: true } : {}),
269
+ },
270
+ };
271
+ return runReplyAgent({
272
+ commandBody: prefixedCommandBody,
273
+ followupRun,
274
+ queueKey,
275
+ resolvedQueue,
276
+ shouldSteer,
277
+ shouldFollowup,
278
+ isActive,
279
+ isStreaming,
280
+ opts,
281
+ typing,
282
+ sessionEntry,
283
+ sessionStore,
284
+ sessionKey,
285
+ storePath,
286
+ defaultModel,
287
+ agentCfgContextTokens: agentCfg?.contextTokens,
288
+ resolvedVerboseLevel: resolvedVerboseLevel ?? "off",
289
+ isNewSession,
290
+ blockStreamingEnabled,
291
+ blockReplyChunking,
292
+ resolvedBlockStreamingBreak,
293
+ sessionCtx,
294
+ shouldInjectGroupIntro,
295
+ typingMode,
296
+ });
297
+ }
@@ -0,0 +1,102 @@
1
+ import { getChannelDock } from "../../channels/dock.js";
2
+ import { getChannelPlugin, normalizeChannelId } from "../../channels/plugins/index.js";
3
+ import { isInternalMessageChannel } from "../../utils/message-channel.js";
4
+ import { normalizeGroupActivation } from "../group-activation.js";
5
+ function extractGroupId(raw) {
6
+ const trimmed = (raw ?? "").trim();
7
+ if (!trimmed)
8
+ return undefined;
9
+ const parts = trimmed.split(":").filter(Boolean);
10
+ if (parts.length >= 3 && (parts[1] === "group" || parts[1] === "channel")) {
11
+ return parts.slice(2).join(":") || undefined;
12
+ }
13
+ if (parts.length >= 2 &&
14
+ parts[0]?.toLowerCase() === "whatsapp" &&
15
+ trimmed.toLowerCase().includes("@g.us")) {
16
+ return parts.slice(1).join(":") || undefined;
17
+ }
18
+ if (parts.length >= 2 && (parts[0] === "group" || parts[0] === "channel")) {
19
+ return parts.slice(1).join(":") || undefined;
20
+ }
21
+ return trimmed;
22
+ }
23
+ export function resolveGroupRequireMention(params) {
24
+ const { cfg, ctx, groupResolution } = params;
25
+ const rawChannel = groupResolution?.channel ?? ctx.Provider?.trim();
26
+ const channel = normalizeChannelId(rawChannel);
27
+ if (!channel)
28
+ return true;
29
+ const groupId = groupResolution?.id ?? extractGroupId(ctx.From);
30
+ const groupChannel = ctx.GroupChannel?.trim() ?? ctx.GroupSubject?.trim();
31
+ const groupSpace = ctx.GroupSpace?.trim();
32
+ const requireMention = getChannelDock(channel)?.groups?.resolveRequireMention?.({
33
+ cfg,
34
+ groupId,
35
+ groupChannel,
36
+ groupSpace,
37
+ accountId: ctx.AccountId,
38
+ });
39
+ if (typeof requireMention === "boolean")
40
+ return requireMention;
41
+ return true;
42
+ }
43
+ export function defaultGroupActivation(requireMention) {
44
+ return requireMention === false ? "always" : "mention";
45
+ }
46
+ export function buildGroupIntro(params) {
47
+ const activation = normalizeGroupActivation(params.sessionEntry?.groupActivation) ?? params.defaultActivation;
48
+ const subject = params.sessionCtx.GroupSubject?.trim();
49
+ const members = params.sessionCtx.GroupMembers?.trim();
50
+ const rawProvider = params.sessionCtx.Provider?.trim();
51
+ const providerKey = rawProvider?.toLowerCase() ?? "";
52
+ const providerId = normalizeChannelId(rawProvider);
53
+ const providerLabel = (() => {
54
+ if (!providerKey)
55
+ return "chat";
56
+ if (isInternalMessageChannel(providerKey))
57
+ return "WebChat";
58
+ if (providerId)
59
+ return getChannelPlugin(providerId)?.meta.label ?? providerId;
60
+ return `${providerKey.at(0)?.toUpperCase() ?? ""}${providerKey.slice(1)}`;
61
+ })();
62
+ const subjectLine = subject
63
+ ? `You are replying inside the ${providerLabel} group "${subject}".`
64
+ : `You are replying inside a ${providerLabel} group chat.`;
65
+ const membersLine = members ? `Group members: ${members}.` : undefined;
66
+ const activationLine = activation === "always"
67
+ ? "Activation: always-on (you receive every group message)."
68
+ : "Activation: trigger-only (you are invoked only when explicitly mentioned; recent context may be included).";
69
+ const groupId = params.sessionEntry?.groupId ?? extractGroupId(params.sessionCtx.From);
70
+ const groupChannel = params.sessionCtx.GroupChannel?.trim() ?? subject;
71
+ const groupSpace = params.sessionCtx.GroupSpace?.trim();
72
+ const providerIdsLine = providerId
73
+ ? getChannelDock(providerId)?.groups?.resolveGroupIntroHint?.({
74
+ cfg: params.cfg,
75
+ groupId,
76
+ groupChannel,
77
+ groupSpace,
78
+ accountId: params.sessionCtx.AccountId,
79
+ })
80
+ : undefined;
81
+ const silenceLine = activation === "always"
82
+ ? `If no response is needed, reply with exactly "${params.silentToken}" (and nothing else) so Poolbot stays silent. Do not add any other words, punctuation, tags, markdown/code blocks, or explanations.`
83
+ : undefined;
84
+ const cautionLine = activation === "always"
85
+ ? "Be extremely selective: reply only when directly addressed or clearly helpful. Otherwise stay silent."
86
+ : undefined;
87
+ const lurkLine = "Be a good group participant: mostly lurk and follow the conversation; reply only when directly addressed or you can add clear value. Emoji reactions are welcome when available.";
88
+ const styleLine = "Write like a human. Avoid Markdown tables. Don't type literal \\n sequences; use real line breaks sparingly.";
89
+ return [
90
+ subjectLine,
91
+ membersLine,
92
+ activationLine,
93
+ providerIdsLine,
94
+ silenceLine,
95
+ cautionLine,
96
+ lurkLine,
97
+ styleLine,
98
+ ]
99
+ .filter(Boolean)
100
+ .join(" ")
101
+ .concat(" Address the specific sender noted in the message context.");
102
+ }
@@ -0,0 +1,129 @@
1
+ import { resolveAgentConfig } from "../../agents/agent-scope.js";
2
+ import { getChannelDock } from "../../channels/dock.js";
3
+ import { normalizeChannelId } from "../../channels/plugins/index.js";
4
+ import { escapeRegExp } from "../../utils.js";
5
+ function deriveMentionPatterns(identity) {
6
+ const patterns = [];
7
+ const name = identity?.name?.trim();
8
+ if (name) {
9
+ const parts = name.split(/\s+/).filter(Boolean).map(escapeRegExp);
10
+ const re = parts.length ? parts.join(String.raw `\s+`) : escapeRegExp(name);
11
+ patterns.push(String.raw `\b@?${re}\b`);
12
+ }
13
+ const emoji = identity?.emoji?.trim();
14
+ if (emoji) {
15
+ patterns.push(escapeRegExp(emoji));
16
+ }
17
+ return patterns;
18
+ }
19
+ const BACKSPACE_CHAR = "\u0008";
20
+ export const CURRENT_MESSAGE_MARKER = "[Current message - respond to this]";
21
+ function normalizeMentionPattern(pattern) {
22
+ if (!pattern.includes(BACKSPACE_CHAR)) {
23
+ return pattern;
24
+ }
25
+ return pattern.split(BACKSPACE_CHAR).join("\\b");
26
+ }
27
+ function normalizeMentionPatterns(patterns) {
28
+ return patterns.map(normalizeMentionPattern);
29
+ }
30
+ function resolveMentionPatterns(cfg, agentId) {
31
+ if (!cfg) {
32
+ return [];
33
+ }
34
+ const agentConfig = agentId ? resolveAgentConfig(cfg, agentId) : undefined;
35
+ const agentGroupChat = agentConfig?.groupChat;
36
+ if (agentGroupChat && Object.hasOwn(agentGroupChat, "mentionPatterns")) {
37
+ return agentGroupChat.mentionPatterns ?? [];
38
+ }
39
+ const globalGroupChat = cfg.messages?.groupChat;
40
+ if (globalGroupChat && Object.hasOwn(globalGroupChat, "mentionPatterns")) {
41
+ return globalGroupChat.mentionPatterns ?? [];
42
+ }
43
+ const derived = deriveMentionPatterns(agentConfig?.identity);
44
+ return derived.length > 0 ? derived : [];
45
+ }
46
+ export function buildMentionRegexes(cfg, agentId) {
47
+ const patterns = normalizeMentionPatterns(resolveMentionPatterns(cfg, agentId));
48
+ return patterns
49
+ .map((pattern) => {
50
+ try {
51
+ return new RegExp(pattern, "i");
52
+ }
53
+ catch {
54
+ return null;
55
+ }
56
+ })
57
+ .filter((value) => Boolean(value));
58
+ }
59
+ export function normalizeMentionText(text) {
60
+ return (text ?? "").replace(/[\u200b-\u200f\u202a-\u202e\u2060-\u206f]/g, "").toLowerCase();
61
+ }
62
+ export function matchesMentionPatterns(text, mentionRegexes) {
63
+ if (mentionRegexes.length === 0) {
64
+ return false;
65
+ }
66
+ const cleaned = normalizeMentionText(text ?? "");
67
+ if (!cleaned) {
68
+ return false;
69
+ }
70
+ return mentionRegexes.some((re) => re.test(cleaned));
71
+ }
72
+ export function matchesMentionWithExplicit(params) {
73
+ const cleaned = normalizeMentionText(params.text ?? "");
74
+ const explicit = params.explicit?.isExplicitlyMentioned === true;
75
+ const explicitAvailable = params.explicit?.canResolveExplicit === true;
76
+ const hasAnyMention = params.explicit?.hasAnyMention === true;
77
+ // Check transcript if text is empty and transcript is provided
78
+ const transcriptCleaned = params.transcript ? normalizeMentionText(params.transcript) : "";
79
+ const textToCheck = cleaned || transcriptCleaned;
80
+ if (hasAnyMention && explicitAvailable) {
81
+ return explicit || params.mentionRegexes.some((re) => re.test(textToCheck));
82
+ }
83
+ if (!textToCheck) {
84
+ return explicit;
85
+ }
86
+ return explicit || params.mentionRegexes.some((re) => re.test(textToCheck));
87
+ }
88
+ export function stripStructuralPrefixes(text) {
89
+ // Ignore wrapper labels, timestamps, and sender prefixes so directive-only
90
+ // detection still works in group batches that include history/context.
91
+ const afterMarker = text.includes(CURRENT_MESSAGE_MARKER)
92
+ ? text.slice(text.indexOf(CURRENT_MESSAGE_MARKER) + CURRENT_MESSAGE_MARKER.length).trimStart()
93
+ : text;
94
+ return afterMarker
95
+ .replace(/\[[^\]]+\]\s*/g, "")
96
+ .replace(/^[ \t]*[A-Za-z0-9+()\-_. ]+:\s*/gm, "")
97
+ .replace(/\\n/g, " ")
98
+ .replace(/\s+/g, " ")
99
+ .trim();
100
+ }
101
+ export function stripMentions(text, ctx, cfg, agentId) {
102
+ let result = text;
103
+ const providerId = ctx.Provider ? normalizeChannelId(ctx.Provider) : null;
104
+ const providerMentions = providerId ? getChannelDock(providerId)?.mentions : undefined;
105
+ const patterns = normalizeMentionPatterns([
106
+ ...resolveMentionPatterns(cfg, agentId),
107
+ ...(providerMentions?.stripPatterns?.({ ctx, cfg, agentId }) ?? []),
108
+ ]);
109
+ for (const p of patterns) {
110
+ try {
111
+ const re = new RegExp(p, "gi");
112
+ result = result.replace(re, " ");
113
+ }
114
+ catch {
115
+ // ignore invalid regex
116
+ }
117
+ }
118
+ if (providerMentions?.stripMentions) {
119
+ result = providerMentions.stripMentions({
120
+ text: result,
121
+ ctx,
122
+ cfg,
123
+ agentId,
124
+ });
125
+ }
126
+ // Generic mention patterns like @123456789 or plain digits
127
+ result = result.replace(/@[0-9+]{5,}/g, " ");
128
+ return result.replace(/\s+/g, " ").trim();
129
+ }
@@ -0,0 +1,92 @@
1
+ import { logVerbose } from "../../globals.js";
2
+ import { SILENT_REPLY_TOKEN } from "../tokens.js";
3
+ import { createBlockReplyPayloadKey } from "./block-reply-pipeline.js";
4
+ import { parseReplyDirectives } from "./reply-directives.js";
5
+ import { applyReplyTagsToPayload, isRenderablePayload } from "./reply-payloads.js";
6
+ export function normalizeReplyPayloadDirectives(params) {
7
+ const parseMode = params.parseMode ?? "always";
8
+ const silentToken = params.silentToken ?? SILENT_REPLY_TOKEN;
9
+ const sourceText = params.payload.text ?? "";
10
+ const shouldParse = parseMode === "always" ||
11
+ (parseMode === "auto" &&
12
+ (sourceText.includes("[[") ||
13
+ sourceText.includes("MEDIA:") ||
14
+ sourceText.includes(silentToken)));
15
+ const parsed = shouldParse
16
+ ? parseReplyDirectives(sourceText, {
17
+ currentMessageId: params.currentMessageId,
18
+ silentToken,
19
+ })
20
+ : undefined;
21
+ let text = parsed ? parsed.text || undefined : params.payload.text || undefined;
22
+ if (params.trimLeadingWhitespace && text) {
23
+ text = text.trimStart() || undefined;
24
+ }
25
+ const mediaUrls = params.payload.mediaUrls ?? parsed?.mediaUrls;
26
+ const mediaUrl = params.payload.mediaUrl ?? parsed?.mediaUrl ?? mediaUrls?.[0];
27
+ return {
28
+ payload: {
29
+ ...params.payload,
30
+ text,
31
+ mediaUrls,
32
+ mediaUrl,
33
+ replyToId: params.payload.replyToId ?? parsed?.replyToId,
34
+ replyToTag: params.payload.replyToTag || parsed?.replyToTag,
35
+ replyToCurrent: params.payload.replyToCurrent || parsed?.replyToCurrent,
36
+ audioAsVoice: Boolean(params.payload.audioAsVoice || parsed?.audioAsVoice),
37
+ },
38
+ isSilent: parsed?.isSilent ?? false,
39
+ };
40
+ }
41
+ const hasRenderableMedia = (payload) => Boolean(payload.mediaUrl) || (payload.mediaUrls?.length ?? 0) > 0;
42
+ export function createBlockReplyDeliveryHandler(params) {
43
+ return async (payload) => {
44
+ const { text, skip } = params.normalizeStreamingText(payload);
45
+ if (skip && !hasRenderableMedia(payload)) {
46
+ return;
47
+ }
48
+ const taggedPayload = applyReplyTagsToPayload({
49
+ ...payload,
50
+ text,
51
+ mediaUrl: payload.mediaUrl ?? payload.mediaUrls?.[0],
52
+ replyToId: payload.replyToId ??
53
+ (payload.replyToCurrent === false ? undefined : params.currentMessageId),
54
+ }, params.currentMessageId);
55
+ // Let through payloads with audioAsVoice flag even if empty (need to track it).
56
+ if (!isRenderablePayload(taggedPayload) && !payload.audioAsVoice) {
57
+ return;
58
+ }
59
+ const normalized = normalizeReplyPayloadDirectives({
60
+ payload: taggedPayload,
61
+ currentMessageId: params.currentMessageId,
62
+ silentToken: SILENT_REPLY_TOKEN,
63
+ trimLeadingWhitespace: true,
64
+ parseMode: "auto",
65
+ });
66
+ const blockPayload = params.applyReplyToMode(normalized.payload);
67
+ const blockHasMedia = hasRenderableMedia(blockPayload);
68
+ // Skip empty payloads unless they have audioAsVoice flag (need to track it).
69
+ if (!blockPayload.text && !blockHasMedia && !blockPayload.audioAsVoice) {
70
+ return;
71
+ }
72
+ if (normalized.isSilent && !blockHasMedia) {
73
+ return;
74
+ }
75
+ if (blockPayload.text) {
76
+ void params.typingSignals.signalTextDelta(blockPayload.text).catch((err) => {
77
+ logVerbose(`block reply typing signal failed: ${String(err)}`);
78
+ });
79
+ }
80
+ // Use pipeline if available (block streaming enabled), otherwise send directly.
81
+ if (params.blockStreamingEnabled && params.blockReplyPipeline) {
82
+ params.blockReplyPipeline.enqueue(blockPayload);
83
+ }
84
+ else if (params.blockStreamingEnabled) {
85
+ // Send directly when flushing before tool execution (no pipeline but streaming enabled).
86
+ // Track sent key to avoid duplicate in final payloads.
87
+ params.directlySentBlockKeys.add(createBlockReplyPayloadKey(blockPayload));
88
+ await params.onBlockReply(blockPayload);
89
+ }
90
+ // When streaming is disabled entirely, blocks are accumulated in final text instead.
91
+ };
92
+ }
@@ -0,0 +1,30 @@
1
+ import { splitMediaFromOutput } from "../../media/parse.js";
2
+ import { parseInlineDirectives } from "../../utils/directive-tags.js";
3
+ import { isSilentReplyText, SILENT_REPLY_TOKEN } from "../tokens.js";
4
+ export function parseReplyDirectives(raw, options = {}) {
5
+ const split = splitMediaFromOutput(raw);
6
+ let text = split.text ?? "";
7
+ const replyParsed = parseInlineDirectives(text, {
8
+ currentMessageId: options.currentMessageId,
9
+ stripAudioTag: false,
10
+ stripReplyTags: true,
11
+ });
12
+ if (replyParsed.hasReplyTag) {
13
+ text = replyParsed.text;
14
+ }
15
+ const silentToken = options.silentToken ?? SILENT_REPLY_TOKEN;
16
+ const isSilent = isSilentReplyText(text, silentToken);
17
+ if (isSilent) {
18
+ text = "";
19
+ }
20
+ return {
21
+ text,
22
+ mediaUrls: split.mediaUrls,
23
+ mediaUrl: split.mediaUrl,
24
+ replyToId: replyParsed.replyToId,
25
+ replyToCurrent: replyParsed.replyToCurrent,
26
+ replyToTag: replyParsed.hasReplyTag,
27
+ audioAsVoice: split.audioAsVoice,
28
+ isSilent,
29
+ };
30
+ }