@poolzin/pool-bot 2026.2.25 → 2026.2.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 (506) hide show
  1. package/dist/acp/event-mapper.js +87 -22
  2. package/dist/acp/meta.js +12 -6
  3. package/dist/agents/agent-paths.js +8 -9
  4. package/dist/agents/agent-scope.js +7 -5
  5. package/dist/agents/auth-profiles/oauth.js +148 -64
  6. package/dist/agents/auth-profiles/session-override.js +13 -7
  7. package/dist/agents/bash-tools.exec-host-gateway.js +14 -4
  8. package/dist/agents/bash-tools.exec-runtime.js +2 -25
  9. package/dist/agents/bedrock-discovery.js +3 -1
  10. package/dist/agents/byteplus-models.js +97 -0
  11. package/dist/agents/chutes-oauth.js +1 -0
  12. package/dist/agents/cli-runner/helpers.js +4 -0
  13. package/dist/agents/compaction.js +41 -14
  14. package/dist/agents/doubao-models.js +121 -0
  15. package/dist/agents/failover-error.js +2 -0
  16. package/dist/agents/huggingface-models.js +5 -3
  17. package/dist/agents/live-model-filter.js +5 -0
  18. package/dist/agents/minimax-vlm.js +10 -8
  19. package/dist/agents/model-auth.js +6 -0
  20. package/dist/agents/model-catalog.js +3 -1
  21. package/dist/agents/model-selection.js +7 -1
  22. package/dist/agents/models-config.providers.js +93 -11
  23. package/dist/agents/ollama-stream.js +117 -4
  24. package/dist/agents/opencode-zen-models.js +22 -11
  25. package/dist/agents/pi-embedded-helpers/errors.js +55 -33
  26. package/dist/agents/pi-embedded-helpers/messaging-dedupe.js +10 -5
  27. package/dist/agents/pi-embedded-helpers/thinking.js +10 -5
  28. package/dist/agents/pi-embedded-helpers.js +1 -1
  29. package/dist/agents/pi-embedded-runner/compact.js +29 -7
  30. package/dist/agents/pi-embedded-runner/extensions.js +28 -26
  31. package/dist/agents/pi-embedded-runner/google.js +20 -8
  32. package/dist/agents/pi-embedded-runner/run/attempt.js +95 -36
  33. package/dist/agents/pi-embedded-runner/run.js +71 -12
  34. package/dist/agents/pi-embedded-runner/run.overflow-compaction.mocks.shared.js +11 -2
  35. package/dist/agents/pi-embedded-runner/session-manager-cache.js +11 -7
  36. package/dist/agents/pi-embedded-runner/system-prompt.js +2 -0
  37. package/dist/agents/pi-embedded-runner/thinking.js +42 -0
  38. package/dist/agents/pi-embedded-runner/tool-name-allowlist.js +19 -0
  39. package/dist/agents/pi-embedded-runner/utils.js +7 -10
  40. package/dist/agents/pi-embedded-subscribe.handlers.lifecycle.js +45 -56
  41. package/dist/agents/pi-embedded-subscribe.handlers.tools.js +2 -2
  42. package/dist/agents/pi-embedded-subscribe.js +9 -4
  43. package/dist/agents/pi-embedded-subscribe.tools.js +68 -14
  44. package/dist/agents/pi-embedded-utils.js +3 -0
  45. package/dist/agents/pi-extensions/compaction-safeguard-runtime.js +4 -20
  46. package/dist/agents/pi-extensions/compaction-safeguard.js +75 -33
  47. package/dist/agents/pi-settings.js +40 -0
  48. package/dist/agents/pi-tools.policy.js +2 -1
  49. package/dist/agents/provider/config-loader.js +1 -1
  50. package/dist/agents/sandbox/browser.js +170 -33
  51. package/dist/agents/sandbox/config-hash.js +14 -27
  52. package/dist/agents/sandbox/config.js +21 -2
  53. package/dist/agents/sandbox/constants.js +2 -0
  54. package/dist/agents/sandbox/docker.js +16 -2
  55. package/dist/agents/sandbox/novnc-auth.js +62 -0
  56. package/dist/agents/sandbox/sanitize-env-vars.js +1 -1
  57. package/dist/agents/sandbox/shared.js +10 -6
  58. package/dist/agents/sandbox-paths.js +24 -11
  59. package/dist/agents/schema/clean-for-gemini.js +132 -85
  60. package/dist/agents/session-slug.js +10 -5
  61. package/dist/agents/session-tool-result-guard-wrapper.js +1 -0
  62. package/dist/agents/session-tool-result-guard.js +3 -1
  63. package/dist/agents/session-transcript-repair.js +40 -6
  64. package/dist/agents/skills/bundled-dir.js +19 -5
  65. package/dist/agents/skills/env-overrides.js +124 -43
  66. package/dist/agents/skills/frontmatter.js +6 -6
  67. package/dist/agents/skills/plugin-skills.js +14 -7
  68. package/dist/agents/skills/workspace.js +1 -0
  69. package/dist/agents/subagent-announce.js +251 -49
  70. package/dist/agents/subagent-lifecycle-events.js +19 -0
  71. package/dist/agents/subagent-registry-cleanup.js +31 -0
  72. package/dist/agents/subagent-registry-completion.js +68 -0
  73. package/dist/agents/subagent-registry-queries.js +117 -0
  74. package/dist/agents/subagent-registry-state.js +46 -0
  75. package/dist/agents/subagent-registry.js +252 -221
  76. package/dist/agents/subagent-registry.store.js +1 -0
  77. package/dist/agents/subagent-registry.types.js +1 -0
  78. package/dist/agents/subagent-spawn.js +195 -7
  79. package/dist/agents/system-prompt.js +22 -6
  80. package/dist/agents/test-helpers/fast-coding-tools.js +1 -18
  81. package/dist/agents/test-helpers/fast-core-tools.js +1 -17
  82. package/dist/agents/timeout.js +18 -6
  83. package/dist/agents/tool-call-id.js +1 -1
  84. package/dist/agents/tool-display-common.js +162 -29
  85. package/dist/agents/tool-images.js +82 -9
  86. package/dist/agents/tool-policy.js +51 -26
  87. package/dist/agents/tools/browser-tool.js +2 -2
  88. package/dist/agents/tools/canvas-tool.js +27 -1
  89. package/dist/agents/tools/common.js +45 -0
  90. package/dist/agents/tools/discord-actions-guild.js +4 -1
  91. package/dist/agents/tools/gateway-tool.js +3 -1
  92. package/dist/agents/tools/nodes-utils.js +1 -10
  93. package/dist/agents/tools/sessions-send-helpers.js +12 -6
  94. package/dist/agents/tools/sessions-spawn-tool.js +8 -2
  95. package/dist/agents/tools/subagents-tool.js +2 -1
  96. package/dist/agents/tools/whatsapp-actions.js +10 -2
  97. package/dist/agents/tools/whatsapp-target-auth.js +18 -0
  98. package/dist/agents/transcript-policy.js +22 -8
  99. package/dist/agents/venice-models.js +11 -3
  100. package/dist/auto-reply/commands-registry.data.js +51 -0
  101. package/dist/auto-reply/commands-registry.js +4 -3
  102. package/dist/auto-reply/group-activation.js +10 -5
  103. package/dist/auto-reply/inbound-debounce.js +10 -5
  104. package/dist/auto-reply/reply/abort.js +1 -1
  105. package/dist/auto-reply/reply/agent-runner-execution.js +4 -1
  106. package/dist/auto-reply/reply/bash-command.js +41 -39
  107. package/dist/auto-reply/reply/command-gates.js +25 -0
  108. package/dist/auto-reply/reply/commands-allowlist.js +111 -72
  109. package/dist/auto-reply/reply/commands-bash.js +6 -5
  110. package/dist/auto-reply/reply/commands-config.js +30 -28
  111. package/dist/auto-reply/reply/commands-core.js +2 -1
  112. package/dist/auto-reply/reply/commands-info.js +1 -0
  113. package/dist/auto-reply/reply/commands-models.js +65 -14
  114. package/dist/auto-reply/reply/commands-session.js +237 -82
  115. package/dist/auto-reply/reply/commands-setunset.js +45 -0
  116. package/dist/auto-reply/reply/commands-subagents/action-agents.js +44 -0
  117. package/dist/auto-reply/reply/commands-subagents/action-focus.js +64 -0
  118. package/dist/auto-reply/reply/commands-subagents/action-help.js +4 -0
  119. package/dist/auto-reply/reply/commands-subagents/action-info.js +45 -0
  120. package/dist/auto-reply/reply/commands-subagents/action-kill.js +60 -0
  121. package/dist/auto-reply/reply/commands-subagents/action-list.js +44 -0
  122. package/dist/auto-reply/reply/commands-subagents/action-log.js +29 -0
  123. package/dist/auto-reply/reply/commands-subagents/action-send.js +119 -0
  124. package/dist/auto-reply/reply/commands-subagents/action-spawn.js +52 -0
  125. package/dist/auto-reply/reply/commands-subagents/action-unfocus.js +30 -0
  126. package/dist/auto-reply/reply/commands-subagents/shared.js +303 -0
  127. package/dist/auto-reply/reply/commands-subagents.js +51 -587
  128. package/dist/auto-reply/reply/commands-tts.js +10 -5
  129. package/dist/auto-reply/reply/config-value.js +10 -5
  130. package/dist/auto-reply/reply/directive-handling.model-picker.js +12 -6
  131. package/dist/auto-reply/reply/directive-handling.persist.js +9 -21
  132. package/dist/auto-reply/reply/directive-handling.shared.js +24 -4
  133. package/dist/auto-reply/reply/followup-runner.js +1 -0
  134. package/dist/auto-reply/reply/get-reply-directives-utils.js +23 -14
  135. package/dist/auto-reply/reply/get-reply-directives.js +17 -28
  136. package/dist/auto-reply/reply/get-reply-inline-actions.js +1 -0
  137. package/dist/auto-reply/reply/get-reply.js +71 -12
  138. package/dist/auto-reply/reply/model-selection.js +80 -39
  139. package/dist/auto-reply/reply/queue/enqueue.js +10 -5
  140. package/dist/auto-reply/reply/queue/state.js +13 -12
  141. package/dist/auto-reply/reply/reply-payloads.js +67 -36
  142. package/dist/auto-reply/reply/reply-reference.js +9 -8
  143. package/dist/auto-reply/reply/route-reply.js +15 -8
  144. package/dist/auto-reply/reply/session-reset-prompt.js +1 -1
  145. package/dist/auto-reply/reply/session.js +22 -6
  146. package/dist/auto-reply/reply/strip-inbound-meta.js +147 -0
  147. package/dist/auto-reply/reply/subagents-utils.js +56 -30
  148. package/dist/auto-reply/reply/typing.js +46 -21
  149. package/dist/auto-reply/send-policy.js +14 -7
  150. package/dist/auto-reply/status.js +140 -16
  151. package/dist/auto-reply/templating.js +10 -5
  152. package/dist/auto-reply/thinking.js +7 -16
  153. package/dist/auto-reply/tokens.js +21 -5
  154. package/dist/browser/bridge-server.js +36 -20
  155. package/dist/browser/cdp.helpers.js +7 -14
  156. package/dist/browser/cdp.js +35 -15
  157. package/dist/browser/chrome.profile-decoration.js +7 -4
  158. package/dist/browser/config.js +4 -0
  159. package/dist/browser/extension-relay-auth.js +55 -0
  160. package/dist/browser/extension-relay.js +74 -29
  161. package/dist/browser/navigation-guard.js +9 -1
  162. package/dist/browser/paths.js +77 -0
  163. package/dist/browser/profiles.js +13 -8
  164. package/dist/browser/pw-ai-module.js +10 -5
  165. package/dist/browser/pw-session.js +76 -39
  166. package/dist/browser/pw-tools-core.interactions.js +14 -7
  167. package/dist/browser/pw-tools-core.state.js +12 -6
  168. package/dist/browser/routes/agent.act.js +2 -2
  169. package/dist/browser/server-context.js +7 -0
  170. package/dist/build-info.json +3 -3
  171. package/dist/channels/allow-from.js +2 -1
  172. package/dist/channels/allowlists/resolve-utils.js +43 -19
  173. package/dist/channels/channel-config.js +14 -7
  174. package/dist/channels/draft-stream-loop.js +7 -0
  175. package/dist/channels/model-overrides.js +82 -0
  176. package/dist/channels/plugins/normalize/imessage.js +14 -7
  177. package/dist/channels/plugins/normalize/slack.js +10 -5
  178. package/dist/channels/plugins/normalize/telegram.js +14 -7
  179. package/dist/channels/plugins/outbound/discord.js +80 -8
  180. package/dist/channels/plugins/outbound/signal.js +11 -11
  181. package/dist/channels/plugins/setup-helpers.js +10 -5
  182. package/dist/channels/sender-label.js +14 -7
  183. package/dist/channels/session.js +4 -2
  184. package/dist/channels/status-reactions.js +297 -0
  185. package/dist/cli/banner.js +1 -1
  186. package/dist/cli/browser-cli-actions-input/register.files-downloads.js +65 -56
  187. package/dist/cli/cli-name.js +11 -11
  188. package/dist/cli/cli-utils.js +13 -3
  189. package/dist/cli/command-format.js +1 -1
  190. package/dist/cli/config-cli.js +1 -1
  191. package/dist/cli/daemon-cli/lifecycle-core.js +31 -19
  192. package/dist/cli/daemon-cli/lifecycle.js +64 -2
  193. package/dist/cli/daemon-cli/restart-health.js +126 -0
  194. package/dist/cli/daemon-cli/status.gather.js +9 -13
  195. package/dist/cli/daemon-cli/status.print.js +2 -10
  196. package/dist/cli/deps.js +27 -22
  197. package/dist/cli/gateway-cli/run-loop.js +23 -5
  198. package/dist/cli/node-cli/register.js +14 -5
  199. package/dist/cli/nodes-media-utils.js +7 -2
  200. package/dist/cli/outbound-send-deps.js +2 -9
  201. package/dist/cli/outbound-send-mapping.js +11 -0
  202. package/dist/cli/pairing-cli.js +40 -14
  203. package/dist/cli/plugins-cli.js +34 -41
  204. package/dist/cli/ports.js +11 -10
  205. package/dist/cli/program/command-registry.js +2 -11
  206. package/dist/cli/program/command-tree.js +16 -0
  207. package/dist/cli/program/preaction.js +13 -9
  208. package/dist/cli/program/register.configure.js +3 -18
  209. package/dist/cli/program/register.maintenance.js +2 -2
  210. package/dist/cli/program/register.onboard.js +2 -0
  211. package/dist/cli/program/register.status-health-sessions.js +16 -17
  212. package/dist/cli/program/register.subclis.js +93 -52
  213. package/dist/cli/route.js +11 -7
  214. package/dist/cli/system-cli.js +36 -46
  215. package/dist/cli/update-cli/shared.js +22 -9
  216. package/dist/cli/update-cli/update-command.js +89 -14
  217. package/dist/cli/update-cli/wizard.js +6 -12
  218. package/dist/commands/agent/run-context.js +18 -5
  219. package/dist/commands/agent/session-store.js +17 -4
  220. package/dist/commands/agent.js +22 -2
  221. package/dist/commands/agents.bindings.js +14 -7
  222. package/dist/commands/agents.commands.add.js +13 -9
  223. package/dist/commands/agents.commands.identity.js +12 -6
  224. package/dist/commands/agents.commands.list.js +11 -6
  225. package/dist/commands/agents.config.js +8 -10
  226. package/dist/commands/agents.providers.js +12 -6
  227. package/dist/commands/auth-choice-options.js +103 -75
  228. package/dist/commands/auth-choice.apply.byteplus.js +55 -0
  229. package/dist/commands/auth-choice.apply.js +4 -0
  230. package/dist/commands/auth-choice.apply.minimax.js +61 -13
  231. package/dist/commands/auth-choice.apply.openai.js +3 -1
  232. package/dist/commands/auth-choice.apply.volcengine.js +55 -0
  233. package/dist/commands/auth-choice.preferred-provider.js +2 -0
  234. package/dist/commands/channels/remove.js +13 -6
  235. package/dist/commands/channels/shared.js +4 -14
  236. package/dist/commands/configure.commands.js +14 -0
  237. package/dist/commands/configure.gateway.js +2 -4
  238. package/dist/commands/configure.js +1 -1
  239. package/dist/commands/configure.shared.js +11 -0
  240. package/dist/commands/daemon-install-helpers.js +2 -2
  241. package/dist/commands/dashboard.js +12 -10
  242. package/dist/commands/docs.js +14 -8
  243. package/dist/commands/doctor-config-flow.js +11 -9
  244. package/dist/commands/doctor-legacy-config.js +281 -0
  245. package/dist/commands/doctor-state-integrity.js +99 -23
  246. package/dist/commands/doctor-update.js +12 -9
  247. package/dist/commands/models/list.list-command.js +7 -5
  248. package/dist/commands/models/set-image.js +2 -21
  249. package/dist/commands/node-daemon-install-helpers.js +10 -8
  250. package/dist/commands/onboard-auth.config-minimax.js +54 -80
  251. package/dist/commands/onboard-auth.config-opencode.js +2 -18
  252. package/dist/commands/onboard-auth.credentials.js +90 -13
  253. package/dist/commands/onboard-auth.js +1 -1
  254. package/dist/commands/onboard-auth.models.js +6 -5
  255. package/dist/commands/onboard-hooks.js +1 -1
  256. package/dist/commands/onboard-non-interactive/api-keys.js +14 -7
  257. package/dist/commands/onboard-non-interactive/local/auth-choice.js +64 -49
  258. package/dist/commands/onboard-provider-auth-flags.js +14 -0
  259. package/dist/commands/onboard-remote.js +14 -7
  260. package/dist/commands/onboard.js +11 -13
  261. package/dist/commands/sandbox-display.js +6 -5
  262. package/dist/commands/status-all/diagnosis.js +14 -10
  263. package/dist/commands/status-all/format.js +1 -0
  264. package/dist/commands/status.gateway-probe.js +1 -16
  265. package/dist/commands/systemd-linger.js +12 -6
  266. package/dist/config/agent-limits.js +2 -0
  267. package/dist/config/commands.js +30 -16
  268. package/dist/config/config-paths.js +9 -11
  269. package/dist/config/defaults.js +22 -2
  270. package/dist/config/discord-preview-streaming.js +104 -0
  271. package/dist/config/env-vars.js +37 -8
  272. package/dist/config/includes.js +4 -0
  273. package/dist/config/io.js +97 -12
  274. package/dist/config/legacy.migrations.part-1.js +189 -78
  275. package/dist/config/legacy.shared.js +3 -1
  276. package/dist/config/merge-patch.js +4 -0
  277. package/dist/config/prototype-keys.js +4 -0
  278. package/dist/config/schema.help.js +44 -7
  279. package/dist/config/schema.labels.js +38 -6
  280. package/dist/config/sessions/delivery-info.js +10 -3
  281. package/dist/config/sessions/main-session.js +10 -5
  282. package/dist/config/sessions/session-file.js +33 -0
  283. package/dist/config/sessions/session-key.js +10 -5
  284. package/dist/config/sessions/store.js +1 -1
  285. package/dist/config/sessions.js +1 -0
  286. package/dist/config/zod-schema.agent-runtime.js +11 -0
  287. package/dist/config/zod-schema.js +148 -13
  288. package/dist/config/zod-schema.providers-core.js +78 -4
  289. package/dist/config/zod-schema.providers.js +6 -1
  290. package/dist/config/zod-schema.session.js +41 -2
  291. package/dist/cron/run-log.js +3 -0
  292. package/dist/cron/schedule.js +21 -10
  293. package/dist/cron/service/ops.js +35 -21
  294. package/dist/cron/service/timer.js +116 -16
  295. package/dist/cron/stagger.js +3 -1
  296. package/dist/discord/api.js +12 -6
  297. package/dist/discord/draft-chunking.js +22 -0
  298. package/dist/discord/draft-stream.js +124 -0
  299. package/dist/discord/monitor/agent-components.js +1 -1
  300. package/dist/discord/monitor/commands.js +5 -0
  301. package/dist/discord/monitor/gateway-plugin.js +2 -1
  302. package/dist/discord/monitor/listeners.js +37 -27
  303. package/dist/discord/monitor/message-handler.js +4 -1
  304. package/dist/discord/monitor/message-handler.preflight.js +65 -8
  305. package/dist/discord/monitor/message-handler.process.js +246 -217
  306. package/dist/discord/monitor/message-utils.js +143 -6
  307. package/dist/discord/monitor/model-picker-preferences.js +143 -0
  308. package/dist/discord/monitor/model-picker.js +651 -0
  309. package/dist/discord/monitor/native-command.js +573 -16
  310. package/dist/discord/monitor/provider.allowlist.js +223 -0
  311. package/dist/discord/monitor/provider.js +275 -347
  312. package/dist/discord/monitor/provider.lifecycle.js +100 -0
  313. package/dist/discord/monitor/reply-delivery.js +123 -16
  314. package/dist/discord/monitor/thread-bindings.discord-api.js +215 -0
  315. package/dist/discord/monitor/thread-bindings.js +4 -0
  316. package/dist/discord/monitor/thread-bindings.lifecycle.js +177 -0
  317. package/dist/discord/monitor/thread-bindings.manager.js +423 -0
  318. package/dist/discord/monitor/thread-bindings.messages.js +55 -0
  319. package/dist/discord/monitor/thread-bindings.state.js +358 -0
  320. package/dist/discord/monitor/thread-bindings.types.js +6 -0
  321. package/dist/discord/resolve-users.js +33 -21
  322. package/dist/discord/send.channels.js +15 -0
  323. package/dist/discord/send.js +3 -2
  324. package/dist/discord/send.outbound.js +82 -26
  325. package/dist/discord/send.permissions.js +83 -30
  326. package/dist/discord/send.reactions.js +8 -4
  327. package/dist/discord/token.js +10 -5
  328. package/dist/discord/voice/command.js +263 -0
  329. package/dist/discord/voice/manager.js +531 -0
  330. package/dist/gateway/auth.js +34 -10
  331. package/dist/gateway/call.js +4 -16
  332. package/dist/gateway/client.js +28 -4
  333. package/dist/gateway/config-reload.js +3 -4
  334. package/dist/gateway/control-ui.js +219 -96
  335. package/dist/gateway/hooks-mapping.js +88 -38
  336. package/dist/gateway/http-auth-helpers.js +3 -2
  337. package/dist/gateway/http-endpoint-helpers.js +1 -0
  338. package/dist/gateway/net.js +54 -12
  339. package/dist/gateway/node-invoke-system-run-approval.js +14 -35
  340. package/dist/gateway/node-registry.js +10 -5
  341. package/dist/gateway/openai-http.js +1 -0
  342. package/dist/gateway/openresponses-http.js +1 -0
  343. package/dist/gateway/origin-check.js +1 -18
  344. package/dist/gateway/protocol/index.js +4 -3
  345. package/dist/gateway/protocol/schema/cron.js +1 -0
  346. package/dist/gateway/protocol/schema/devices.js +1 -0
  347. package/dist/gateway/protocol/schema/protocol-schemas.js +2 -1
  348. package/dist/gateway/protocol/schema/sessions.js +6 -0
  349. package/dist/gateway/role-policy.js +17 -0
  350. package/dist/gateway/server/ws-connection/connect-policy.js +37 -0
  351. package/dist/gateway/server/ws-connection/message-handler.js +175 -148
  352. package/dist/gateway/server-chat.js +83 -25
  353. package/dist/gateway/server-constants.js +10 -9
  354. package/dist/gateway/server-cron.js +1 -0
  355. package/dist/gateway/server-http.js +16 -7
  356. package/dist/gateway/server-maintenance.js +20 -5
  357. package/dist/gateway/server-methods/chat.js +10 -6
  358. package/dist/gateway/server-methods/config.js +12 -14
  359. package/dist/gateway/server-methods/devices.js +17 -3
  360. package/dist/gateway/server-methods/models.js +11 -1
  361. package/dist/gateway/server-methods/sessions.js +64 -8
  362. package/dist/gateway/server-methods/usage.js +162 -75
  363. package/dist/gateway/server-node-events.js +29 -0
  364. package/dist/gateway/server-runtime-config.js +34 -13
  365. package/dist/gateway/server-startup-memory.js +17 -11
  366. package/dist/gateway/session-utils.fs.js +32 -34
  367. package/dist/gateway/sessions-resolve.js +17 -5
  368. package/dist/gateway/test-helpers.openai-mock.js +14 -7
  369. package/dist/gateway/tools-invoke-http.js +21 -10
  370. package/dist/hooks/bundled/bootstrap-extra-files/handler.js +3 -1
  371. package/dist/hooks/bundled/command-logger/handler.js +7 -2
  372. package/dist/hooks/bundled/session-memory/handler.js +6 -5
  373. package/dist/hooks/frontmatter.js +6 -6
  374. package/dist/hooks/gmail-watcher.js +11 -6
  375. package/dist/hooks/internal-hooks.js +11 -1
  376. package/dist/hooks/llm-slug-generator.js +4 -1
  377. package/dist/hooks/workspace.js +47 -17
  378. package/dist/imessage/accounts.js +9 -20
  379. package/dist/imessage/monitor/inbound-processing.js +2 -1
  380. package/dist/infra/archive.js +174 -73
  381. package/dist/infra/control-ui-assets.js +14 -6
  382. package/dist/infra/device-pairing.js +108 -29
  383. package/dist/infra/env.js +10 -5
  384. package/dist/infra/exec-approvals-allowlist.js +122 -0
  385. package/dist/infra/exec-approvals-analysis.js +34 -3
  386. package/dist/infra/exec-approvals.js +5 -17
  387. package/dist/infra/exec-safe-bin-policy.js +53 -45
  388. package/dist/infra/fs-safe.js +71 -39
  389. package/dist/infra/gateway-lock.js +6 -2
  390. package/dist/infra/heartbeat-wake.js +6 -12
  391. package/dist/infra/host-env-security-policy.json +19 -0
  392. package/dist/infra/host-env-security.js +66 -0
  393. package/dist/infra/net/ssrf.js +131 -38
  394. package/dist/infra/outbound/bound-delivery-router.js +88 -0
  395. package/dist/infra/outbound/channel-selection.js +12 -6
  396. package/dist/infra/outbound/envelope.js +1 -1
  397. package/dist/infra/outbound/format.js +12 -6
  398. package/dist/infra/outbound/payloads.js +14 -7
  399. package/dist/infra/outbound/session-binding-service.js +123 -0
  400. package/dist/infra/path-guards.js +25 -0
  401. package/dist/infra/provider-usage.fetch.codex.js +7 -15
  402. package/dist/infra/provider-usage.fetch.gemini.js +14 -11
  403. package/dist/infra/provider-usage.fetch.shared.js +30 -1
  404. package/dist/infra/provider-usage.fetch.zai.js +10 -9
  405. package/dist/infra/retry-policy.js +4 -2
  406. package/dist/infra/retry.js +9 -5
  407. package/dist/infra/session-cost-usage.js +107 -59
  408. package/dist/infra/session-maintenance-warning.js +3 -1
  409. package/dist/infra/shell-env.js +98 -34
  410. package/dist/infra/ssh-config.js +12 -6
  411. package/dist/infra/system-run-command.js +49 -4
  412. package/dist/infra/update-channels.js +10 -5
  413. package/dist/line/accounts.js +5 -7
  414. package/dist/line/bot-access.js +8 -20
  415. package/dist/line/bot-handlers.js +3 -1
  416. package/dist/link-understanding/detect.js +15 -7
  417. package/dist/media/constants.js +15 -6
  418. package/dist/media/image-ops.js +7 -0
  419. package/dist/media/local-roots.js +3 -2
  420. package/dist/media-understanding/apply.js +4 -1
  421. package/dist/media-understanding/concurrency.js +8 -20
  422. package/dist/memory/backend-config.js +45 -6
  423. package/dist/memory/embeddings.js +10 -4
  424. package/dist/memory/fs-utils.js +23 -0
  425. package/dist/memory/manager-search.js +12 -6
  426. package/dist/memory/manager-sync-ops.js +12 -2
  427. package/dist/memory/qmd-manager.js +466 -53
  428. package/dist/memory/query-expansion.js +167 -3
  429. package/dist/memory/status-format.js +10 -5
  430. package/dist/memory/sync-memory-files.js +1 -1
  431. package/dist/node-host/invoke-system-run.js +281 -0
  432. package/dist/node-host/invoke.js +55 -337
  433. package/dist/pairing/pairing-store.js +22 -0
  434. package/dist/plugin-sdk/allow-from.js +1 -1
  435. package/dist/plugin-sdk/command-auth.js +3 -1
  436. package/dist/plugin-sdk/index.js +6 -3
  437. package/dist/plugin-sdk/webhook-targets.js +32 -0
  438. package/dist/plugins/bundled-dir.js +9 -6
  439. package/dist/plugins/hooks.js +50 -0
  440. package/dist/plugins/install.js +28 -16
  441. package/dist/plugins/runtime.js +3 -17
  442. package/dist/plugins/update.js +78 -12
  443. package/dist/process/spawn-utils.js +14 -7
  444. package/dist/providers/github-copilot-token.js +11 -6
  445. package/dist/providers/qwen-portal-oauth.js +14 -6
  446. package/dist/routing/account-id.js +30 -0
  447. package/dist/routing/resolve-route.js +3 -7
  448. package/dist/routing/session-key.js +2 -16
  449. package/dist/security/audit-channel.js +93 -2
  450. package/dist/security/audit-extra.async.js +159 -5
  451. package/dist/security/audit-extra.js +1 -1
  452. package/dist/security/audit-extra.sync.js +85 -6
  453. package/dist/security/audit.js +40 -4
  454. package/dist/security/dm-policy-shared.js +44 -0
  455. package/dist/security/external-content.js +26 -6
  456. package/dist/shared/entry-status.js +6 -0
  457. package/dist/shared/frontmatter.js +5 -5
  458. package/dist/shared/node-match.js +11 -4
  459. package/dist/shared/operator-scope-compat.js +8 -3
  460. package/dist/signal/accounts.js +7 -20
  461. package/dist/signal/monitor/event-handler.js +3 -1
  462. package/dist/slack/accounts.js +6 -19
  463. package/dist/slack/actions.js +11 -3
  464. package/dist/slack/monitor/auth.js +1 -1
  465. package/dist/slack/monitor/message-handler/dispatch.js +50 -29
  466. package/dist/slack/monitor/replies.js +15 -7
  467. package/dist/slack/monitor/slash.js +22 -13
  468. package/dist/slack/resolve-channels.js +10 -5
  469. package/dist/slack/send.js +102 -12
  470. package/dist/slack/stream-mode.js +10 -0
  471. package/dist/slack/streaming.js +4 -2
  472. package/dist/telegram/accounts.js +19 -14
  473. package/dist/telegram/bot/helpers.js +3 -5
  474. package/dist/telegram/bot-access.js +35 -36
  475. package/dist/telegram/bot-handlers.js +120 -148
  476. package/dist/telegram/bot-message-context.js +68 -9
  477. package/dist/telegram/bot-message-dispatch.js +155 -90
  478. package/dist/telegram/bot-native-commands.js +16 -0
  479. package/dist/telegram/draft-stream.js +14 -1
  480. package/dist/telegram/inline-buttons.js +5 -15
  481. package/dist/telegram/monitor.js +11 -7
  482. package/dist/telegram/network-config.js +19 -7
  483. package/dist/telegram/send.js +3 -2
  484. package/dist/telegram/sent-message-cache.js +5 -6
  485. package/dist/telegram/status-reaction-variants.js +208 -0
  486. package/dist/telegram/sticker-cache.js +11 -9
  487. package/dist/terminal/theme.js +12 -12
  488. package/dist/tts/tts.js +80 -567
  489. package/dist/tui/components/chat-log.js +41 -8
  490. package/dist/tui/theme/theme.js +10 -12
  491. package/dist/tui/tui-local-shell.js +16 -6
  492. package/dist/tui/tui.js +58 -6
  493. package/dist/utils/account-id.js +2 -4
  494. package/dist/utils/boolean.js +10 -5
  495. package/dist/utils/directive-tags.js +11 -0
  496. package/dist/utils/queue-helpers.js +67 -12
  497. package/dist/web/auto-reply/deliver-reply.js +8 -4
  498. package/dist/web/auto-reply/mentions.js +10 -5
  499. package/dist/web/auto-reply/monitor/group-members.js +14 -7
  500. package/dist/web/auto-reply/monitor/process-message.js +45 -24
  501. package/dist/web/inbound/access-control.js +5 -2
  502. package/dist/web/login-qr.js +12 -6
  503. package/dist/web/media.js +123 -16
  504. package/extensions/bluebubbles/src/monitor-processing.ts +580 -139
  505. package/extensions/bluebubbles/src/monitor.ts +208 -1950
  506. package/package.json +1 -1
