@poolzin/pool-bot 2026.2.0 → 2026.2.2

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 (258) hide show
  1. package/CHANGELOG.md +118 -0
  2. package/README-header.png +0 -0
  3. package/dist/agents/bash-tools.exec.js +76 -25
  4. package/dist/agents/cli-runner/helpers.js +9 -11
  5. package/dist/agents/context.js +1 -1
  6. package/dist/agents/identity.js +47 -7
  7. package/dist/agents/memory-search.js +25 -8
  8. package/dist/agents/model-catalog.js +1 -1
  9. package/dist/agents/model-selection.js +21 -0
  10. package/dist/agents/pi-embedded-block-chunker.js +117 -42
  11. package/dist/agents/pi-embedded-helpers/errors.js +183 -78
  12. package/dist/agents/pi-embedded-helpers.js +1 -1
  13. package/dist/agents/pi-embedded-runner/compact.js +8 -10
  14. package/dist/agents/pi-embedded-runner/model.js +62 -3
  15. package/dist/agents/pi-embedded-runner/run/attempt.js +21 -11
  16. package/dist/agents/pi-embedded-runner/run.js +199 -46
  17. package/dist/agents/pi-embedded-runner/system-prompt.js +10 -2
  18. package/dist/agents/pi-embedded-subscribe.js +118 -29
  19. package/dist/agents/pi-tools.js +10 -5
  20. package/dist/agents/poolbot-tools.js +15 -10
  21. package/dist/agents/sandbox-paths.js +31 -0
  22. package/dist/agents/session-tool-result-guard.js +94 -15
  23. package/dist/agents/shell-utils.js +51 -0
  24. package/dist/agents/skills/bundled-context.js +23 -0
  25. package/dist/agents/skills/bundled-dir.js +41 -7
  26. package/dist/agents/skills-install.js +60 -23
  27. package/dist/agents/subagent-announce.js +79 -34
  28. package/dist/agents/tool-policy.conformance.js +14 -0
  29. package/dist/agents/tool-policy.js +24 -0
  30. package/dist/agents/tools/cron-tool.js +166 -19
  31. package/dist/agents/tools/discord-actions-presence.js +78 -0
  32. package/dist/agents/tools/image-tool.js +1 -1
  33. package/dist/agents/tools/message-tool.js +56 -2
  34. package/dist/agents/tools/sessions-history-tool.js +69 -1
  35. package/dist/agents/tools/web-search.js +211 -42
  36. package/dist/agents/usage.js +23 -1
  37. package/dist/agents/workspace-run.js +67 -0
  38. package/dist/agents/workspace-templates.js +44 -0
  39. package/dist/auto-reply/command-auth.js +121 -6
  40. package/dist/auto-reply/envelope.js +74 -82
  41. package/dist/auto-reply/reply/commands-compact.js +1 -0
  42. package/dist/auto-reply/reply/commands-context-report.js +1 -0
  43. package/dist/auto-reply/reply/commands-context.js +1 -0
  44. package/dist/auto-reply/reply/commands-models.js +107 -60
  45. package/dist/auto-reply/reply/commands-ptt.js +171 -0
  46. package/dist/auto-reply/reply/get-reply-run.js +2 -1
  47. package/dist/auto-reply/reply/inbound-context.js +5 -1
  48. package/dist/auto-reply/reply/mentions.js +1 -1
  49. package/dist/auto-reply/reply/model-selection.js +3 -3
  50. package/dist/auto-reply/thinking.js +88 -43
  51. package/dist/browser/bridge-server.js +13 -0
  52. package/dist/browser/cdp.helpers.js +38 -24
  53. package/dist/browser/client-fetch.js +50 -7
  54. package/dist/browser/config.js +1 -10
  55. package/dist/browser/extension-relay.js +101 -40
  56. package/dist/browser/pw-ai.js +1 -1
  57. package/dist/browser/pw-session.js +143 -8
  58. package/dist/browser/pw-tools-core.interactions.js +125 -27
  59. package/dist/browser/pw-tools-core.responses.js +1 -1
  60. package/dist/browser/pw-tools-core.state.js +1 -1
  61. package/dist/browser/routes/agent.act.js +86 -41
  62. package/dist/browser/routes/dispatcher.js +4 -4
  63. package/dist/browser/screenshot.js +1 -1
  64. package/dist/browser/server.js +13 -0
  65. package/dist/build-info.json +3 -3
  66. package/dist/canvas-host/a2ui/index.html +28 -28
  67. package/dist/channels/reply-prefix.js +8 -1
  68. package/dist/cli/cron-cli/register.cron-add.js +61 -40
  69. package/dist/cli/cron-cli/register.cron-edit.js +60 -34
  70. package/dist/cli/cron-cli/shared.js +56 -41
  71. package/dist/cli/dns-cli.js +26 -14
  72. package/dist/cli/gateway-cli/register.js +37 -19
  73. package/dist/cli/memory-cli.js +5 -5
  74. package/dist/cli/parse-bytes.js +37 -0
  75. package/dist/cli/update-cli.js +173 -52
  76. package/dist/commands/agent.js +1 -0
  77. package/dist/commands/auth-choice.apply.oauth.js +1 -1
  78. package/dist/commands/doctor-config-flow.js +61 -5
  79. package/dist/commands/doctor-state-migrations.js +1 -1
  80. package/dist/commands/health.js +1 -1
  81. package/dist/commands/model-allowlist.js +29 -0
  82. package/dist/commands/model-picker.js +2 -1
  83. package/dist/commands/models/list.registry.js +1 -1
  84. package/dist/commands/models/list.status-command.js +43 -23
  85. package/dist/commands/models/shared.js +15 -0
  86. package/dist/commands/onboard-custom.js +384 -0
  87. package/dist/commands/onboard-non-interactive/local/auth-choice-inference.js +35 -0
  88. package/dist/commands/onboard-non-interactive/local/auth-choice.js +6 -3
  89. package/dist/commands/onboard-skills.js +63 -38
  90. package/dist/commands/openai-model-default.js +41 -0
  91. package/dist/compat/legacy-names.js +2 -0
  92. package/dist/config/defaults.js +3 -2
  93. package/dist/config/paths.js +136 -35
  94. package/dist/config/plugin-auto-enable.js +21 -5
  95. package/dist/config/redact-snapshot.js +153 -0
  96. package/dist/config/schema.field-metadata.js +590 -0
  97. package/dist/config/schema.js +2 -2
  98. package/dist/config/sessions/store.js +291 -23
  99. package/dist/config/zod-schema.agent-defaults.js +3 -0
  100. package/dist/config/zod-schema.agent-runtime.js +13 -2
  101. package/dist/config/zod-schema.providers-core.js +142 -0
  102. package/dist/config/zod-schema.session.js +3 -0
  103. package/dist/control-ui/assets/{index-CIRDm-Lu.css → index-CSfXd2LO.css} +1 -1
  104. package/dist/control-ui/assets/{index-CmNMuoem.js → index-HRr1grwl.js} +446 -413
  105. package/dist/control-ui/assets/index-HRr1grwl.js.map +1 -0
  106. package/dist/control-ui/index.html +4 -4
  107. package/dist/cron/delivery.js +57 -0
  108. package/dist/cron/isolated-agent/delivery-target.js +18 -3
  109. package/dist/cron/isolated-agent/helpers.js +22 -5
  110. package/dist/cron/isolated-agent/run.js +172 -63
  111. package/dist/cron/isolated-agent/session.js +2 -0
  112. package/dist/cron/normalize.js +356 -28
  113. package/dist/cron/parse.js +10 -5
  114. package/dist/cron/run-log.js +35 -10
  115. package/dist/cron/schedule.js +41 -6
  116. package/dist/cron/service/jobs.js +208 -35
  117. package/dist/cron/service/ops.js +72 -16
  118. package/dist/cron/service/state.js +2 -0
  119. package/dist/cron/service/store.js +386 -14
  120. package/dist/cron/service/timer.js +390 -147
  121. package/dist/cron/session-reaper.js +86 -0
  122. package/dist/cron/store.js +23 -8
  123. package/dist/cron/validate-timestamp.js +43 -0
  124. package/dist/discord/monitor/agent-components.js +438 -0
  125. package/dist/discord/monitor/allow-list.js +28 -5
  126. package/dist/discord/monitor/gateway-registry.js +29 -0
  127. package/dist/discord/monitor/native-command.js +44 -23
  128. package/dist/discord/monitor/sender-identity.js +45 -0
  129. package/dist/discord/pluralkit.js +27 -0
  130. package/dist/discord/send.outbound.js +92 -5
  131. package/dist/discord/send.shared.js +60 -23
  132. package/dist/discord/targets.js +84 -1
  133. package/dist/entry.js +15 -9
  134. package/dist/extensionAPI.js +8 -0
  135. package/dist/gateway/control-ui.js +8 -1
  136. package/dist/gateway/hooks-mapping.js +3 -0
  137. package/dist/gateway/hooks.js +65 -0
  138. package/dist/gateway/net.js +96 -31
  139. package/dist/gateway/node-command-policy.js +50 -15
  140. package/dist/gateway/origin-check.js +56 -0
  141. package/dist/gateway/protocol/client-info.js +9 -0
  142. package/dist/gateway/protocol/index.js +9 -2
  143. package/dist/gateway/protocol/schema/agents-models-skills.js +71 -1
  144. package/dist/gateway/protocol/schema/cron.js +22 -10
  145. package/dist/gateway/protocol/schema/protocol-schemas.js +16 -2
  146. package/dist/gateway/protocol/schema/sessions.js +12 -0
  147. package/dist/gateway/server/hooks.js +1 -1
  148. package/dist/gateway/server-broadcast.js +26 -9
  149. package/dist/gateway/server-chat.js +112 -23
  150. package/dist/gateway/server-discovery-runtime.js +10 -2
  151. package/dist/gateway/server-http.js +109 -11
  152. package/dist/gateway/server-methods/agent-timestamp.js +60 -0
  153. package/dist/gateway/server-methods/agents.js +321 -2
  154. package/dist/gateway/server-methods/usage.js +559 -16
  155. package/dist/gateway/server-runtime-state.js +22 -8
  156. package/dist/gateway/server-startup-memory.js +16 -0
  157. package/dist/gateway/server.impl.js +5 -1
  158. package/dist/gateway/session-utils.fs.js +23 -25
  159. package/dist/gateway/session-utils.js +20 -10
  160. package/dist/gateway/sessions-patch.js +7 -22
  161. package/dist/gateway/test-helpers.mocks.js +11 -7
  162. package/dist/gateway/test-helpers.server.js +35 -2
  163. package/dist/imessage/constants.js +2 -0
  164. package/dist/imessage/monitor/deliver.js +4 -1
  165. package/dist/imessage/monitor/monitor-provider.js +51 -1
  166. package/dist/infra/bonjour-discovery.js +131 -70
  167. package/dist/infra/control-ui-assets.js +134 -12
  168. package/dist/infra/errors.js +12 -0
  169. package/dist/infra/exec-approvals.js +266 -57
  170. package/dist/infra/format-time/format-datetime.js +79 -0
  171. package/dist/infra/format-time/format-duration.js +81 -0
  172. package/dist/infra/format-time/format-relative.js +80 -0
  173. package/dist/infra/heartbeat-runner.js +140 -49
  174. package/dist/infra/home-dir.js +54 -0
  175. package/dist/infra/net/fetch-guard.js +122 -0
  176. package/dist/infra/net/ssrf.js +65 -29
  177. package/dist/infra/outbound/abort.js +14 -0
  178. package/dist/infra/outbound/message-action-runner.js +77 -13
  179. package/dist/infra/outbound/outbound-session.js +143 -37
  180. package/dist/infra/poolbot-root.js +43 -1
  181. package/dist/infra/session-cost-usage.js +631 -41
  182. package/dist/infra/state-migrations.js +317 -47
  183. package/dist/infra/update-global.js +35 -0
  184. package/dist/infra/update-runner.js +149 -43
  185. package/dist/infra/warning-filter.js +65 -0
  186. package/dist/infra/widearea-dns.js +30 -9
  187. package/dist/logging/redact-identifier.js +12 -0
  188. package/dist/media/fetch.js +81 -58
  189. package/dist/media/store.js +2 -0
  190. package/dist/media-understanding/apply.js +403 -3
  191. package/dist/media-understanding/attachments.js +38 -27
  192. package/dist/media-understanding/defaults.js +16 -0
  193. package/dist/media-understanding/providers/deepgram/audio.js +22 -14
  194. package/dist/media-understanding/providers/google/audio.js +24 -17
  195. package/dist/media-understanding/providers/google/video.js +24 -17
  196. package/dist/media-understanding/providers/image.js +3 -3
  197. package/dist/media-understanding/providers/index.js +4 -1
  198. package/dist/media-understanding/providers/openai/audio.js +22 -14
  199. package/dist/media-understanding/providers/shared.js +16 -11
  200. package/dist/media-understanding/providers/zai/index.js +6 -0
  201. package/dist/media-understanding/runner.js +158 -90
  202. package/dist/memory/batch-voyage.js +277 -0
  203. package/dist/memory/embeddings-voyage.js +75 -0
  204. package/dist/memory/embeddings.js +28 -16
  205. package/dist/memory/internal.js +101 -18
  206. package/dist/memory/manager.js +154 -48
  207. package/dist/memory/search-manager.js +173 -0
  208. package/dist/memory/session-files.js +9 -3
  209. package/dist/node-host/runner.js +34 -24
  210. package/dist/node-host/with-timeout.js +27 -0
  211. package/dist/plugins/commands.js +5 -1
  212. package/dist/plugins/config-state.js +86 -7
  213. package/dist/plugins/source-display.js +51 -0
  214. package/dist/process/exec.js +20 -2
  215. package/dist/routing/resolve-route.js +12 -0
  216. package/dist/routing/session-key.js +15 -0
  217. package/dist/runtime.js +2 -0
  218. package/dist/security/audit-extra.async.js +601 -0
  219. package/dist/security/audit-extra.js +2 -830
  220. package/dist/security/audit-extra.sync.js +505 -0
  221. package/dist/security/channel-metadata.js +34 -0
  222. package/dist/security/external-content.js +88 -6
  223. package/dist/security/skill-scanner.js +330 -0
  224. package/dist/sessions/session-key-utils.js +7 -0
  225. package/dist/signal/monitor/event-handler.js +80 -1
  226. package/dist/slack/monitor/media.js +85 -15
  227. package/dist/tailscale/detect.js +1 -2
  228. package/dist/telegram/bot/helpers.js +109 -28
  229. package/dist/telegram/bot-handlers.js +144 -3
  230. package/dist/telegram/bot-message-context.js +37 -10
  231. package/dist/telegram/bot-message-dispatch.js +54 -17
  232. package/dist/telegram/bot-native-commands.js +86 -29
  233. package/dist/telegram/bot.js +30 -29
  234. package/dist/telegram/model-buttons.js +163 -0
  235. package/dist/telegram/monitor.js +110 -85
  236. package/dist/telegram/send.js +129 -47
  237. package/dist/terminal/restore.js +45 -0
  238. package/dist/test-helpers/state-dir-env.js +16 -0
  239. package/dist/tts/tts.js +12 -6
  240. package/dist/tui/tui-session-actions.js +166 -54
  241. package/dist/utils/fetch-timeout.js +20 -0
  242. package/dist/utils/normalize-secret-input.js +19 -0
  243. package/dist/utils/transcript-tools.js +58 -0
  244. package/dist/utils.js +45 -14
  245. package/dist/version.js +42 -5
  246. package/dist/wizard/clack-prompter.js +9 -6
  247. package/extensions/googlechat/node_modules/.bin/poolbot +21 -0
  248. package/extensions/googlechat/package.json +2 -2
  249. package/extensions/line/node_modules/.bin/poolbot +21 -0
  250. package/extensions/line/package.json +1 -1
  251. package/extensions/matrix/node_modules/.bin/poolbot +21 -0
  252. package/extensions/matrix/package.json +1 -1
  253. package/extensions/memory-core/node_modules/.bin/poolbot +21 -0
  254. package/extensions/memory-core/package.json +4 -1
  255. package/extensions/twitch/node_modules/.bin/poolbot +21 -0
  256. package/extensions/twitch/package.json +1 -1
  257. package/package.json +183 -24
  258. package/dist/control-ui/assets/index-CmNMuoem.js.map +0 -1
