@poolzin/pool-bot 2026.3.25 → 2026.3.27

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 +32 -257
  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,152 @@
1
+ import { sleep } from "../../utils.js";
2
+ import { registerDispatcher } from "./dispatcher-registry.js";
3
+ import { normalizeReplyPayload } from "./normalize-reply.js";
4
+ const DEFAULT_HUMAN_DELAY_MIN_MS = 800;
5
+ const DEFAULT_HUMAN_DELAY_MAX_MS = 2500;
6
+ /** Generate a random delay within the configured range. */
7
+ function getHumanDelay(config) {
8
+ const mode = config?.mode ?? "off";
9
+ if (mode === "off") {
10
+ return 0;
11
+ }
12
+ const min = mode === "custom" ? (config?.minMs ?? DEFAULT_HUMAN_DELAY_MIN_MS) : DEFAULT_HUMAN_DELAY_MIN_MS;
13
+ const max = mode === "custom" ? (config?.maxMs ?? DEFAULT_HUMAN_DELAY_MAX_MS) : DEFAULT_HUMAN_DELAY_MAX_MS;
14
+ if (max <= min) {
15
+ return min;
16
+ }
17
+ return Math.floor(Math.random() * (max - min + 1)) + min;
18
+ }
19
+ function normalizeReplyPayloadInternal(payload, opts) {
20
+ // Prefer dynamic context provider over static context
21
+ const prefixContext = opts.responsePrefixContextProvider?.() ?? opts.responsePrefixContext;
22
+ return normalizeReplyPayload(payload, {
23
+ responsePrefix: opts.responsePrefix,
24
+ responsePrefixContext: prefixContext,
25
+ onHeartbeatStrip: opts.onHeartbeatStrip,
26
+ onSkip: opts.onSkip,
27
+ });
28
+ }
29
+ export function createReplyDispatcher(options) {
30
+ let sendChain = Promise.resolve();
31
+ // Track in-flight deliveries so we can emit a reliable "idle" signal.
32
+ // Start with pending=1 as a "reservation" to prevent premature gateway restart.
33
+ // This is decremented when markComplete() is called to signal no more replies will come.
34
+ let pending = 1;
35
+ let completeCalled = false;
36
+ // Track whether we've sent a block reply (for human delay - skip delay on first block).
37
+ let sentFirstBlock = false;
38
+ // Serialize outbound replies to preserve tool/block/final order.
39
+ const queuedCounts = {
40
+ tool: 0,
41
+ block: 0,
42
+ final: 0,
43
+ };
44
+ // Register this dispatcher globally for gateway restart coordination.
45
+ const { unregister } = registerDispatcher({
46
+ pending: () => pending,
47
+ waitForIdle: () => sendChain,
48
+ });
49
+ const enqueue = (kind, payload) => {
50
+ const normalized = normalizeReplyPayloadInternal(payload, {
51
+ responsePrefix: options.responsePrefix,
52
+ responsePrefixContext: options.responsePrefixContext,
53
+ responsePrefixContextProvider: options.responsePrefixContextProvider,
54
+ onHeartbeatStrip: options.onHeartbeatStrip,
55
+ onSkip: (reason) => options.onSkip?.(payload, { kind, reason }),
56
+ });
57
+ if (!normalized) {
58
+ return false;
59
+ }
60
+ queuedCounts[kind] += 1;
61
+ pending += 1;
62
+ // Determine if we should add human-like delay (only for block replies after the first).
63
+ const shouldDelay = kind === "block" && sentFirstBlock;
64
+ if (kind === "block") {
65
+ sentFirstBlock = true;
66
+ }
67
+ sendChain = sendChain
68
+ .then(async () => {
69
+ // Add human-like delay between block replies for natural rhythm.
70
+ if (shouldDelay) {
71
+ const delayMs = getHumanDelay(options.humanDelay);
72
+ if (delayMs > 0) {
73
+ await sleep(delayMs);
74
+ }
75
+ }
76
+ // Safe: deliver is called inside an async .then() callback, so even a synchronous
77
+ // throw becomes a rejection that flows through .catch()/.finally(), ensuring cleanup.
78
+ await options.deliver(normalized, { kind });
79
+ })
80
+ .catch((err) => {
81
+ options.onError?.(err, { kind });
82
+ })
83
+ .finally(() => {
84
+ pending -= 1;
85
+ // Clear reservation if:
86
+ // 1. pending is now 1 (just the reservation left)
87
+ // 2. markComplete has been called
88
+ // 3. No more replies will be enqueued
89
+ if (pending === 1 && completeCalled) {
90
+ pending -= 1; // Clear the reservation
91
+ }
92
+ if (pending === 0) {
93
+ // Unregister from global tracking when idle.
94
+ unregister();
95
+ options.onIdle?.();
96
+ }
97
+ });
98
+ return true;
99
+ };
100
+ const markComplete = () => {
101
+ if (completeCalled) {
102
+ return;
103
+ }
104
+ completeCalled = true;
105
+ // If no replies were enqueued (pending is still 1 = just the reservation),
106
+ // schedule clearing the reservation after current microtasks complete.
107
+ // This gives any in-flight enqueue() calls a chance to increment pending.
108
+ void Promise.resolve().then(() => {
109
+ if (pending === 1 && completeCalled) {
110
+ // Still just the reservation, no replies were enqueued
111
+ pending -= 1;
112
+ if (pending === 0) {
113
+ unregister();
114
+ options.onIdle?.();
115
+ }
116
+ }
117
+ });
118
+ };
119
+ return {
120
+ sendToolResult: (payload) => enqueue("tool", payload),
121
+ sendBlockReply: (payload) => enqueue("block", payload),
122
+ sendFinalReply: (payload) => enqueue("final", payload),
123
+ waitForIdle: () => sendChain,
124
+ getQueuedCounts: () => ({ ...queuedCounts }),
125
+ markComplete,
126
+ };
127
+ }
128
+ export function createReplyDispatcherWithTyping(options) {
129
+ const { onReplyStart, onIdle, onCleanup, ...dispatcherOptions } = options;
130
+ let typingController;
131
+ const dispatcher = createReplyDispatcher({
132
+ ...dispatcherOptions,
133
+ onIdle: () => {
134
+ typingController?.markDispatchIdle();
135
+ onIdle?.();
136
+ },
137
+ });
138
+ return {
139
+ dispatcher,
140
+ replyOptions: {
141
+ onReplyStart,
142
+ onTypingCleanup: onCleanup,
143
+ onTypingController: (typing) => {
144
+ typingController = typing;
145
+ },
146
+ },
147
+ markDispatchIdle: () => {
148
+ typingController?.markDispatchIdle();
149
+ onIdle?.();
150
+ },
151
+ };
152
+ }
@@ -0,0 +1,166 @@
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 { CHAT_CHANNEL_ORDER } from "../../channels/registry.js";
5
+ import { INTERNAL_MESSAGE_CHANNEL } from "../../utils/message-channel.js";
6
+ import { formatCliCommand } from "../../cli/command-format.js";
7
+ function normalizeAllowToken(value) {
8
+ if (!value)
9
+ return "";
10
+ return value.trim().toLowerCase();
11
+ }
12
+ function slugAllowToken(value) {
13
+ if (!value)
14
+ return "";
15
+ let text = value.trim().toLowerCase();
16
+ if (!text)
17
+ return "";
18
+ text = text.replace(/^[@#]+/, "");
19
+ text = text.replace(/[\s_]+/g, "-");
20
+ text = text.replace(/[^a-z0-9-]+/g, "-");
21
+ return text.replace(/-{2,}/g, "-").replace(/^-+|-+$/g, "");
22
+ }
23
+ const SENDER_PREFIXES = [
24
+ ...CHAT_CHANNEL_ORDER,
25
+ INTERNAL_MESSAGE_CHANNEL,
26
+ "user",
27
+ "group",
28
+ "channel",
29
+ ];
30
+ const SENDER_PREFIX_RE = new RegExp(`^(${SENDER_PREFIXES.join("|")}):`, "i");
31
+ function stripSenderPrefix(value) {
32
+ if (!value)
33
+ return "";
34
+ const trimmed = value.trim();
35
+ return trimmed.replace(SENDER_PREFIX_RE, "");
36
+ }
37
+ function resolveElevatedAllowList(allowFrom, provider, fallbackAllowFrom) {
38
+ if (!allowFrom)
39
+ return fallbackAllowFrom;
40
+ const value = allowFrom[provider];
41
+ return Array.isArray(value) ? value : fallbackAllowFrom;
42
+ }
43
+ function isApprovedElevatedSender(params) {
44
+ const rawAllow = resolveElevatedAllowList(params.allowFrom, params.provider, params.fallbackAllowFrom);
45
+ if (!rawAllow || rawAllow.length === 0)
46
+ return false;
47
+ const allowTokens = rawAllow.map((entry) => String(entry).trim()).filter(Boolean);
48
+ if (allowTokens.length === 0)
49
+ return false;
50
+ if (allowTokens.some((entry) => entry === "*"))
51
+ return true;
52
+ const tokens = new Set();
53
+ const addToken = (value) => {
54
+ if (!value)
55
+ return;
56
+ const trimmed = value.trim();
57
+ if (!trimmed)
58
+ return;
59
+ tokens.add(trimmed);
60
+ const normalized = normalizeAllowToken(trimmed);
61
+ if (normalized)
62
+ tokens.add(normalized);
63
+ const slugged = slugAllowToken(trimmed);
64
+ if (slugged)
65
+ tokens.add(slugged);
66
+ };
67
+ addToken(params.ctx.SenderName);
68
+ addToken(params.ctx.SenderUsername);
69
+ addToken(params.ctx.SenderTag);
70
+ addToken(params.ctx.SenderE164);
71
+ addToken(params.ctx.From);
72
+ addToken(stripSenderPrefix(params.ctx.From));
73
+ addToken(params.ctx.To);
74
+ addToken(stripSenderPrefix(params.ctx.To));
75
+ for (const rawEntry of allowTokens) {
76
+ const entry = rawEntry.trim();
77
+ if (!entry)
78
+ continue;
79
+ const stripped = stripSenderPrefix(entry);
80
+ if (tokens.has(entry) || tokens.has(stripped))
81
+ return true;
82
+ const normalized = normalizeAllowToken(stripped);
83
+ if (normalized && tokens.has(normalized))
84
+ return true;
85
+ const slugged = slugAllowToken(stripped);
86
+ if (slugged && tokens.has(slugged))
87
+ return true;
88
+ }
89
+ return false;
90
+ }
91
+ export function resolveElevatedPermissions(params) {
92
+ const globalConfig = params.cfg.tools?.elevated;
93
+ const agentConfig = resolveAgentConfig(params.cfg, params.agentId)?.tools?.elevated;
94
+ const globalEnabled = globalConfig?.enabled !== false;
95
+ const agentEnabled = agentConfig?.enabled !== false;
96
+ const enabled = globalEnabled && agentEnabled;
97
+ const failures = [];
98
+ if (!globalEnabled)
99
+ failures.push({ gate: "enabled", key: "tools.elevated.enabled" });
100
+ if (!agentEnabled)
101
+ failures.push({
102
+ gate: "enabled",
103
+ key: "agents.list[].tools.elevated.enabled",
104
+ });
105
+ if (!enabled)
106
+ return { enabled, allowed: false, failures };
107
+ if (!params.provider) {
108
+ failures.push({ gate: "provider", key: "ctx.Provider" });
109
+ return { enabled, allowed: false, failures };
110
+ }
111
+ const normalizedProvider = normalizeChannelId(params.provider);
112
+ const dockFallbackAllowFrom = normalizedProvider
113
+ ? getChannelDock(normalizedProvider)?.elevated?.allowFromFallback?.({
114
+ cfg: params.cfg,
115
+ accountId: params.ctx.AccountId,
116
+ })
117
+ : undefined;
118
+ const fallbackAllowFrom = dockFallbackAllowFrom;
119
+ const globalAllowed = isApprovedElevatedSender({
120
+ provider: params.provider,
121
+ ctx: params.ctx,
122
+ allowFrom: globalConfig?.allowFrom,
123
+ fallbackAllowFrom,
124
+ });
125
+ if (!globalAllowed) {
126
+ failures.push({
127
+ gate: "allowFrom",
128
+ key: `tools.elevated.allowFrom.${params.provider}`,
129
+ });
130
+ return { enabled, allowed: false, failures };
131
+ }
132
+ const agentAllowed = agentConfig?.allowFrom
133
+ ? isApprovedElevatedSender({
134
+ provider: params.provider,
135
+ ctx: params.ctx,
136
+ allowFrom: agentConfig.allowFrom,
137
+ fallbackAllowFrom,
138
+ })
139
+ : true;
140
+ if (!agentAllowed) {
141
+ failures.push({
142
+ gate: "allowFrom",
143
+ key: `agents.list[].tools.elevated.allowFrom.${params.provider}`,
144
+ });
145
+ }
146
+ return { enabled, allowed: globalAllowed && agentAllowed, failures };
147
+ }
148
+ export function formatElevatedUnavailableMessage(params) {
149
+ const lines = [];
150
+ lines.push(`elevated is not available right now (runtime=${params.runtimeSandboxed ? "sandboxed" : "direct"}).`);
151
+ if (params.failures.length > 0) {
152
+ lines.push(`Failing gates: ${params.failures.map((f) => `${f.gate} (${f.key})`).join(", ")}`);
153
+ }
154
+ else {
155
+ lines.push("Failing gates: enabled (tools.elevated.enabled / agents.list[].tools.elevated.enabled), allowFrom (tools.elevated.allowFrom.<provider>).");
156
+ }
157
+ lines.push("Fix-it keys:");
158
+ lines.push("- tools.elevated.enabled");
159
+ lines.push("- tools.elevated.allowFrom.<provider>");
160
+ lines.push("- agents.list[].tools.elevated.enabled");
161
+ lines.push("- agents.list[].tools.elevated.allowFrom.<provider>");
162
+ if (params.sessionKey) {
163
+ lines.push(`See: ${formatCliCommand(`poolbot sandbox explain --session ${params.sessionKey}`)}`);
164
+ }
165
+ return lines.join("\n");
166
+ }
@@ -0,0 +1,28 @@
1
+ const INLINE_SIMPLE_COMMAND_ALIASES = new Map([
2
+ ["/help", "/help"],
3
+ ["/commands", "/commands"],
4
+ ["/whoami", "/whoami"],
5
+ ["/id", "/whoami"],
6
+ ]);
7
+ const INLINE_SIMPLE_COMMAND_RE = /(?:^|\s)\/(help|commands|whoami|id)(?=$|\s|:)/i;
8
+ const INLINE_STATUS_RE = /(?:^|\s)\/status(?=$|\s|:)(?:\s*:\s*)?/gi;
9
+ export function extractInlineSimpleCommand(body) {
10
+ if (!body)
11
+ return null;
12
+ const match = body.match(INLINE_SIMPLE_COMMAND_RE);
13
+ if (!match || match.index === undefined)
14
+ return null;
15
+ const alias = `/${match[1].toLowerCase()}`;
16
+ const command = INLINE_SIMPLE_COMMAND_ALIASES.get(alias);
17
+ if (!command)
18
+ return null;
19
+ const cleaned = body.replace(match[0], " ").replace(/\s+/g, " ").trim();
20
+ return { command, cleaned };
21
+ }
22
+ export function stripInlineStatus(body) {
23
+ const trimmed = body.trim();
24
+ if (!trimmed)
25
+ return { cleaned: "", didStrip: false };
26
+ const cleaned = trimmed.replace(INLINE_STATUS_RE, " ").replace(/\s+/g, " ").trim();
27
+ return { cleaned, didStrip: cleaned !== trimmed };
28
+ }
@@ -0,0 +1,114 @@
1
+ import { isMessagingToolDuplicate } from "../../agents/pi-embedded-helpers.js";
2
+ import { normalizeTargetForProvider } from "../../infra/outbound/target-normalization.js";
3
+ import { normalizeOptionalAccountId } from "../../routing/account-id.js";
4
+ import { extractReplyToTag } from "./reply-tags.js";
5
+ import { createReplyToModeFilterForChannel } from "./reply-threading.js";
6
+ function resolveReplyThreadingForPayload(params) {
7
+ const implicitReplyToId = params.implicitReplyToId?.trim() || undefined;
8
+ const currentMessageId = params.currentMessageId?.trim() || undefined;
9
+ // 1) Apply implicit reply threading first (replyToMode will strip later if needed).
10
+ let resolved = params.payload.replyToId || params.payload.replyToCurrent === false || !implicitReplyToId
11
+ ? params.payload
12
+ : { ...params.payload, replyToId: implicitReplyToId };
13
+ // 2) Parse explicit reply tags from text (if present) and clean them.
14
+ if (typeof resolved.text === "string" && resolved.text.includes("[[")) {
15
+ const { cleaned, replyToId, replyToCurrent, hasTag } = extractReplyToTag(resolved.text, currentMessageId);
16
+ resolved = {
17
+ ...resolved,
18
+ text: cleaned ? cleaned : undefined,
19
+ replyToId: replyToId ?? resolved.replyToId,
20
+ replyToTag: hasTag || resolved.replyToTag,
21
+ replyToCurrent: replyToCurrent || resolved.replyToCurrent,
22
+ };
23
+ }
24
+ // 3) If replyToCurrent was set out-of-band (e.g. tags already stripped upstream),
25
+ // ensure replyToId is set to the current message id when available.
26
+ if (resolved.replyToCurrent && !resolved.replyToId && currentMessageId) {
27
+ resolved = {
28
+ ...resolved,
29
+ replyToId: currentMessageId,
30
+ };
31
+ }
32
+ return resolved;
33
+ }
34
+ // Backward-compatible helper: apply explicit reply tags/directives to a single payload.
35
+ // This intentionally does not apply implicit threading.
36
+ export function applyReplyTagsToPayload(payload, currentMessageId) {
37
+ return resolveReplyThreadingForPayload({ payload, currentMessageId });
38
+ }
39
+ export function isRenderablePayload(payload) {
40
+ return Boolean(payload.text ||
41
+ payload.mediaUrl ||
42
+ (payload.mediaUrls && payload.mediaUrls.length > 0) ||
43
+ payload.audioAsVoice ||
44
+ payload.channelData);
45
+ }
46
+ export function applyReplyThreading(params) {
47
+ const { payloads, replyToMode, replyToChannel, currentMessageId } = params;
48
+ const applyReplyToMode = createReplyToModeFilterForChannel(replyToMode, replyToChannel);
49
+ const implicitReplyToId = currentMessageId?.trim() || undefined;
50
+ return payloads
51
+ .map((payload) => resolveReplyThreadingForPayload({ payload, implicitReplyToId, currentMessageId }))
52
+ .filter(isRenderablePayload)
53
+ .map(applyReplyToMode);
54
+ }
55
+ export function filterMessagingToolDuplicates(params) {
56
+ const { payloads, sentTexts } = params;
57
+ if (sentTexts.length === 0) {
58
+ return payloads;
59
+ }
60
+ return payloads.filter((payload) => !isMessagingToolDuplicate(payload.text ?? "", sentTexts));
61
+ }
62
+ export function filterMessagingToolMediaDuplicates(params) {
63
+ const { payloads, sentMediaUrls } = params;
64
+ if (sentMediaUrls.length === 0) {
65
+ return payloads;
66
+ }
67
+ const sentSet = new Set(sentMediaUrls);
68
+ return payloads.map((payload) => {
69
+ const mediaUrl = payload.mediaUrl;
70
+ const mediaUrls = payload.mediaUrls;
71
+ const stripSingle = mediaUrl && sentSet.has(mediaUrl);
72
+ const filteredUrls = mediaUrls?.filter((u) => !sentSet.has(u));
73
+ if (!stripSingle && (!mediaUrls || filteredUrls?.length === mediaUrls.length)) {
74
+ return payload; // No change
75
+ }
76
+ return {
77
+ ...payload,
78
+ mediaUrl: stripSingle ? undefined : mediaUrl,
79
+ mediaUrls: filteredUrls?.length ? filteredUrls : undefined,
80
+ };
81
+ });
82
+ }
83
+ export function shouldSuppressMessagingToolReplies(params) {
84
+ const provider = params.messageProvider?.trim().toLowerCase();
85
+ if (!provider) {
86
+ return false;
87
+ }
88
+ const originTarget = normalizeTargetForProvider(provider, params.originatingTo);
89
+ if (!originTarget) {
90
+ return false;
91
+ }
92
+ const originAccount = normalizeOptionalAccountId(params.accountId);
93
+ const sentTargets = params.messagingToolSentTargets ?? [];
94
+ if (sentTargets.length === 0) {
95
+ return false;
96
+ }
97
+ return sentTargets.some((target) => {
98
+ if (!target?.provider) {
99
+ return false;
100
+ }
101
+ if (target.provider.trim().toLowerCase() !== provider) {
102
+ return false;
103
+ }
104
+ const targetKey = normalizeTargetForProvider(provider, target.to);
105
+ if (!targetKey) {
106
+ return false;
107
+ }
108
+ const targetAccount = normalizeOptionalAccountId(target.accountId);
109
+ if (originAccount && targetAccount && originAccount !== targetAccount) {
110
+ return false;
111
+ }
112
+ return targetKey === originTarget;
113
+ });
114
+ }
@@ -0,0 +1,36 @@
1
+ export function createReplyReferencePlanner(options) {
2
+ let hasReplied = options.hasReplied ?? false;
3
+ const allowReference = options.allowReference !== false;
4
+ const existingId = options.existingId?.trim();
5
+ const startId = options.startId?.trim();
6
+ const use = () => {
7
+ if (!allowReference) {
8
+ return undefined;
9
+ }
10
+ if (options.replyToMode === "off") {
11
+ return undefined;
12
+ }
13
+ const id = existingId ?? startId;
14
+ if (!id) {
15
+ return undefined;
16
+ }
17
+ if (options.replyToMode === "all") {
18
+ hasReplied = true;
19
+ return id;
20
+ }
21
+ // "first": only the first reply gets a reference.
22
+ if (!hasReplied) {
23
+ hasReplied = true;
24
+ return id;
25
+ }
26
+ return undefined;
27
+ };
28
+ const markSent = () => {
29
+ hasReplied = true;
30
+ };
31
+ return {
32
+ use,
33
+ markSent,
34
+ hasReplied: () => hasReplied,
35
+ };
36
+ }
@@ -0,0 +1,13 @@
1
+ import { parseInlineDirectives } from "../../utils/directive-tags.js";
2
+ export function extractReplyToTag(text, currentMessageId) {
3
+ const result = parseInlineDirectives(text, {
4
+ currentMessageId,
5
+ stripAudioTag: false,
6
+ });
7
+ return {
8
+ cleaned: result.text,
9
+ replyToId: result.replyToId,
10
+ replyToCurrent: result.replyToCurrent,
11
+ hasTag: result.hasReplyTag,
12
+ };
13
+ }
@@ -0,0 +1,41 @@
1
+ import { getChannelDock } from "../../channels/dock.js";
2
+ import { normalizeChannelId } from "../../channels/plugins/index.js";
3
+ export function resolveReplyToMode(cfg, channel, accountId, chatType) {
4
+ const provider = normalizeChannelId(channel);
5
+ if (!provider)
6
+ return "all";
7
+ const resolved = getChannelDock(provider)?.threading?.resolveReplyToMode?.({
8
+ cfg,
9
+ accountId,
10
+ chatType,
11
+ });
12
+ return resolved ?? "all";
13
+ }
14
+ export function createReplyToModeFilter(mode, opts = {}) {
15
+ let hasThreaded = false;
16
+ return (payload) => {
17
+ if (!payload.replyToId)
18
+ return payload;
19
+ if (mode === "off") {
20
+ if (opts.allowTagsWhenOff && payload.replyToTag)
21
+ return payload;
22
+ return { ...payload, replyToId: undefined };
23
+ }
24
+ if (mode === "all")
25
+ return payload;
26
+ if (hasThreaded) {
27
+ return { ...payload, replyToId: undefined };
28
+ }
29
+ hasThreaded = true;
30
+ return payload;
31
+ };
32
+ }
33
+ export function createReplyToModeFilterForChannel(mode, channel) {
34
+ const provider = normalizeChannelId(channel);
35
+ const allowTagsWhenOff = provider
36
+ ? Boolean(getChannelDock(provider)?.threading?.allowTagsWhenOff)
37
+ : false;
38
+ return createReplyToModeFilter(mode, {
39
+ allowTagsWhenOff,
40
+ });
41
+ }