@@ -1,14 +1,21 @@
1
1
  import type { PoolBotConfig } from "poolbot/plugin-sdk";
2
2
  import {
3
3
  createReplyPrefixOptions,
4
+ evictOldHistoryKeys,
4
5
  logAckFailure,
5
6
  logInboundDrop,
6
7
  logTypingFailure,
8
+ recordPendingHistoryEntryIfEnabled,
7
9
  resolveAckReaction,
10
+ resolveDmGroupAccessDecision,
11
+ resolveEffectiveAllowFromLists,
8
12
  resolveControlCommandGate,
13
+ stripMarkdown,
14
+ type HistoryEntry,
9
15
  } from "poolbot/plugin-sdk";
10
16
  import { downloadBlueBubblesAttachment } from "./attachments.js";
11
17
  import { markBlueBubblesChatRead, sendBlueBubblesTyping } from "./chat.js";
18
+ import { fetchBlueBubblesHistory } from "./history.js";
12
19
  import { sendBlueBubblesMedia } from "./media-send.js";
13
20
  import {
14
21
  buildMessagePlaceholder,
@@ -40,6 +47,135 @@ import { formatBlueBubblesChatTarget, isAllowedBlueBubblesSender } from "./targe
40
47
  const DEFAULT_TEXT_LIMIT = 4000;
41
48
  const invalidAckReactions = new Set<string>();
42
49
  const REPLY_DIRECTIVE_TAG_RE = /\[\[\s*(?:reply_to_current|reply_to\s*:\s*[^\]\n]+)\s*\]\]/gi;