@@ -1,5 +1,5 @@
1
- import { lookup as dnsLookup } from "node:dns/promises";
2
1
  import { lookup as dnsLookupCb } from "node:dns";
2
+ import { lookup as dnsLookup } from "node:dns/promises";
3
3
  import { Agent } from "undici";
4
4
  export class SsrFBlockedError extends Error {
5
5
  constructor(message) {
@@ -16,13 +16,21 @@ function normalizeHostname(hostname) {
16
16
  }
17
17
  return normalized;
18
18
  }
19
+ function normalizeHostnameSet(values) {
20
+ if (!values || values.length === 0) {
21
+ return new Set();
22
+ }
23
+ return new Set(values.map((value) => normalizeHostname(value)).filter(Boolean));
24
+ }
19
25
  function parseIpv4(address) {
20
26
  const parts = address.split(".");
21
- if (parts.length !== 4)
27
+ if (parts.length !== 4) {
22
28
  return null;
29
+ }
23
30
  const numbers = parts.map((part) => Number.parseInt(part, 10));
24
- if (numbers.some((value) => Number.isNaN(value) || value < 0 || value > 255))
31
+ if (numbers.some((value) => Number.isNaN(value) || value < 0 || value > 255)) {
25
32
  return null;
33
+ }
26
34
  return numbers;
27
35
  }
28
36
  function parseIpv4FromMappedIpv6(mapped) {
@@ -32,12 +40,14 @@ function parseIpv4FromMappedIpv6(mapped) {
32
40
  const parts = mapped.split(":").filter(Boolean);
33
41
  if (parts.length === 1) {
34
42
  const value = Number.parseInt(parts[0], 16);
35
- if (Number.isNaN(value) || value < 0 || value > 0xffff_ffff)
43
+ if (Number.isNaN(value) || value < 0 || value > 0xffff_ffff) {
36
44
  return null;
45
+ }
37
46
  return [(value >>> 24) & 0xff, (value >>> 16) & 0xff, (value >>> 8) & 0xff, value & 0xff];
38
47
  }
39
- if (parts.length !== 2)
48
+ if (parts.length !== 2) {
40
49
  return null;
50
+ }
41
51
  const high = Number.parseInt(parts[0], 16);
42
52
  const low = Number.parseInt(parts[1], 16);
43
53
  if (Number.isNaN(high) ||
@@ -53,20 +63,27 @@ function parseIpv4FromMappedIpv6(mapped) {
53
63
  }
54
64
  function isPrivateIpv4(parts) {
55
65
  const [octet1, octet2] = parts;
56
- if (octet1 === 0)
66
+ if (octet1 === 0) {
57
67
  return true;
58
- if (octet1 === 10)
68
+ }
69
+ if (octet1 === 10) {
59
70
  return true;
60
- if (octet1 === 127)
71
+ }
72
+ if (octet1 === 127) {
61
73
  return true;
62
- if (octet1 === 169 && octet2 === 254)
74
+ }
75
+ if (octet1 === 169 && octet2 === 254) {
63
76
  return true;
64
- if (octet1 === 172 && octet2 >= 16 && octet2 <= 31)
77
+ }
78
+ if (octet1 === 172 && octet2 >= 16 && octet2 <= 31) {
65
79
  return true;
66
- if (octet1 === 192 && octet2 === 168)
80
+ }
81
+ if (octet1 === 192 && octet2 === 168) {
67
82
  return true;
68
- if (octet1 === 100 && octet2 >= 64 && octet2 <= 127)
83
+ }
84
+ if (octet1 === 100 && octet2 >= 64 && octet2 <= 127) {
69
85
  return true;
86
+ }
70
87
  return false;
71
88
  }
72
89
  export function isPrivateIpAddress(address) {
@@ -74,30 +91,36 @@ export function isPrivateIpAddress(address) {
74
91
  if (normalized.startsWith("[") && normalized.endsWith("]")) {
75
92
  normalized = normalized.slice(1, -1);
76
93
  }
77
- if (!normalized)
94
+ if (!normalized) {
78
95
  return false;
96
+ }
79
97
  if (normalized.startsWith("::ffff:")) {
80
98
  const mapped = normalized.slice("::ffff:".length);
81
99
  const ipv4 = parseIpv4FromMappedIpv6(mapped);
82
- if (ipv4)
100
+ if (ipv4) {
83
101
  return isPrivateIpv4(ipv4);
102
+ }
84
103
  }
85
104
  if (normalized.includes(":")) {
86
- if (normalized === "::" || normalized === "::1")
105
+ if (normalized === "::" || normalized === "::1") {
87
106
  return true;
107
+ }
88
108
  return PRIVATE_IPV6_PREFIXES.some((prefix) => normalized.startsWith(prefix));
89
109
  }
90
110
  const ipv4 = parseIpv4(normalized);
91
- if (!ipv4)
111
+ if (!ipv4) {
92
112
  return false;
113
+ }
93
114
  return isPrivateIpv4(ipv4);
94
115
  }
95
116
  export function isBlockedHostname(hostname) {
96
117
  const normalized = normalizeHostname(hostname);
97
- if (!normalized)
118
+ if (!normalized) {
98
119
  return false;
99
- if (BLOCKED_HOSTNAMES.has(normalized))
120
+ }
121
+ if (BLOCKED_HOSTNAMES.has(normalized)) {
100
122
  return true;
123
+ }
101
124
  return (normalized.endsWith(".localhost") ||
102
125
  normalized.endsWith(".local") ||
103
126
  normalized.endsWith(".internal"));
@@ -114,8 +137,9 @@ export function createPinnedLookup(params) {
114
137
  let index = 0;
115
138
  return ((host, options, callback) => {
116
139
  const cb = typeof options === "function" ? options : callback;
117
- if (!cb)
140
+ if (!cb) {
118
141
  return;
142
+ }
119
143
  const normalized = normalizeHostname(host);
120
144
  if (!normalized || normalized !== normalizedHost) {
121
145
  if (typeof options === "function" || options === undefined) {
@@ -140,24 +164,32 @@ export function createPinnedLookup(params) {
140
164
  cb(null, chosen.address, chosen.family);
141
165
  });
142
166
  }
143
- export async function resolvePinnedHostname(hostname, lookupFn = dnsLookup) {
167
+ export async function resolvePinnedHostnameWithPolicy(hostname, params = {}) {
144
168
  const normalized = normalizeHostname(hostname);
145
169
  if (!normalized) {
146
170
  throw new Error("Invalid hostname");
147
171
  }
148
- if (isBlockedHostname(normalized)) {
149
- throw new SsrFBlockedError(`Blocked hostname: ${hostname}`);
150
- }
151
- if (isPrivateIpAddress(normalized)) {
152
- throw new SsrFBlockedError("Blocked: private/internal IP address");
172
+ const allowPrivateNetwork = Boolean(params.policy?.allowPrivateNetwork);
173
+ const allowedHostnames = normalizeHostnameSet(params.policy?.allowedHostnames);
174
+ const isExplicitAllowed = allowedHostnames.has(normalized);
175
+ if (!allowPrivateNetwork && !isExplicitAllowed) {
176
+ if (isBlockedHostname(normalized)) {
177
+ throw new SsrFBlockedError(`Blocked hostname: ${hostname}`);
178
+ }
179
+ if (isPrivateIpAddress(normalized)) {
180
+ throw new SsrFBlockedError("Blocked: private/internal IP address");
181
+ }
153
182
  }
183
+ const lookupFn = params.lookupFn ?? dnsLookup;
154
184
  const results = await lookupFn(normalized, { all: true });
155
185
  if (results.length === 0) {
156
186
  throw new Error(`Unable to resolve hostname: ${hostname}`);
157
187
  }
158
- for (const entry of results) {
159
- if (isPrivateIpAddress(entry.address)) {
160
- throw new SsrFBlockedError("Blocked: resolves to private/internal IP address");
188
+ if (!allowPrivateNetwork && !isExplicitAllowed) {
189
+ for (const entry of results) {
190
+ if (isPrivateIpAddress(entry.address)) {
191
+ throw new SsrFBlockedError("Blocked: resolves to private/internal IP address");
192
+ }
161
193
  }
162
194
  }
163
195
  const addresses = Array.from(new Set(results.map((entry) => entry.address)));
@@ -170,6 +202,9 @@ export async function resolvePinnedHostname(hostname, lookupFn = dnsLookup) {
170
202
  lookup: createPinnedLookup({ hostname: normalized, addresses }),
171
203
  };
172
204
  }
205
+ export async function resolvePinnedHostname(hostname, lookupFn = dnsLookup) {
206
+ return await resolvePinnedHostnameWithPolicy(hostname, { lookupFn });
207
+ }
173
208
  export function createPinnedDispatcher(pinned) {
174
209
  return new Agent({
175
210
  connect: {
@@ -178,8 +213,9 @@ export function createPinnedDispatcher(pinned) {
178
213
  });
179
214
  }
180
215
  export async function closeDispatcher(dispatcher) {
181
- if (!dispatcher)
216
+ if (!dispatcher) {
182
217
  return;
218
+ }
183
219
  const candidate = dispatcher;
184
220
  try {
185
221
  if (typeof candidate.close === "function") {
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Utility for checking AbortSignal state and throwing a standard AbortError.
3
+ */
4
+ /**
5
+ * Throws an AbortError if the given signal has been aborted.
6
+ * Use at async checkpoints to support cancellation.
7
+ */
8
+ export function throwIfAborted(abortSignal) {
9
+ if (abortSignal?.aborted) {
10
+ const err = new Error("Operation aborted");
11
+ err.name = "AbortError";
12
+ throw err;
13
+ }
14
+ }
@@ -15,6 +15,9 @@ import { resolveChannelTarget } from "./target-resolver.js";
15
15
  import { loadWebMedia } from "../../web/media.js";
16
16
  import { extensionForMime } from "../../media/mime.js";
17
17
  import { parseSlackTarget } from "../../slack/targets.js";
18
+ import { parseTelegramTarget } from "../../telegram/targets.js";
19
+ import { assertMediaNotDataUrl, resolveSandboxedMediaSource } from "../../agents/sandbox-paths.js";
20
+ import { throwIfAborted } from "./abort.js";
18
21
  export function getToolResult(result) {
19
22
  return "toolResult" in result ? result.toolResult : undefined;
20
23
  }
@@ -99,13 +102,23 @@ function resolveSlackAutoThreadId(params) {
99
102
  return undefined;
100
103
  return context.currentThreadTs;
101
104
  }
105
+ /**
106
+ * Auto-inject Telegram forum topic thread ID when the message tool targets
107
+ * the same chat the session originated from.
108
+ */
109
+ function resolveTelegramAutoThreadId(params) {
110
+ const context = params.toolContext;
111
+ if (!context?.currentThreadTs || !context.currentChannelId)
112
+ return undefined;
113
+ const parsedTo = parseTelegramTarget(params.to);
114
+ const parsedChannel = parseTelegramTarget(context.currentChannelId);
115
+ if (parsedTo.chatId.toLowerCase() !== parsedChannel.chatId.toLowerCase())
116
+ return undefined;
117
+ return context.currentThreadTs;
118
+ }
102
119
  function resolveAttachmentMaxBytes(params) {
103
- const fallback = params.cfg.agents?.defaults?.mediaMaxMb;
104
- if (params.channel !== "bluebubbles") {
105
- return typeof fallback === "number" ? fallback * 1024 * 1024 : undefined;
106
- }
107
120
  const accountId = typeof params.accountId === "string" ? params.accountId.trim() : "";
108
- const channelCfg = params.cfg.channels?.bluebubbles;
121
+ const channelCfg = params.cfg.channels?.[params.channel];
109
122
  const channelObj = channelCfg && typeof channelCfg === "object"
110
123
  ? channelCfg
111
124
  : undefined;
@@ -163,6 +176,41 @@ function normalizeBase64Payload(params) {
163
176
  contentType: params.contentType ?? mime,
164
177
  };
165
178
  }
179
+ async function normalizeSandboxMediaParams(params) {
180
+ const sandboxRoot = params.sandboxRoot?.trim();
181
+ const mediaKeys = ["media", "path", "filePath"];
182
+ for (const key of mediaKeys) {
183
+ const raw = readStringParam(params.args, key, { trim: false });
184
+ if (!raw)
185
+ continue;
186
+ assertMediaNotDataUrl(raw);
187
+ if (!sandboxRoot)
188
+ continue;
189
+ const normalized = await resolveSandboxedMediaSource({ media: raw, sandboxRoot });
190
+ if (normalized !== raw) {
191
+ params.args[key] = normalized;
192
+ }
193
+ }
194
+ }
195
+ async function normalizeSandboxMediaList(params) {
196
+ const sandboxRoot = params.sandboxRoot?.trim();
197
+ const normalized = [];
198
+ const seen = new Set();
199
+ for (const value of params.values) {
200
+ const raw = value?.trim();
201
+ if (!raw)
202
+ continue;
203
+ assertMediaNotDataUrl(raw);
204
+ const resolved = sandboxRoot
205
+ ? await resolveSandboxedMediaSource({ media: raw, sandboxRoot })
206
+ : raw;
207
+ if (seen.has(resolved))
208
+ continue;
209
+ seen.add(resolved);
210
+ normalized.push(resolved);
211
+ }
212
+ return normalized;
213
+ }
166
214
  async function hydrateSetGroupIconParams(params) {
167
215
  if (params.action !== "setGroupIcon")
168
216
  return;
@@ -417,13 +465,6 @@ async function handleBroadcastAction(input, params) {
417
465
  dryRun: Boolean(input.dryRun),
418
466
  };
419
467
  }
420
- function throwIfAborted(abortSignal) {
421
- if (abortSignal?.aborted) {
422
- const err = new Error("Message send aborted");
423
- err.name = "AbortError";
424
- throw err;
425
- }
426
- }
427
468
  async function handleSendAction(ctx) {
428
469
  const { cfg, params, channel, accountId, dryRun, gateway, input, agentId, resolvedTarget, abortSignal, } = ctx;
429
470
  throwIfAborted(abortSignal);
@@ -438,6 +479,10 @@ async function handleSendAction(ctx) {
438
479
  required: !mediaHint && !hasCard,
439
480
  allowEmpty: true,
440
481
  }) ?? "";
482
+ // Unescape literal \\n sequences that models sometimes emit in tool arguments.
483
+ if (message.includes("\\n")) {
484
+ message = message.replaceAll("\\n", "\n");
485
+ }
441
486
  const parsed = parseReplyDirectives(message);
442
487
  const mergedMediaUrls = [];
443
488
  const seenMedia = new Set();
@@ -454,6 +499,13 @@ async function handleSendAction(ctx) {
454
499
  for (const url of parsed.mediaUrls ?? [])
455
500
  pushMedia(url);
456
501
  pushMedia(parsed.mediaUrl);
502
+ // Sandbox-validate and deduplicate media URLs.
503
+ const normalizedMediaUrls = await normalizeSandboxMediaList({
504
+ values: mergedMediaUrls,
505
+ sandboxRoot: input.sandboxRoot,
506
+ });
507
+ mergedMediaUrls.length = 0;
508
+ mergedMediaUrls.push(...normalizedMediaUrls);
457
509
  message = parsed.text;
458
510
  params.message = message;
459
511
  if (!params.replyTo && parsed.replyToId)
@@ -482,6 +534,14 @@ async function handleSendAction(ctx) {
482
534
  const slackAutoThreadId = channel === "slack" && !replyToId && !threadId
483
535
  ? resolveSlackAutoThreadId({ to, toolContext: input.toolContext })
484
536
  : undefined;
537
+ // Telegram forum topic auto-threading
538
+ const telegramAutoThreadId = channel === "telegram" && !threadId
539
+ ? resolveTelegramAutoThreadId({ to, toolContext: input.toolContext })
540
+ : undefined;
541
+ const resolvedThreadId = threadId ?? slackAutoThreadId ?? telegramAutoThreadId;
542
+ if (resolvedThreadId && !params.threadId) {
543
+ params.threadId = resolvedThreadId;
544
+ }
485
545
  const outboundRoute = agentId && !dryRun
486
546
  ? await resolveOutboundSessionRoute({
487
547
  cfg,
@@ -491,7 +551,7 @@ async function handleSendAction(ctx) {
491
551
  target: to,
492
552
  resolvedTarget,
493
553
  replyToId,
494
- threadId: threadId ?? slackAutoThreadId,
554
+ threadId: resolvedThreadId,
495
555
  })
496
556
  : null;
497
557
  if (outboundRoute && agentId && !dryRun) {
@@ -696,6 +756,10 @@ export async function runMessageAction(input) {
696
756
  params.accountId = accountId;
697
757
  }
698
758
  const dryRun = Boolean(input.dryRun ?? readBooleanParam(params, "dryRun"));
759
+ await normalizeSandboxMediaParams({
760
+ args: params,
761
+ sandboxRoot: input.sandboxRoot,
762
+ });
699
763
  await hydrateSendAttachmentParams({
700
764
  cfg,
701
765
  channel,