50
+ const PENDING_OUTBOUND_MESSAGE_ID_TTL_MS = 2 * 60 * 1000;
51
+
52
+ type PendingOutboundMessageId = {
53
+ id: number;
54
+ accountId: string;
55
+ sessionKey: string;
56
+ outboundTarget: string;
57
+ chatGuid?: string;
58
+ chatIdentifier?: string;
59
+ chatId?: number;
60
+ snippetRaw: string;
61
+ snippetNorm: string;
62
+ isMediaSnippet: boolean;
63
+ createdAt: number;
64
+ };
65
+
66
+ const pendingOutboundMessageIds: PendingOutboundMessageId[] = [];
67
+ let pendingOutboundMessageIdCounter = 0;
68
+
69
+ function trimOrUndefined(value?: string | null): string | undefined {
70
+ const trimmed = value?.trim();
71
+ return trimmed ? trimmed : undefined;
72
+ }
73
+
74
+ function normalizeSnippet(value: string): string {
75
+ return stripMarkdown(value).replace(/\s+/g, " ").trim().toLowerCase();
76
+ }
77
+
78
+ function prunePendingOutboundMessageIds(now = Date.now()): void {
79
+ const cutoff = now - PENDING_OUTBOUND_MESSAGE_ID_TTL_MS;
80
+ for (let i = pendingOutboundMessageIds.length - 1; i >= 0; i--) {
81
+ if (pendingOutboundMessageIds[i].createdAt < cutoff) {
82
+ pendingOutboundMessageIds.splice(i, 1);
83
+ }
84
+ }
85
+ }
86
+
87
+ function rememberPendingOutboundMessageId(entry: {
88
+ accountId: string;
89
+ sessionKey: string;
90
+ outboundTarget: string;
91
+ chatGuid?: string;
92
+ chatIdentifier?: string;
93
+ chatId?: number;
94
+ snippet: string;
95
+ }): number {
96
+ prunePendingOutboundMessageIds();
97
+ pendingOutboundMessageIdCounter += 1;
98
+ const snippetRaw = entry.snippet.trim();
99
+ const snippetNorm = normalizeSnippet(snippetRaw);
100
+ pendingOutboundMessageIds.push({
101
+ id: pendingOutboundMessageIdCounter,
102
+ accountId: entry.accountId,
103
+ sessionKey: entry.sessionKey,
104
+ outboundTarget: entry.outboundTarget,
105
+ chatGuid: trimOrUndefined(entry.chatGuid),
106
+ chatIdentifier: trimOrUndefined(entry.chatIdentifier),
107
+ chatId: typeof entry.chatId === "number" ? entry.chatId : undefined,
108
+ snippetRaw,
109
+ snippetNorm,
110
+ isMediaSnippet: snippetRaw.toLowerCase().startsWith("<media:"),
111
+ createdAt: Date.now(),
112
+ });
113
+ return pendingOutboundMessageIdCounter;
114
+ }
115
+
116
+ function forgetPendingOutboundMessageId(id: number): void {
117
+ const index = pendingOutboundMessageIds.findIndex((entry) => entry.id === id);
118
+ if (index >= 0) {
119
+ pendingOutboundMessageIds.splice(index, 1);
120
+ }
121
+ }
122
+
123
+ function chatsMatch(
124
+ left: Pick<PendingOutboundMessageId, "chatGuid" | "chatIdentifier" | "chatId">,
125
+ right: { chatGuid?: string; chatIdentifier?: string; chatId?: number },
126
+ ): boolean {
127
+ const leftGuid = trimOrUndefined(left.chatGuid);
128
+ const rightGuid = trimOrUndefined(right.chatGuid);
129
+ if (leftGuid && rightGuid) {
130
+ return leftGuid === rightGuid;
131
+ }
132
+
133
+ const leftIdentifier = trimOrUndefined(left.chatIdentifier);
134
+ const rightIdentifier = trimOrUndefined(right.chatIdentifier);
135
+ if (leftIdentifier && rightIdentifier) {
136
+ return leftIdentifier === rightIdentifier;
137
+ }
138
+
139
+ const leftChatId = typeof left.chatId === "number" ? left.chatId : undefined;
140
+ const rightChatId = typeof right.chatId === "number" ? right.chatId : undefined;
141
+ if (leftChatId !== undefined && rightChatId !== undefined) {
142
+ return leftChatId === rightChatId;
143
+ }
144
+
145
+ return false;
146
+ }
147
+
148
+ function consumePendingOutboundMessageId(params: {
149
+ accountId: string;
150
+ chatGuid?: string;
151
+ chatIdentifier?: string;
152
+ chatId?: number;
153
+ body: string;
154
+ }): PendingOutboundMessageId | null {
155
+ prunePendingOutboundMessageIds();
156
+ const bodyNorm = normalizeSnippet(params.body);
157
+ const isMediaBody = params.body.trim().toLowerCase().startsWith("<media:");
158
+
159
+ for (let i = 0; i < pendingOutboundMessageIds.length; i++) {
160
+ const entry = pendingOutboundMessageIds[i];
161
+ if (entry.accountId !== params.accountId) {
162
+ continue;
163
+ }
164
+ if (!chatsMatch(entry, params)) {
165
+ continue;
166
+ }
167
+ if (entry.snippetNorm && entry.snippetNorm === bodyNorm) {
168
+ pendingOutboundMessageIds.splice(i, 1);
169
+ return entry;
170
+ }
171
+ if (entry.isMediaSnippet && isMediaBody) {
172
+ pendingOutboundMessageIds.splice(i, 1);
173
+ return entry;
174
+ }
175
+ }
176
+
177
+ return null;
178
+ }
43
179
 
44
180
  export function logVerbose(
45
181
  core: BlueBubblesCoreRuntime,
@@ -107,6 +243,178 @@ function resolveBlueBubblesAckReaction(params: {
107
243
  }
108
244
  }
109
245
 
246
+ /**
247
+ * In-memory rolling history map keyed by account + chat identifier.
248
+ * Populated from incoming messages during the session.
249
+ * API backfill is attempted until one fetch resolves (or retries are exhausted).
250
+ */
251
+ const chatHistories = new Map<string, HistoryEntry[]>();
252
+ type HistoryBackfillState = {
253
+ attempts: number;
254
+ firstAttemptAt: number;
255
+ nextAttemptAt: number;
256
+ resolved: boolean;
257
+ };
258
+
259
+ const historyBackfills = new Map<string, HistoryBackfillState>();
260
+ const HISTORY_BACKFILL_BASE_DELAY_MS = 5_000;
261
+ const HISTORY_BACKFILL_MAX_DELAY_MS = 2 * 60 * 1000;
262
+ const HISTORY_BACKFILL_MAX_ATTEMPTS = 6;
263
+ const HISTORY_BACKFILL_RETRY_WINDOW_MS = 30 * 60 * 1000;
264
+ const MAX_STORED_HISTORY_ENTRY_CHARS = 2_000;
265
+ const MAX_INBOUND_HISTORY_ENTRY_CHARS = 1_200;
266
+ const MAX_INBOUND_HISTORY_TOTAL_CHARS = 12_000;
267
+
268
+ function buildAccountScopedHistoryKey(accountId: string, historyIdentifier: string): string {
269
+ return `${accountId}\u0000${historyIdentifier}`;
270
+ }
271
+
272
+ function historyDedupKey(entry: HistoryEntry): string {
273
+ const messageId = entry.messageId?.trim();
274
+ if (messageId) {
275
+ return `id:${messageId}`;
276
+ }
277
+ return `fallback:${entry.sender}\u0000${entry.body}\u0000${entry.timestamp ?? ""}`;
278
+ }
279
+
280
+ function truncateHistoryBody(body: string, maxChars: number): string {
281
+ const trimmed = body.trim();
282
+ if (!trimmed) {
283
+ return "";
284
+ }
285
+ if (trimmed.length <= maxChars) {
286
+ return trimmed;
287
+ }
288
+ return `${trimmed.slice(0, maxChars).trimEnd()}...`;
289
+ }
290
+
291
+ function mergeHistoryEntries(params: {
292
+ apiEntries: HistoryEntry[];
293
+ currentEntries: HistoryEntry[];
294
+ limit: number;
295
+ }): HistoryEntry[] {
296
+ if (params.limit <= 0) {
297
+ return [];
298
+ }
299
+
300
+ const merged: HistoryEntry[] = [];
301
+ const seen = new Set<string>();
302
+ const appendUnique = (entry: HistoryEntry) => {
303
+ const key = historyDedupKey(entry);
304
+ if (seen.has(key)) {
305
+ return;
306
+ }
307
+ seen.add(key);
308
+ merged.push(entry);
309
+ };
310
+
311
+ for (const entry of params.apiEntries) {
312
+ appendUnique(entry);
313
+ }
314
+ for (const entry of params.currentEntries) {
315
+ appendUnique(entry);
316
+ }
317
+
318
+ if (merged.length <= params.limit) {
319
+ return merged;
320
+ }
321
+ return merged.slice(merged.length - params.limit);
322
+ }
323
+
324
+ function pruneHistoryBackfillState(): void {
325
+ for (const key of historyBackfills.keys()) {
326
+ if (!chatHistories.has(key)) {
327
+ historyBackfills.delete(key);
328
+ }
329
+ }
330
+ }
331
+
332
+ function markHistoryBackfillResolved(historyKey: string): void {
333
+ const state = historyBackfills.get(historyKey);
334
+ if (state) {
335
+ state.resolved = true;
336
+ historyBackfills.set(historyKey, state);
337
+ return;
338
+ }
339
+ historyBackfills.set(historyKey, {
340
+ attempts: 0,
341
+ firstAttemptAt: Date.now(),
342
+ nextAttemptAt: Number.POSITIVE_INFINITY,
343
+ resolved: true,
344
+ });
345
+ }
346
+
347
+ function planHistoryBackfillAttempt(historyKey: string, now: number): HistoryBackfillState | null {
348
+ const existing = historyBackfills.get(historyKey);
349
+ if (existing?.resolved) {
350
+ return null;
351
+ }
352
+ if (existing && now - existing.firstAttemptAt > HISTORY_BACKFILL_RETRY_WINDOW_MS) {
353
+ markHistoryBackfillResolved(historyKey);
354
+ return null;
355
+ }
356
+ if (existing && existing.attempts >= HISTORY_BACKFILL_MAX_ATTEMPTS) {
357
+ markHistoryBackfillResolved(historyKey);
358
+ return null;
359
+ }
360
+ if (existing && now < existing.nextAttemptAt) {
361
+ return null;
362
+ }
363
+
364
+ const attempts = (existing?.attempts ?? 0) + 1;
365
+ const firstAttemptAt = existing?.firstAttemptAt ?? now;
366
+ const backoffDelay = Math.min(
367
+ HISTORY_BACKFILL_BASE_DELAY_MS * 2 ** (attempts - 1),
368
+ HISTORY_BACKFILL_MAX_DELAY_MS,
369
+ );
370
+ const state: HistoryBackfillState = {
371
+ attempts,
372
+ firstAttemptAt,
373
+ nextAttemptAt: now + backoffDelay,
374
+ resolved: false,
375
+ };
376
+ historyBackfills.set(historyKey, state);
377
+ return state;
378
+ }
379
+
380
+ function buildInboundHistorySnapshot(params: {
381
+ entries: HistoryEntry[];
382
+ limit: number;
383
+ }): Array<{ sender: string; body: string; timestamp?: number }> | undefined {
384
+ if (params.limit <= 0 || params.entries.length === 0) {
385
+ return undefined;
386
+ }
387
+ const recent = params.entries.slice(-params.limit);
388
+ const selected: Array<{ sender: string; body: string; timestamp?: number }> = [];
389
+ let remainingChars = MAX_INBOUND_HISTORY_TOTAL_CHARS;
390
+
391
+ for (let i = recent.length - 1; i >= 0; i--) {
392
+ const entry = recent[i];
393
+ const body = truncateHistoryBody(entry.body, MAX_INBOUND_HISTORY_ENTRY_CHARS);
394
+ if (!body) {
395
+ continue;
396
+ }
397
+ if (selected.length > 0 && body.length > remainingChars) {
398
+ break;
399
+ }
400
+ selected.push({
401
+ sender: entry.sender,
402
+ body,
403
+ timestamp: entry.timestamp,
404
+ });
405
+ remainingChars -= body.length;
406
+ if (remainingChars <= 0) {
407
+ break;
408
+ }
409
+ }
410
+
411
+ if (selected.length === 0) {
412
+ return undefined;
413
+ }
414
+ selected.reverse();
415
+ return selected;
416
+ }
417
+
110
418
  export async function processMessage(
111
419
  message: NormalizedWebhookMessage,
112
420
  target: WebhookTarget,
@@ -158,6 +466,26 @@ export async function processMessage(
158
466
  if (message.fromMe) {
159
467
  // Cache from-me messages so reply context can resolve sender/body.
160
468
  cacheInboundMessage();
469
+ if (cacheMessageId) {
470
+ const pending = consumePendingOutboundMessageId({
471
+ accountId: account.accountId,
472
+ chatGuid: message.chatGuid,
473
+ chatIdentifier: message.chatIdentifier,
474
+ chatId: message.chatId,
475
+ body: rawBody,
476
+ });
477
+ if (pending) {
478
+ const displayId = getShortIdForUuid(cacheMessageId) || cacheMessageId;
479
+ const previewSource = pending.snippetRaw || rawBody;
480
+ const preview = previewSource
481
+ ? ` "${previewSource.slice(0, 12)}${previewSource.length > 12 ? "…" : ""}"`
482
+ : "";
483
+ core.system.enqueueSystemEvent(`Assistant sent${preview} [message_id:${displayId}]`, {
484
+ sessionKey: pending.sessionKey,
485
+ contextKey: `bluebubbles:outbound:${pending.outboundTarget}:${cacheMessageId}`,
486
+ });
487
+ }
488
+ }
161
489
  return;
162
490
  }
163
491
 
@@ -173,41 +501,51 @@ export async function processMessage(
173
501
 
174
502
  const dmPolicy = account.config.dmPolicy ?? "pairing";
175
503
  const groupPolicy = account.config.groupPolicy ?? "allowlist";
176
- const configAllowFrom = (account.config.allowFrom ?? []).map((entry) => String(entry));
177
- const configGroupAllowFrom = (account.config.groupAllowFrom ?? []).map((entry) => String(entry));
178
504
  const storeAllowFrom = await core.channel.pairing
179
505
  .readAllowFromStore("bluebubbles")
180
506
  .catch(() => []);
181
- const effectiveAllowFrom = [...configAllowFrom, ...storeAllowFrom]
182
- .map((entry) => String(entry).trim())
183
- .filter(Boolean);
184
- const effectiveGroupAllowFrom = [
185
- ...(configGroupAllowFrom.length > 0 ? configGroupAllowFrom : configAllowFrom),
186
- ...storeAllowFrom,
187
- ]
188
- .map((entry) => String(entry).trim())
189
- .filter(Boolean);
507
+ const { effectiveAllowFrom, effectiveGroupAllowFrom } = resolveEffectiveAllowFromLists({
508
+ allowFrom: account.config.allowFrom,
509
+ groupAllowFrom: account.config.groupAllowFrom,
510
+ storeAllowFrom,
511
+ dmPolicy,
512
+ });
190
513
  const groupAllowEntry = formatGroupAllowlistEntry({
191
514
  chatGuid: message.chatGuid,
192
515
  chatId: message.chatId ?? undefined,
193
516
  chatIdentifier: message.chatIdentifier ?? undefined,
194
517
  });
195
518
  const groupName = message.chatName?.trim() || undefined;
519
+ const accessDecision = resolveDmGroupAccessDecision({
520
+ isGroup,
521
+ dmPolicy,
522
+ groupPolicy,
523
+ effectiveAllowFrom,
524
+ effectiveGroupAllowFrom,
525
+ isSenderAllowed: (allowFrom) =>
526
+ isAllowedBlueBubblesSender({
527
+ allowFrom,
528
+ sender: message.senderId,
529
+ chatId: message.chatId ?? undefined,
530
+ chatGuid: message.chatGuid ?? undefined,
531
+ chatIdentifier: message.chatIdentifier ?? undefined,
532
+ }),
533
+ });
196
534
 
197
- if (isGroup) {
198
- if (groupPolicy === "disabled") {
199
- logVerbose(core, runtime, "Blocked BlueBubbles group message (groupPolicy=disabled)");
200
- logGroupAllowlistHint({
201
- runtime,
202
- reason: "groupPolicy=disabled",
203
- entry: groupAllowEntry,
204
- chatName: groupName,
205
- accountId: account.accountId,
206
- });
207
- return;
208
- }
209
- if (groupPolicy === "allowlist") {
210
- if (effectiveGroupAllowFrom.length === 0) {
535
+ if (accessDecision.decision !== "allow") {
536
+ if (isGroup) {
537
+ if (accessDecision.reason === "groupPolicy=disabled") {
538
+ logVerbose(core, runtime, "Blocked BlueBubbles group message (groupPolicy=disabled)");
539
+ logGroupAllowlistHint({
540
+ runtime,
541
+ reason: "groupPolicy=disabled",
542
+ entry: groupAllowEntry,
543
+ chatName: groupName,
544
+ accountId: account.accountId,
545
+ });
546
+ return;
547
+ }
548
+ if (accessDecision.reason === "groupPolicy=allowlist (empty allowlist)") {
211
549
  logVerbose(core, runtime, "Blocked BlueBubbles group message (no allowlist)");
212
550
  logGroupAllowlistHint({
213
551
  runtime,
@@ -218,14 +556,7 @@ export async function processMessage(
218
556
  });
219
557
  return;
220
558
  }
221
- const allowed = isAllowedBlueBubblesSender({
222
- allowFrom: effectiveGroupAllowFrom,
223
- sender: message.senderId,
224
- chatId: message.chatId ?? undefined,
225
- chatGuid: message.chatGuid ?? undefined,
226
- chatIdentifier: message.chatIdentifier ?? undefined,
227
- });
228
- if (!allowed) {
559
+ if (accessDecision.reason === "groupPolicy=allowlist (not allowlisted)") {
229
560
  logVerbose(
230
561
  core,
231
562
  runtime,
@@ -245,70 +576,60 @@ export async function processMessage(
245
576
  });
246
577
  return;
247
578
  }
579
+ return;
248
580
  }
249
- } else {
250
- if (dmPolicy === "disabled") {
581
+
582
+ if (accessDecision.reason === "dmPolicy=disabled") {
251
583
  logVerbose(core, runtime, `Blocked BlueBubbles DM from ${message.senderId}`);
252
584
  logVerbose(core, runtime, `drop: dmPolicy disabled sender=${message.senderId}`);
253
585
  return;
254
586
  }
255
- if (dmPolicy !== "open") {
256
- const allowed = isAllowedBlueBubblesSender({
257
- allowFrom: effectiveAllowFrom,
258
- sender: message.senderId,
259
- chatId: message.chatId ?? undefined,
260
- chatGuid: message.chatGuid ?? undefined,
261
- chatIdentifier: message.chatIdentifier ?? undefined,
587
+
588
+ if (accessDecision.decision === "pairing") {
589
+ const { code, created } = await core.channel.pairing.upsertPairingRequest({
590
+ channel: "bluebubbles",
591
+ id: message.senderId,
592
+ meta: { name: message.senderName },
262
593
  });
263
- if (!allowed) {
264
- if (dmPolicy === "pairing") {
265
- const { code, created } = await core.channel.pairing.upsertPairingRequest({
266
- channel: "bluebubbles",
267
- id: message.senderId,
268
- meta: { name: message.senderName },
269
- });
270
- runtime.log?.(
271
- `[bluebubbles] pairing request sender=${message.senderId} created=${created}`,
594
+ runtime.log?.(`[bluebubbles] pairing request sender=${message.senderId} created=${created}`);
595
+ if (created) {
596
+ logVerbose(core, runtime, `bluebubbles pairing request sender=${message.senderId}`);
597
+ try {
598
+ await sendMessageBlueBubbles(
599
+ message.senderId,
600
+ core.channel.pairing.buildPairingReply({
601
+ channel: "bluebubbles",
602
+ idLine: `Your BlueBubbles sender id: ${message.senderId}`,
603
+ code,
604
+ }),
605
+ { cfg: config, accountId: account.accountId },
272
606
  );
273
- if (created) {
274
- logVerbose(core, runtime, `bluebubbles pairing request sender=${message.senderId}`);
275
- try {
276
- await sendMessageBlueBubbles(
277
- message.senderId,
278
- core.channel.pairing.buildPairingReply({
279
- channel: "bluebubbles",
280
- idLine: `Your BlueBubbles sender id: ${message.senderId}`,
281
- code,
282
- }),
283
- { cfg: config, accountId: account.accountId },
284
- );
285
- statusSink?.({ lastOutboundAt: Date.now() });
286
- } catch (err) {
287
- logVerbose(
288
- core,
289
- runtime,
290
- `bluebubbles pairing reply failed for ${message.senderId}: ${String(err)}`,
291
- );
292
- runtime.error?.(
293
- `[bluebubbles] pairing reply failed sender=${message.senderId}: ${String(err)}`,
294
- );
295
- }
296
- }
297
- } else {
607
+ statusSink?.({ lastOutboundAt: Date.now() });
608
+ } catch (err) {
298
609
  logVerbose(
299
610
  core,
300
611
  runtime,
301
- `Blocked unauthorized BlueBubbles sender ${message.senderId} (dmPolicy=${dmPolicy})`,
612
+ `bluebubbles pairing reply failed for ${message.senderId}: ${String(err)}`,
302
613
  );
303
- logVerbose(
304
- core,
305
- runtime,
306
- `drop: dm sender not allowed sender=${message.senderId} allowFrom=${effectiveAllowFrom.join(",")}`,
614
+ runtime.error?.(
615
+ `[bluebubbles] pairing reply failed sender=${message.senderId}: ${String(err)}`,
307
616
  );
308
617
  }
309
- return;
310
618
  }
619
+ return;
311
620
  }
621
+
622
+ logVerbose(
623
+ core,
624
+ runtime,
625
+ `Blocked unauthorized BlueBubbles sender ${message.senderId} (dmPolicy=${dmPolicy})`,
626
+ );
627
+ logVerbose(
628
+ core,
629
+ runtime,
630
+ `drop: dm sender not allowed sender=${message.senderId} allowFrom=${effectiveAllowFrom.join(",")}`,
631
+ );
632
+ return;
312
633
  }
313
634
 
314
635
  const chatId = message.chatId ?? undefined;
@@ -629,10 +950,10 @@ export async function processMessage(
629
950
  ? formatBlueBubblesChatTarget({ chatGuid: chatGuidForActions })
630
951
  : message.senderId;
631
952
 
632
- const maybeEnqueueOutboundMessageId = (messageId?: string, snippet?: string) => {
953
+ const maybeEnqueueOutboundMessageId = (messageId?: string, snippet?: string): boolean => {
633
954
  const trimmed = messageId?.trim();
634
955
  if (!trimmed || trimmed === "ok" || trimmed === "unknown") {
635
- return;
956
+ return false;
636
957
  }
637
958
  // Cache outbound message to get short ID
638
959
  const cacheEntry = rememberBlueBubblesReplyCache({
@@ -651,6 +972,7 @@ export async function processMessage(
651
972
  sessionKey: route.sessionKey,
652
973
  contextKey: `bluebubbles:outbound:${outboundTarget}:${trimmed}`,
653
974
  });
975
+ return true;
654
976
  };
655
977
  const sanitizeReplyDirectiveText = (value: string): string => {
656
978
  if (privateApiEnabled) {
@@ -662,9 +984,118 @@ export async function processMessage(
662
984
  .trim();
663
985
  };
664
986
 
987
+ // History: in-memory rolling map with bounded API backfill retries
988
+ const historyLimit = isGroup
989
+ ? (account.config.historyLimit ?? 0)
990
+ : (account.config.dmHistoryLimit ?? 0);
991
+
992
+ const historyIdentifier =
993
+ chatGuid ||
994
+ chatIdentifier ||
995
+ (chatId ? String(chatId) : null) ||
996
+ (isGroup ? null : message.senderId) ||
997
+ "";
998
+ const historyKey = historyIdentifier
999
+ ? buildAccountScopedHistoryKey(account.accountId, historyIdentifier)
1000
+ : "";
1001
+
1002
+ // Record the current message into rolling history
1003
+ if (historyKey && historyLimit > 0) {
1004
+ const nowMs = Date.now();
1005
+ const senderLabel = message.fromMe ? "me" : message.senderName || message.senderId;
1006
+ const normalizedHistoryBody = truncateHistoryBody(text, MAX_STORED_HISTORY_ENTRY_CHARS);
1007
+ const currentEntries = recordPendingHistoryEntryIfEnabled({
1008
+ historyMap: chatHistories,
1009
+ limit: historyLimit,
1010
+ historyKey,
1011
+ entry: normalizedHistoryBody
1012
+ ? {
1013
+ sender: senderLabel,
1014
+ body: normalizedHistoryBody,
1015
+ timestamp: message.timestamp ?? nowMs,
1016
+ messageId: message.messageId ?? undefined,
1017
+ }
1018
+ : null,
1019
+ });
1020
+ pruneHistoryBackfillState();
1021
+
1022
+ const backfillAttempt = planHistoryBackfillAttempt(historyKey, nowMs);
1023
+ if (backfillAttempt) {
1024
+ try {
1025
+ const backfillResult = await fetchBlueBubblesHistory(historyIdentifier, historyLimit, {
1026
+ cfg: config,
1027
+ accountId: account.accountId,
1028
+ });
1029
+ if (backfillResult.resolved) {
1030
+ markHistoryBackfillResolved(historyKey);
1031
+ }
1032
+ if (backfillResult.entries.length > 0) {
1033
+ const apiEntries: HistoryEntry[] = [];
1034
+ for (const entry of backfillResult.entries) {
1035
+ const body = truncateHistoryBody(entry.body, MAX_STORED_HISTORY_ENTRY_CHARS);
1036
+ if (!body) {
1037
+ continue;
1038
+ }
1039
+ apiEntries.push({
1040
+ sender: entry.sender,
1041
+ body,
1042
+ timestamp: entry.timestamp,
1043
+ messageId: entry.messageId,
1044
+ });
1045
+ }
1046
+ const merged = mergeHistoryEntries({
1047
+ apiEntries,
1048
+ currentEntries:
1049
+ currentEntries.length > 0 ? currentEntries : (chatHistories.get(historyKey) ?? []),
1050
+ limit: historyLimit,
1051
+ });
1052
+ if (chatHistories.has(historyKey)) {
1053
+ chatHistories.delete(historyKey);
1054
+ }
1055
+ chatHistories.set(historyKey, merged);
1056
+ evictOldHistoryKeys(chatHistories);
1057
+ logVerbose(
1058
+ core,
1059
+ runtime,
1060
+ `backfilled ${backfillResult.entries.length} history messages for ${isGroup ? "group" : "DM"}: ${historyIdentifier}`,
1061
+ );
1062
+ } else if (!backfillResult.resolved) {
1063
+ const remainingAttempts = HISTORY_BACKFILL_MAX_ATTEMPTS - backfillAttempt.attempts;
1064
+ const nextBackoffMs = Math.max(backfillAttempt.nextAttemptAt - nowMs, 0);
1065
+ logVerbose(
1066
+ core,
1067
+ runtime,
1068
+ `history backfill unresolved for ${historyIdentifier}; retries left=${Math.max(remainingAttempts, 0)} next_in_ms=${nextBackoffMs}`,
1069
+ );
1070
+ }
1071
+ } catch (err) {
1072
+ const remainingAttempts = HISTORY_BACKFILL_MAX_ATTEMPTS - backfillAttempt.attempts;
1073
+ const nextBackoffMs = Math.max(backfillAttempt.nextAttemptAt - nowMs, 0);
1074
+ logVerbose(
1075
+ core,
1076
+ runtime,
1077
+ `history backfill failed for ${historyIdentifier}: ${String(err)} (retries left=${Math.max(remainingAttempts, 0)} next_in_ms=${nextBackoffMs})`,
1078
+ );
1079
+ }
1080
+ }
1081
+ }
1082
+
1083
+ // Build inbound history from the in-memory map
1084
+ let inboundHistory: Array<{ sender: string; body: string; timestamp?: number }> | undefined;
1085
+ if (historyKey && historyLimit > 0) {
1086
+ const entries = chatHistories.get(historyKey);
1087
+ if (entries && entries.length > 0) {
1088
+ inboundHistory = buildInboundHistorySnapshot({
1089
+ entries,
1090
+ limit: historyLimit,
1091
+ });
1092
+ }
1093
+ }
1094
+
665
1095
  const ctxPayload = core.channel.reply.finalizeInboundContext({
666
1096
  Body: body,
667
1097
  BodyForAgent: rawBody,
1098
+ InboundHistory: inboundHistory,
668
1099
  RawBody: rawBody,
669
1100
  CommandBody: rawBody,
670
1101
  BodyForCommands: rawBody,
@@ -768,16 +1199,33 @@ export async function processMessage(
768
1199
  for (const mediaUrl of mediaList) {
769
1200
  const caption = first ? text : undefined;
770
1201
  first = false;
771
- const result = await sendBlueBubblesMedia({
772
- cfg: config,
773
- to: outboundTarget,
774
- mediaUrl,
775
- caption: caption ?? undefined,
776
- replyToId: replyToMessageGuid || null,
1202
+ const cachedBody = (caption ?? "").trim() || "<media:attachment>";
1203
+ const pendingId = rememberPendingOutboundMessageId({
777
1204
  accountId: account.accountId,
1205
+ sessionKey: route.sessionKey,
1206
+ outboundTarget,
1207
+ chatGuid: chatGuidForActions ?? chatGuid,
1208
+ chatIdentifier,
1209
+ chatId,
1210
+ snippet: cachedBody,
778
1211
  });
779
- const cachedBody = (caption ?? "").trim() || "<media:attachment>";
780
- maybeEnqueueOutboundMessageId(result.messageId, cachedBody);
1212
+ let result: Awaited<ReturnType<typeof sendBlueBubblesMedia>>;
1213
+ try {
1214
+ result = await sendBlueBubblesMedia({
1215
+ cfg: config,
1216
+ to: outboundTarget,
1217
+ mediaUrl,
1218
+ caption: caption ?? undefined,
1219
+ replyToId: replyToMessageGuid || null,
1220
+ accountId: account.accountId,
1221
+ });
1222
+ } catch (err) {
1223
+ forgetPendingOutboundMessageId(pendingId);
1224
+ throw err;
1225
+ }
1226
+ if (maybeEnqueueOutboundMessageId(result.messageId, cachedBody)) {
1227
+ forgetPendingOutboundMessageId(pendingId);
1228
+ }
781
1229
  sentMessage = true;
782
1230
  statusSink?.({ lastOutboundAt: Date.now() });
783
1231
  if (info.kind === "block") {
@@ -811,12 +1259,29 @@ export async function processMessage(
811
1259
  return;
812
1260
  }
813
1261
  for (const chunk of chunks) {
814
- const result = await sendMessageBlueBubbles(outboundTarget, chunk, {
815
- cfg: config,
1262
+ const pendingId = rememberPendingOutboundMessageId({
816
1263
  accountId: account.accountId,
817
- replyToMessageGuid: replyToMessageGuid || undefined,
1264
+ sessionKey: route.sessionKey,
1265
+ outboundTarget,
1266
+ chatGuid: chatGuidForActions ?? chatGuid,
1267
+ chatIdentifier,
1268
+ chatId,
1269
+ snippet: chunk,
818
1270
  });
819
- maybeEnqueueOutboundMessageId(result.messageId, chunk);
1271
+ let result: Awaited<ReturnType<typeof sendMessageBlueBubbles>>;
1272
+ try {
1273
+ result = await sendMessageBlueBubbles(outboundTarget, chunk, {
1274
+ cfg: config,
1275
+ accountId: account.accountId,
1276
+ replyToMessageGuid: replyToMessageGuid || undefined,
1277
+ });
1278
+ } catch (err) {
1279
+ forgetPendingOutboundMessageId(pendingId);
1280
+ throw err;
1281
+ }
1282
+ if (maybeEnqueueOutboundMessageId(result.messageId, chunk)) {
1283
+ forgetPendingOutboundMessageId(pendingId);
1284
+ }
820
1285
  sentMessage = true;
821
1286
  statusSink?.({ lastOutboundAt: Date.now() });
822
1287
  if (info.kind === "block") {
@@ -921,56 +1386,32 @@ export async function processReaction(
921
1386
 
922
1387
  const dmPolicy = account.config.dmPolicy ?? "pairing";
923
1388
  const groupPolicy = account.config.groupPolicy ?? "allowlist";
924
- const configAllowFrom = (account.config.allowFrom ?? []).map((entry) => String(entry));
925
- const configGroupAllowFrom = (account.config.groupAllowFrom ?? []).map((entry) => String(entry));
926
1389
  const storeAllowFrom = await core.channel.pairing
927
1390
  .readAllowFromStore("bluebubbles")
928
1391
  .catch(() => []);
929
- const effectiveAllowFrom = [...configAllowFrom, ...storeAllowFrom]
930
- .map((entry) => String(entry).trim())
931
- .filter(Boolean);
932
- const effectiveGroupAllowFrom = [
933
- ...(configGroupAllowFrom.length > 0 ? configGroupAllowFrom : configAllowFrom),
934
- ...storeAllowFrom,
935
- ]
936
- .map((entry) => String(entry).trim())
937
- .filter(Boolean);
938
-
939
- if (reaction.isGroup) {
940
- if (groupPolicy === "disabled") {
941
- return;
942
- }
943
- if (groupPolicy === "allowlist") {
944
- if (effectiveGroupAllowFrom.length === 0) {
945
- return;
946
- }
947
- const allowed = isAllowedBlueBubblesSender({
948
- allowFrom: effectiveGroupAllowFrom,
949
- sender: reaction.senderId,
950
- chatId: reaction.chatId ?? undefined,
951
- chatGuid: reaction.chatGuid ?? undefined,
952
- chatIdentifier: reaction.chatIdentifier ?? undefined,
953
- });
954
- if (!allowed) {
955
- return;
956
- }
957
- }
958
- } else {
959
- if (dmPolicy === "disabled") {
960
- return;
961
- }
962
- if (dmPolicy !== "open") {
963
- const allowed = isAllowedBlueBubblesSender({
964
- allowFrom: effectiveAllowFrom,
1392
+ const { effectiveAllowFrom, effectiveGroupAllowFrom } = resolveEffectiveAllowFromLists({
1393
+ allowFrom: account.config.allowFrom,
1394
+ groupAllowFrom: account.config.groupAllowFrom,
1395
+ storeAllowFrom,
1396
+ dmPolicy,
1397
+ });
1398
+ const accessDecision = resolveDmGroupAccessDecision({
1399
+ isGroup: reaction.isGroup,
1400
+ dmPolicy,
1401
+ groupPolicy,
1402
+ effectiveAllowFrom,
1403
+ effectiveGroupAllowFrom,
1404
+ isSenderAllowed: (allowFrom) =>
1405
+ isAllowedBlueBubblesSender({
1406
+ allowFrom,
965
1407
  sender: reaction.senderId,
966
1408
  chatId: reaction.chatId ?? undefined,
967
1409
  chatGuid: reaction.chatGuid ?? undefined,
968
1410
  chatIdentifier: reaction.chatIdentifier ?? undefined,
969
- });
970
- if (!allowed) {
971
- return;
972
- }
973
- }
1411
+ }),
1412
+ });
1413
+ if (accessDecision.decision !== "allow") {
1414
+ return;
974
1415
  }
975
1416
 
976
1417
  const chatId = reaction.chatId ?? undefined;