@poolzin/pool-bot 2026.1.39 → 2026.2.1

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 (511) hide show
  1. package/assets/chrome-extension/README.md +3 -3
  2. package/assets/chrome-extension/background.js +5 -5
  3. package/assets/chrome-extension/manifest.json +3 -3
  4. package/assets/chrome-extension/options.html +4 -4
  5. package/assets/chrome-extension/options.js +1 -1
  6. package/dist/acp/client.js +3 -3
  7. package/dist/acp/types.js +1 -1
  8. package/dist/agents/agent-paths.js +3 -3
  9. package/dist/agents/auth-profiles/paths.js +3 -3
  10. package/dist/agents/bash-tools.exec.js +76 -25
  11. package/dist/agents/cli-runner/helpers.js +10 -12
  12. package/dist/agents/cli-runner.js +2 -2
  13. package/dist/agents/cloudflare-ai-gateway.js +31 -0
  14. package/dist/agents/compaction.js +16 -2
  15. package/dist/agents/context-window-guard.js +13 -10
  16. package/dist/agents/context.js +4 -4
  17. package/dist/agents/docs-path.js +1 -1
  18. package/dist/agents/identity.js +47 -7
  19. package/dist/agents/memory-search.js +25 -8
  20. package/dist/agents/minimax-vlm.js +1 -1
  21. package/dist/agents/model-auth.js +12 -1
  22. package/dist/agents/model-catalog.js +4 -4
  23. package/dist/agents/model-selection.js +31 -4
  24. package/dist/agents/models-config.js +3 -3
  25. package/dist/agents/models-config.providers.js +147 -39
  26. package/dist/agents/pi-embedded-block-chunker.js +117 -42
  27. package/dist/agents/pi-embedded-helpers/errors.js +183 -78
  28. package/dist/agents/pi-embedded-helpers/openai.js +1 -1
  29. package/dist/agents/pi-embedded-helpers.js +1 -1
  30. package/dist/agents/pi-embedded-runner/compact.js +9 -8
  31. package/dist/agents/pi-embedded-runner/model.js +63 -4
  32. package/dist/agents/pi-embedded-runner/run/attempt.js +27 -17
  33. package/dist/agents/pi-embedded-runner/run.js +203 -50
  34. package/dist/agents/pi-embedded-runner/system-prompt.js +10 -2
  35. package/dist/agents/pi-embedded-runner/tool-result-truncation.js +275 -0
  36. package/dist/agents/pi-embedded-runner/utils.js +1 -1
  37. package/dist/agents/pi-embedded-subscribe.js +118 -29
  38. package/dist/agents/pi-model-discovery.js +10 -0
  39. package/dist/agents/pi-tool-definition-adapter.js +50 -9
  40. package/dist/agents/pi-tools.before-tool-call.js +67 -0
  41. package/dist/agents/pi-tools.js +20 -10
  42. package/dist/agents/pi-tools.read.js +2 -2
  43. package/dist/agents/poolbot-tools.js +15 -10
  44. package/dist/agents/sandbox-paths.js +31 -0
  45. package/dist/agents/session-file-repair.js +83 -0
  46. package/dist/agents/session-tool-result-guard.js +94 -15
  47. package/dist/agents/session-transcript-repair.js +68 -0
  48. package/dist/agents/shell-utils.js +51 -0
  49. package/dist/agents/skills/bundled-context.js +23 -0
  50. package/dist/agents/skills/bundled-dir.js +41 -7
  51. package/dist/agents/skills/frontmatter.js +1 -1
  52. package/dist/agents/skills/workspace.js +2 -2
  53. package/dist/agents/skills-install.js +60 -23
  54. package/dist/agents/subagent-announce.js +79 -34
  55. package/dist/agents/system-prompt.js +28 -4
  56. package/dist/agents/together-models.js +127 -0
  57. package/dist/agents/tool-images.js +1 -1
  58. package/dist/agents/tool-policy.conformance.js +14 -0
  59. package/dist/agents/tool-policy.js +25 -1
  60. package/dist/agents/tools/browser-tool.js +3 -3
  61. package/dist/agents/tools/cron-tool.js +166 -19
  62. package/dist/agents/tools/discord-actions-presence.js +78 -0
  63. package/dist/agents/tools/image-tool.js +2 -2
  64. package/dist/agents/tools/memory-tool.js +93 -5
  65. package/dist/agents/tools/message-tool.js +56 -2
  66. package/dist/agents/tools/sessions-history-tool.js +69 -1
  67. package/dist/agents/tools/web-search.js +211 -42
  68. package/dist/agents/usage.js +23 -1
  69. package/dist/agents/workspace-run.js +67 -0
  70. package/dist/agents/workspace-templates.js +44 -0
  71. package/dist/auto-reply/command-auth.js +121 -6
  72. package/dist/auto-reply/commands-registry.data.js +1 -1
  73. package/dist/auto-reply/envelope.js +50 -72
  74. package/dist/auto-reply/reply/commands-compact.js +1 -0
  75. package/dist/auto-reply/reply/commands-context-report.js +3 -2
  76. package/dist/auto-reply/reply/commands-context.js +1 -0
  77. package/dist/auto-reply/reply/commands-models.js +107 -60
  78. package/dist/auto-reply/reply/commands-ptt.js +171 -0
  79. package/dist/auto-reply/reply/commands-session.js +2 -2
  80. package/dist/auto-reply/reply/get-reply-run.js +16 -5
  81. package/dist/auto-reply/reply/groups.js +1 -1
  82. package/dist/auto-reply/reply/inbound-context.js +9 -1
  83. package/dist/auto-reply/reply/inbound-meta.js +130 -0
  84. package/dist/auto-reply/reply/model-selection.js +3 -3
  85. package/dist/auto-reply/reply/untrusted-context.js +15 -0
  86. package/dist/auto-reply/status.js +1 -1
  87. package/dist/auto-reply/thinking.js +88 -43
  88. package/dist/browser/bridge-server.js +13 -0
  89. package/dist/browser/cdp.helpers.js +38 -24
  90. package/dist/browser/client-fetch.js +51 -8
  91. package/dist/browser/config.js +2 -11
  92. package/dist/browser/extension-relay.js +104 -43
  93. package/dist/browser/pw-ai.js +1 -1
  94. package/dist/browser/pw-session.js +143 -8
  95. package/dist/browser/pw-tools-core.interactions.js +125 -27
  96. package/dist/browser/pw-tools-core.responses.js +1 -1
  97. package/dist/browser/pw-tools-core.state.js +1 -1
  98. package/dist/browser/routes/agent.act.js +86 -41
  99. package/dist/browser/routes/dispatcher.js +4 -4
  100. package/dist/browser/screenshot.js +1 -1
  101. package/dist/browser/server-context.js +2 -2
  102. package/dist/browser/server.js +13 -0
  103. package/dist/build-info.json +3 -3
  104. package/dist/canvas-host/a2ui.js +3 -3
  105. package/dist/channels/plugins/catalog.js +2 -2
  106. package/dist/channels/plugins/onboarding/imessage.js +1 -1
  107. package/dist/channels/plugins/onboarding/signal.js +1 -1
  108. package/dist/channels/plugins/onboarding/slack.js +4 -4
  109. package/dist/channels/plugins/onboarding/whatsapp.js +3 -3
  110. package/dist/channels/plugins/pairing-message.js +1 -1
  111. package/dist/channels/reply-prefix.js +8 -1
  112. package/dist/cli/browser-cli-extension.js +2 -2
  113. package/dist/cli/cron-cli/register.cron-add.js +61 -40
  114. package/dist/cli/cron-cli/register.cron-edit.js +60 -34
  115. package/dist/cli/cron-cli/shared.js +56 -41
  116. package/dist/cli/dns-cli.js +26 -14
  117. package/dist/cli/docs-cli.js +1 -1
  118. package/dist/cli/gateway-cli/dev.js +1 -1
  119. package/dist/cli/gateway-cli/register.js +37 -19
  120. package/dist/cli/memory-cli.js +30 -20
  121. package/dist/cli/nodes-cli/register.canvas.js +1 -1
  122. package/dist/cli/parse-bytes.js +37 -0
  123. package/dist/cli/plugins-cli.js +1 -1
  124. package/dist/cli/run-main.js +2 -2
  125. package/dist/cli/security-cli.js +1 -1
  126. package/dist/cli/tagline.js +1 -1
  127. package/dist/cli/update-cli.js +173 -52
  128. package/dist/cli/webhooks-cli.js +5 -5
  129. package/dist/commands/agent.js +1 -0
  130. package/dist/commands/agents.commands.add.js +1 -1
  131. package/dist/commands/auth-choice.apply.api-providers.js +305 -17
  132. package/dist/commands/auth-choice.apply.js +4 -1
  133. package/dist/commands/auth-choice.apply.plugin-provider.js +2 -2
  134. package/dist/commands/auth-choice.apply.xai.js +63 -0
  135. package/dist/commands/auth-choice.preferred-provider.js +7 -1
  136. package/dist/commands/configure.wizard.js +1 -1
  137. package/dist/commands/dashboard.js +1 -1
  138. package/dist/commands/docs.js +1 -1
  139. package/dist/commands/doctor-config-flow.js +61 -5
  140. package/dist/commands/doctor-gateway-services.js +3 -3
  141. package/dist/commands/doctor-state-migrations.js +1 -1
  142. package/dist/commands/doctor-update.js +3 -3
  143. package/dist/commands/doctor.js +1 -1
  144. package/dist/commands/health.js +1 -1
  145. package/dist/commands/model-allowlist.js +29 -0
  146. package/dist/commands/model-picker.js +2 -1
  147. package/dist/commands/models/list.probe.js +2 -2
  148. package/dist/commands/models/list.registry.js +4 -4
  149. package/dist/commands/models/list.status-command.js +44 -24
  150. package/dist/commands/models/shared.js +15 -0
  151. package/dist/commands/onboard-auth.config-core.js +366 -28
  152. package/dist/commands/onboard-auth.credentials.js +71 -9
  153. package/dist/commands/onboard-auth.js +3 -3
  154. package/dist/commands/onboard-auth.models.js +26 -24
  155. package/dist/commands/onboard-custom.js +384 -0
  156. package/dist/commands/onboard-non-interactive/local/auth-choice-inference.js +35 -0
  157. package/dist/commands/onboard-non-interactive/local/auth-choice.js +146 -9
  158. package/dist/commands/onboard-skills.js +63 -38
  159. package/dist/commands/openai-model-default.js +41 -0
  160. package/dist/commands/status-all/report-lines.js +1 -1
  161. package/dist/commands/status.command.js +1 -1
  162. package/dist/commands/uninstall.js +3 -3
  163. package/dist/compat/legacy-names.js +1 -1
  164. package/dist/config/defaults.js +3 -2
  165. package/dist/config/io.js +3 -3
  166. package/dist/config/paths.js +136 -35
  167. package/dist/config/plugin-auto-enable.js +21 -5
  168. package/dist/config/redact-snapshot.js +153 -0
  169. package/dist/config/schema.field-metadata.js +590 -0
  170. package/dist/config/schema.js +3 -3
  171. package/dist/config/sessions/store.js +291 -23
  172. package/dist/config/types.memory.js +1 -0
  173. package/dist/config/version.js +4 -4
  174. package/dist/config/zod-schema.agent-defaults.js +3 -0
  175. package/dist/config/zod-schema.agent-runtime.js +13 -2
  176. package/dist/config/zod-schema.providers-core.js +142 -0
  177. package/dist/config/zod-schema.session.js +3 -0
  178. package/dist/cron/delivery.js +57 -0
  179. package/dist/cron/isolated-agent/delivery-target.js +18 -3
  180. package/dist/cron/isolated-agent/helpers.js +22 -5
  181. package/dist/cron/isolated-agent/run.js +171 -63
  182. package/dist/cron/isolated-agent/session.js +2 -0
  183. package/dist/cron/normalize.js +356 -28
  184. package/dist/cron/parse.js +10 -5
  185. package/dist/cron/run-log.js +35 -10
  186. package/dist/cron/schedule.js +41 -6
  187. package/dist/cron/service/jobs.js +208 -35
  188. package/dist/cron/service/ops.js +72 -16
  189. package/dist/cron/service/state.js +2 -0
  190. package/dist/cron/service/store.js +386 -14
  191. package/dist/cron/service/timer.js +390 -147
  192. package/dist/cron/session-reaper.js +86 -0
  193. package/dist/cron/store.js +23 -8
  194. package/dist/cron/validate-timestamp.js +43 -0
  195. package/dist/daemon/constants.js +7 -7
  196. package/dist/daemon/inspect.js +6 -6
  197. package/dist/daemon/systemd-unit.js +1 -1
  198. package/dist/discord/monitor/agent-components.js +438 -0
  199. package/dist/discord/monitor/allow-list.js +28 -5
  200. package/dist/discord/monitor/gateway-registry.js +29 -0
  201. package/dist/discord/monitor/native-command.js +44 -23
  202. package/dist/discord/monitor/sender-identity.js +45 -0
  203. package/dist/discord/pluralkit.js +27 -0
  204. package/dist/discord/send.outbound.js +92 -5
  205. package/dist/discord/send.shared.js +60 -23
  206. package/dist/discord/targets.js +84 -1
  207. package/dist/entry.js +15 -9
  208. package/dist/extensionAPI.js +8 -0
  209. package/dist/gateway/control-ui.js +8 -1
  210. package/dist/gateway/hooks-mapping.js +3 -0
  211. package/dist/gateway/hooks.js +65 -0
  212. package/dist/gateway/live-image-probe.js +1 -66
  213. package/dist/gateway/net.js +96 -31
  214. package/dist/gateway/node-command-policy.js +50 -15
  215. package/dist/gateway/openai-http.js +2 -2
  216. package/dist/gateway/openresponses-http.js +4 -4
  217. package/dist/gateway/origin-check.js +56 -0
  218. package/dist/gateway/protocol/client-info.js +9 -0
  219. package/dist/gateway/protocol/index.js +9 -2
  220. package/dist/gateway/protocol/schema/agents-models-skills.js +71 -1
  221. package/dist/gateway/protocol/schema/cron.js +22 -10
  222. package/dist/gateway/protocol/schema/protocol-schemas.js +16 -2
  223. package/dist/gateway/protocol/schema/sessions.js +12 -0
  224. package/dist/gateway/server/hooks.js +1 -1
  225. package/dist/gateway/server-broadcast.js +26 -9
  226. package/dist/gateway/server-chat.js +112 -23
  227. package/dist/gateway/server-discovery-runtime.js +10 -2
  228. package/dist/gateway/server-discovery.js +2 -2
  229. package/dist/gateway/server-http.js +110 -12
  230. package/dist/gateway/server-methods/agent-timestamp.js +60 -0
  231. package/dist/gateway/server-methods/agents.js +321 -2
  232. package/dist/gateway/server-methods/usage.js +559 -16
  233. package/dist/gateway/server-runtime-state.js +22 -8
  234. package/dist/gateway/server-startup-memory.js +16 -0
  235. package/dist/gateway/server.impl.js +7 -3
  236. package/dist/gateway/session-utils.fs.js +23 -25
  237. package/dist/gateway/session-utils.js +20 -10
  238. package/dist/gateway/sessions-patch.js +7 -22
  239. package/dist/gateway/test-helpers.server.js +35 -2
  240. package/dist/hooks/frontmatter.js +1 -1
  241. package/dist/hooks/hooks-status.js +1 -1
  242. package/dist/hooks/install.js +2 -2
  243. package/dist/hooks/loader.js +1 -1
  244. package/dist/hooks/workspace.js +3 -3
  245. package/dist/imessage/constants.js +2 -0
  246. package/dist/imessage/monitor/deliver.js +4 -1
  247. package/dist/imessage/monitor/monitor-provider.js +51 -1
  248. package/dist/index.js +2 -2
  249. package/dist/infra/bonjour-discovery.js +131 -70
  250. package/dist/infra/bonjour.js +3 -3
  251. package/dist/infra/control-ui-assets.js +134 -12
  252. package/dist/infra/errors.js +12 -0
  253. package/dist/infra/exec-approvals.js +266 -57
  254. package/dist/infra/format-time/format-datetime.js +79 -0
  255. package/dist/infra/format-time/format-duration.js +81 -0
  256. package/dist/infra/format-time/format-relative.js +80 -0
  257. package/dist/infra/heartbeat-runner.js +140 -49
  258. package/dist/infra/home-dir.js +54 -0
  259. package/dist/infra/net/fetch-guard.js +122 -0
  260. package/dist/infra/net/ssrf.js +65 -29
  261. package/dist/infra/outbound/abort.js +14 -0
  262. package/dist/infra/outbound/message-action-runner.js +77 -13
  263. package/dist/infra/outbound/outbound-session.js +143 -37
  264. package/dist/infra/path-env.js +3 -3
  265. package/dist/infra/poolbot-root.js +43 -1
  266. package/dist/infra/provider-usage.fetch.minimax.js +1 -1
  267. package/dist/infra/restart.js +1 -1
  268. package/dist/infra/session-cost-usage.js +631 -41
  269. package/dist/infra/state-migrations.js +317 -47
  270. package/dist/infra/tailscale.js +1 -1
  271. package/dist/infra/update-global.js +35 -0
  272. package/dist/infra/update-runner.js +149 -43
  273. package/dist/infra/warning-filter.js +65 -0
  274. package/dist/infra/widearea-dns.js +30 -9
  275. package/dist/logging/redact-identifier.js +12 -0
  276. package/dist/macos/relay.js +2 -2
  277. package/dist/media/fetch.js +81 -58
  278. package/dist/media/input-files.js +1 -1
  279. package/dist/media/mime.js +4 -0
  280. package/dist/media/png-encode.js +74 -0
  281. package/dist/media-understanding/apply.js +403 -3
  282. package/dist/media-understanding/attachments.js +38 -27
  283. package/dist/media-understanding/defaults.js +16 -0
  284. package/dist/media-understanding/providers/deepgram/audio.js +22 -14
  285. package/dist/media-understanding/providers/google/audio.js +24 -17
  286. package/dist/media-understanding/providers/google/video.js +24 -17
  287. package/dist/media-understanding/providers/image.js +4 -4
  288. package/dist/media-understanding/providers/index.js +4 -1
  289. package/dist/media-understanding/providers/openai/audio.js +22 -14
  290. package/dist/media-understanding/providers/shared.js +16 -11
  291. package/dist/media-understanding/providers/zai/index.js +6 -0
  292. package/dist/media-understanding/runner.js +158 -90
  293. package/dist/memory/backend-config.js +207 -0
  294. package/dist/memory/batch-voyage.js +277 -0
  295. package/dist/memory/embeddings-voyage.js +75 -0
  296. package/dist/memory/embeddings.js +29 -17
  297. package/dist/memory/internal.js +101 -18
  298. package/dist/memory/manager.js +155 -48
  299. package/dist/memory/search-manager.js +173 -0
  300. package/dist/memory/session-files.js +9 -3
  301. package/dist/memory/types.js +1 -0
  302. package/dist/node-host/runner.js +36 -26
  303. package/dist/node-host/with-timeout.js +27 -0
  304. package/dist/pairing/pairing-messages.js +1 -1
  305. package/dist/plugins/commands.js +5 -1
  306. package/dist/plugins/config-state.js +86 -7
  307. package/dist/plugins/discovery.js +1 -1
  308. package/dist/plugins/install.js +2 -2
  309. package/dist/plugins/source-display.js +51 -0
  310. package/dist/plugins/update.js +1 -1
  311. package/dist/process/exec.js +20 -2
  312. package/dist/routing/resolve-route.js +12 -0
  313. package/dist/routing/session-key.js +15 -0
  314. package/dist/runtime.js +2 -0
  315. package/dist/security/audit-extra.async.js +601 -0
  316. package/dist/security/audit-extra.js +2 -830
  317. package/dist/security/audit-extra.sync.js +505 -0
  318. package/dist/security/audit.js +2 -2
  319. package/dist/security/channel-metadata.js +34 -0
  320. package/dist/security/external-content.js +88 -6
  321. package/dist/security/skill-scanner.js +330 -0
  322. package/dist/sessions/session-key-utils.js +7 -0
  323. package/dist/shared/text/reasoning-tags.js +52 -7
  324. package/dist/signal/monitor/event-handler.js +80 -1
  325. package/dist/slack/monitor/media.js +85 -15
  326. package/dist/tailscale/detect.js +145 -0
  327. package/dist/telegram/bot/helpers.js +109 -28
  328. package/dist/telegram/bot-handlers.js +144 -3
  329. package/dist/telegram/bot-message-context.js +38 -11
  330. package/dist/telegram/bot-message-dispatch.js +48 -15
  331. package/dist/telegram/bot-native-commands.js +86 -29
  332. package/dist/telegram/bot.js +30 -29
  333. package/dist/telegram/model-buttons.js +163 -0
  334. package/dist/telegram/monitor.js +110 -85
  335. package/dist/telegram/send.js +129 -47
  336. package/dist/terminal/restore.js +45 -0
  337. package/dist/test-helpers/state-dir-env.js +16 -0
  338. package/dist/test-helpers/workspace.js +11 -0
  339. package/dist/test-utils/channel-plugins.js +82 -0
  340. package/dist/test-utils/ports.js +73 -0
  341. package/dist/tts/tts.js +12 -6
  342. package/dist/tui/tui-session-actions.js +166 -54
  343. package/dist/utils/fetch-timeout.js +20 -0
  344. package/dist/utils/normalize-secret-input.js +19 -0
  345. package/dist/utils/shell-argv.js +61 -0
  346. package/dist/utils/transcript-tools.js +58 -0
  347. package/dist/utils.js +55 -14
  348. package/dist/version.js +42 -5
  349. package/dist/web/qr-image.js +1 -61
  350. package/dist/wizard/onboarding.finalize.js +7 -7
  351. package/dist/wizard/onboarding.js +3 -3
  352. package/docs/RELEASE_WORKFOTS_COMPARISON.md +3 -3
  353. package/docs/_config.yml +2 -2
  354. package/docs/_layouts/default.html +9 -9
  355. package/docs/concepts/typebox.md +1 -1
  356. package/docs/docs.json +1 -1
  357. package/docs/northflank.mdx +7 -7
  358. package/docs/railway.mdx +3 -3
  359. package/docs/render.mdx +5 -5
  360. package/docs/start/lore.md +2 -2
  361. package/extensions/bluebubbles/index.ts +2 -2
  362. package/extensions/bluebubbles/package.json +1 -1
  363. package/extensions/bluebubbles/src/accounts.ts +8 -8
  364. package/extensions/bluebubbles/src/actions.test.ts +22 -22
  365. package/extensions/bluebubbles/src/actions.ts +5 -5
  366. package/extensions/bluebubbles/src/attachments.ts +2 -2
  367. package/extensions/bluebubbles/src/channel.ts +16 -16
  368. package/extensions/bluebubbles/src/chat.ts +2 -2
  369. package/extensions/bluebubbles/src/media-send.ts +2 -2
  370. package/extensions/bluebubbles/src/monitor.test.ts +46 -46
  371. package/extensions/bluebubbles/src/monitor.ts +5 -5
  372. package/extensions/bluebubbles/src/onboarding.ts +7 -7
  373. package/extensions/bluebubbles/src/reactions.ts +2 -2
  374. package/extensions/bluebubbles/src/send.ts +2 -2
  375. package/extensions/copilot-proxy/README.md +1 -1
  376. package/extensions/copilot-proxy/package.json +1 -1
  377. package/extensions/diagnostics-otel/index.ts +2 -2
  378. package/extensions/diagnostics-otel/package.json +1 -1
  379. package/extensions/diagnostics-otel/src/service.ts +3 -3
  380. package/extensions/discord/index.ts +2 -2
  381. package/extensions/discord/package.json +1 -1
  382. package/extensions/google-antigravity-auth/README.md +1 -1
  383. package/extensions/google-antigravity-auth/index.ts +1 -1
  384. package/extensions/google-antigravity-auth/package.json +1 -1
  385. package/extensions/google-gemini-cli-auth/README.md +1 -1
  386. package/extensions/google-gemini-cli-auth/oauth.ts +1 -1
  387. package/extensions/google-gemini-cli-auth/package.json +1 -1
  388. package/extensions/googlechat/index.ts +3 -3
  389. package/extensions/googlechat/package.json +1 -1
  390. package/extensions/googlechat/src/accounts.ts +8 -8
  391. package/extensions/googlechat/src/actions.ts +6 -6
  392. package/extensions/googlechat/src/channel.ts +21 -21
  393. package/extensions/googlechat/src/monitor.ts +8 -8
  394. package/extensions/googlechat/src/onboarding.ts +10 -10
  395. package/extensions/imessage/index.ts +2 -2
  396. package/extensions/imessage/package.json +1 -1
  397. package/extensions/line/index.ts +2 -2
  398. package/extensions/line/package.json +1 -1
  399. package/extensions/line/src/card-command.ts +2 -2
  400. package/extensions/line/src/channel.logout.test.ts +4 -4
  401. package/extensions/line/src/channel.sendPayload.test.ts +8 -8
  402. package/extensions/line/src/channel.ts +3 -3
  403. package/extensions/llm-task/README.md +3 -3
  404. package/extensions/llm-task/index.ts +2 -2
  405. package/extensions/llm-task/package.json +1 -1
  406. package/extensions/llm-task/src/llm-task-tool.ts +4 -4
  407. package/extensions/lobster/README.md +6 -6
  408. package/extensions/lobster/index.ts +2 -2
  409. package/extensions/lobster/src/lobster-tool.test.ts +4 -4
  410. package/extensions/lobster/src/lobster-tool.ts +2 -2
  411. package/extensions/matrix/index.ts +2 -2
  412. package/extensions/matrix/package.json +1 -1
  413. package/extensions/matrix/src/matrix/client/config.ts +1 -1
  414. package/extensions/matrix/src/matrix/monitor/handler.ts +1 -1
  415. package/extensions/matrix/src/onboarding.ts +1 -1
  416. package/extensions/mattermost/index.ts +2 -2
  417. package/extensions/mattermost/package.json +1 -1
  418. package/extensions/mattermost/src/mattermost/accounts.ts +8 -8
  419. package/extensions/mattermost/src/mattermost/monitor-helpers.ts +5 -5
  420. package/extensions/mattermost/src/mattermost/monitor.ts +2 -2
  421. package/extensions/mattermost/src/onboarding-helpers.ts +3 -3
  422. package/extensions/mattermost/src/onboarding.ts +2 -2
  423. package/extensions/memory-core/index.ts +2 -2
  424. package/extensions/memory-core/package.json +1 -1
  425. package/extensions/memory-lancedb/index.ts +3 -3
  426. package/extensions/memory-lancedb/package.json +1 -1
  427. package/extensions/msteams/index.ts +2 -2
  428. package/extensions/msteams/package.json +1 -1
  429. package/extensions/msteams/src/channel.directory.test.ts +2 -2
  430. package/extensions/msteams/src/channel.ts +2 -2
  431. package/extensions/msteams/src/graph-upload.ts +4 -4
  432. package/extensions/msteams/src/monitor-handler.ts +2 -2
  433. package/extensions/msteams/src/monitor.ts +2 -2
  434. package/extensions/msteams/src/onboarding.ts +9 -9
  435. package/extensions/msteams/src/reply-dispatcher.ts +2 -2
  436. package/extensions/msteams/src/send-context.ts +2 -2
  437. package/extensions/msteams/src/send.ts +4 -4
  438. package/extensions/nextcloud-talk/index.ts +2 -2
  439. package/extensions/nextcloud-talk/package.json +1 -1
  440. package/extensions/nextcloud-talk/src/channel.ts +7 -7
  441. package/extensions/nextcloud-talk/src/inbound.ts +7 -7
  442. package/extensions/nextcloud-talk/src/onboarding.ts +1 -1
  443. package/extensions/nostr/README.md +2 -2
  444. package/extensions/nostr/index.ts +5 -5
  445. package/extensions/nostr/package.json +1 -1
  446. package/extensions/nostr/src/types.ts +4 -4
  447. package/extensions/open-prose/index.ts +2 -2
  448. package/extensions/qwen-portal-auth/README.md +1 -1
  449. package/extensions/signal/index.ts +2 -2
  450. package/extensions/signal/package.json +1 -1
  451. package/extensions/slack/index.ts +2 -2
  452. package/extensions/slack/package.json +1 -1
  453. package/extensions/telegram/index.ts +2 -2
  454. package/extensions/telegram/package.json +1 -1
  455. package/extensions/telegram/src/channel.ts +2 -2
  456. package/extensions/tlon/README.md +2 -2
  457. package/extensions/tlon/index.ts +2 -2
  458. package/extensions/tlon/package.json +1 -1
  459. package/extensions/tlon/src/channel.ts +13 -13
  460. package/extensions/tlon/src/monitor/index.ts +3 -3
  461. package/extensions/tlon/src/onboarding.ts +3 -3
  462. package/extensions/tlon/src/types.ts +3 -3
  463. package/extensions/twitch/README.md +1 -1
  464. package/extensions/twitch/index.ts +2 -2
  465. package/extensions/twitch/package.json +1 -1
  466. package/extensions/twitch/src/config.ts +3 -3
  467. package/extensions/twitch/src/monitor.ts +3 -3
  468. package/extensions/twitch/src/onboarding.ts +9 -9
  469. package/extensions/twitch/src/outbound.test.ts +2 -2
  470. package/extensions/twitch/src/plugin.test.ts +2 -2
  471. package/extensions/twitch/src/plugin.ts +8 -8
  472. package/extensions/twitch/src/send.test.ts +2 -2
  473. package/extensions/twitch/src/send.ts +4 -4
  474. package/extensions/twitch/src/token.test.ts +8 -8
  475. package/extensions/twitch/src/token.ts +3 -3
  476. package/extensions/twitch/src/twitch-client.ts +3 -3
  477. package/extensions/twitch/src/types.ts +3 -3
  478. package/extensions/twitch/src/utils/markdown.ts +1 -1
  479. package/extensions/voice-call/README.md +3 -3
  480. package/extensions/voice-call/package.json +1 -1
  481. package/extensions/voice-call/src/core-bridge.ts +2 -2
  482. package/extensions/voice-call/src/response-generator.ts +1 -1
  483. package/extensions/whatsapp/index.ts +2 -2
  484. package/extensions/whatsapp/package.json +1 -1
  485. package/extensions/zalo/README.md +1 -1
  486. package/extensions/zalo/index.ts +2 -2
  487. package/extensions/zalo/package.json +1 -1
  488. package/extensions/zalo/src/accounts.ts +8 -8
  489. package/extensions/zalo/src/actions.ts +4 -4
  490. package/extensions/zalo/src/channel.directory.test.ts +2 -2
  491. package/extensions/zalo/src/channel.ts +18 -18
  492. package/extensions/zalo/src/monitor.ts +9 -9
  493. package/extensions/zalo/src/monitor.webhook.test.ts +2 -2
  494. package/extensions/zalo/src/onboarding.ts +24 -24
  495. package/extensions/zalo/src/send.ts +2 -2
  496. package/extensions/zalouser/README.md +2 -2
  497. package/extensions/zalouser/index.ts +2 -2
  498. package/extensions/zalouser/package.json +1 -1
  499. package/extensions/zalouser/src/accounts.ts +9 -9
  500. package/extensions/zalouser/src/channel.ts +24 -24
  501. package/extensions/zalouser/src/monitor.ts +4 -4
  502. package/extensions/zalouser/src/onboarding.ts +28 -28
  503. package/package.json +13 -251
  504. package/skills/nano-banana-pro/scripts/generate_image.py +1 -1
  505. package/skills/tmux/scripts/find-sessions.sh +1 -1
  506. package/CHANGELOG.md +0 -102
  507. package/README-header.png +0 -0
  508. package/git-hooks/pre-commit +0 -4
  509. package/scripts/format-staged.js +0 -148
  510. package/scripts/postinstall.js +0 -300
  511. package/scripts/setup-git-hooks.js +0 -96
@@ -1,14 +1,77 @@
1
1
  import { fetchRemoteMedia } from "../../media/fetch.js";
2
2
  import { saveMediaBuffer } from "../../media/store.js";
3
+ function normalizeHostname(hostname) {
4
+ const normalized = hostname.trim().toLowerCase().replace(/\.$/, "");
5
+ if (normalized.startsWith("[") && normalized.endsWith("]")) {
6
+ return normalized.slice(1, -1);
7
+ }
8
+ return normalized;
9
+ }
10
+ function isSlackHostname(hostname) {
11
+ const normalized = normalizeHostname(hostname);
12
+ if (!normalized) {
13
+ return false;
14
+ }
15
+ // Slack-hosted files typically come from *.slack.com and redirect to Slack CDN domains.
16
+ // Include a small allowlist of known Slack domains to avoid leaking tokens if a file URL
17
+ // is ever spoofed or mishandled.
18
+ const allowedSuffixes = ["slack.com", "slack-edge.com", "slack-files.com"];
19
+ return allowedSuffixes.some((suffix) => normalized === suffix || normalized.endsWith(`.${suffix}`));
20
+ }
21
+ function assertSlackFileUrl(rawUrl) {
22
+ let parsed;
23
+ try {
24
+ parsed = new URL(rawUrl);
25
+ }
26
+ catch {
27
+ throw new Error(`Invalid Slack file URL: ${rawUrl}`);
28
+ }
29
+ if (parsed.protocol !== "https:") {
30
+ throw new Error(`Refusing Slack file URL with non-HTTPS protocol: ${parsed.protocol}`);
31
+ }
32
+ if (!isSlackHostname(parsed.hostname)) {
33
+ throw new Error(`Refusing to send Slack token to non-Slack host "${parsed.hostname}" (url: ${rawUrl})`);
34
+ }
35
+ return parsed;
36
+ }
37
+ function resolveRequestUrl(input) {
38
+ if (typeof input === "string") {
39
+ return input;
40
+ }
41
+ if (input instanceof URL) {
42
+ return input.toString();
43
+ }
44
+ if ("url" in input && typeof input.url === "string") {
45
+ return input.url;
46
+ }
47
+ throw new Error("Unsupported fetch input: expected string, URL, or Request");
48
+ }
49
+ function createSlackMediaFetch(token) {
50
+ let includeAuth = true;
51
+ return async (input, init) => {
52
+ const url = resolveRequestUrl(input);
53
+ const { headers: initHeaders, redirect: _redirect, ...rest } = init ?? {};
54
+ const headers = new Headers(initHeaders);
55
+ if (includeAuth) {
56
+ includeAuth = false;
57
+ const parsed = assertSlackFileUrl(url);
58
+ headers.set("Authorization", `Bearer ${token}`);
59
+ return fetch(parsed.href, { ...rest, headers, redirect: "manual" });
60
+ }
61
+ headers.delete("Authorization");
62
+ return fetch(url, { ...rest, headers, redirect: "manual" });
63
+ };
64
+ }
3
65
  /**
4
66
  * Fetches a URL with Authorization header, handling cross-origin redirects.
5
67
  * Node.js fetch strips Authorization headers on cross-origin redirects for security.
6
- * Slack's files.slack.com URLs redirect to CDN domains with pre-signed URLs that
7
- * don't need the Authorization header, so we handle the initial auth request manually.
68
+ * Slack's file URLs redirect to CDN domains with pre-signed URLs that don't need the
69
+ * Authorization header, so we handle the initial auth request manually.
8
70
  */
9
71
  export async function fetchWithSlackAuth(url, token) {
72
+ const parsed = assertSlackFileUrl(url);
10
73
  // Initial request with auth and manual redirect handling
11
- const initialRes = await fetch(url, {
74
+ const initialRes = await fetch(parsed.href, {
12
75
  headers: { Authorization: `Bearer ${token}` },
13
76
  redirect: "manual",
14
77
  });
@@ -22,31 +85,36 @@ export async function fetchWithSlackAuth(url, token) {
22
85
  return initialRes;
23
86
  }
24
87
  // Resolve relative URLs against the original
25
- const resolvedUrl = new URL(redirectUrl, url).toString();
88
+ const resolvedUrl = new URL(redirectUrl, parsed.href);
89
+ // Only follow safe protocols (we do NOT include Authorization on redirects).
90
+ if (resolvedUrl.protocol !== "https:") {
91
+ return initialRes;
92
+ }
26
93
  // Follow the redirect without the Authorization header
27
94
  // (Slack's CDN URLs are pre-signed and don't need it)
28
- return fetch(resolvedUrl, { redirect: "follow" });
95
+ return fetch(resolvedUrl.toString(), { redirect: "follow" });
29
96
  }
30
97
  export async function resolveSlackMedia(params) {
31
98
  const files = params.files ?? [];
32
99
  for (const file of files) {
33
100
  const url = file.url_private_download ?? file.url_private;
34
- if (!url)
101
+ if (!url) {
35
102
  continue;
103
+ }
36
104
  try {
37
- // Note: We ignore init options because fetchWithSlackAuth handles
38
- // redirect behavior specially. fetchRemoteMedia only passes the URL.
39
- const fetchImpl = (input) => {
40
- const inputUrl = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
41
- return fetchWithSlackAuth(inputUrl, params.token);
42
- };
105
+ // Note: fetchRemoteMedia calls fetchImpl(url) with the URL string today and
106
+ // handles size limits internally. Provide a fetcher that uses auth once, then lets
107
+ // the redirect chain continue without credentials.
108
+ const fetchImpl = createSlackMediaFetch(params.token);
43
109
  const fetched = await fetchRemoteMedia({
44
110
  url,
45
111
  fetchImpl,
46
112
  filePathHint: file.name,
113
+ maxBytes: params.maxBytes,
47
114
  });
48
- if (fetched.buffer.byteLength > params.maxBytes)
115
+ if (fetched.buffer.byteLength > params.maxBytes) {
49
116
  continue;
117
+ }
50
118
  const saved = await saveMediaBuffer(fetched.buffer, fetched.contentType ?? file.mimetype, "inbound", params.maxBytes);
51
119
  const label = fetched.fileName ?? file.name;
52
120
  return {
@@ -65,8 +133,9 @@ const THREAD_STARTER_CACHE = new Map();
65
133
  export async function resolveSlackThreadStarter(params) {
66
134
  const cacheKey = `${params.channelId}:${params.threadTs}`;
67
135
  const cached = THREAD_STARTER_CACHE.get(cacheKey);
68
- if (cached)
136
+ if (cached) {
69
137
  return cached;
138
+ }
70
139
  try {
71
140
  const response = (await params.client.conversations.replies({
72
141
  channel: params.channelId,
@@ -76,8 +145,9 @@ export async function resolveSlackThreadStarter(params) {
76
145
  }));
77
146
  const message = response?.messages?.[0];
78
147
  const text = (message?.text ?? "").trim();
79
- if (!message || !text)
148
+ if (!message || !text) {
80
149
  return null;
150
+ }
81
151
  const starter = {
82
152
  text,
83
153
  userId: message.user,
@@ -0,0 +1,145 @@
1
+ import { execSync } from "node:child_process";
2
+ import fs from "node:fs/promises";
3
+ import os from "node:os";
4
+ /**
5
+ * Check if Tailscale is installed by looking for the binary.
6
+ */
7
+ async function isTailscaleInstalled() {
8
+ try {
9
+ const platform = os.platform();
10
+ const possiblePaths = [];
11
+ if (platform === "linux") {
12
+ possiblePaths.push("/usr/bin/tailscale", "/usr/local/bin/tailscale");
13
+ }
14
+ else if (platform === "darwin") {
15
+ possiblePaths.push("/Applications/Tailscale.app/Contents/MacOS/Tailscale", "/usr/local/bin/tailscale");
16
+ }
17
+ else if (platform === "win32") {
18
+ possiblePaths.push("C:\\Program Files\\Tailscale\\tailscale.exe", "C:\\ProgramData\\chocolatey\\lib\\tailscale\\tools\\tailscale.exe");
19
+ }
20
+ for (const binPath of possiblePaths) {
21
+ try {
22
+ await fs.access(binPath);
23
+ return true;
24
+ }
25
+ catch {
26
+ continue;
27
+ }
28
+ }
29
+ return false;
30
+ }
31
+ catch {
32
+ return false;
33
+ }
34
+ }
35
+ /**
36
+ * Run tailscale status command and parse output.
37
+ */
38
+ async function getTailscaleStatus() {
39
+ try {
40
+ const platform = os.platform();
41
+ const binPath = platform === "win32"
42
+ ? "tailscale.exe"
43
+ : platform === "darwin"
44
+ ? "/Applications/Tailscale.app/Contents/MacOS/Tailscale"
45
+ : "tailscale";
46
+ const timeout = 5000;
47
+ const result = execSync(`"${binPath}" status --json`, {
48
+ encoding: "utf-8",
49
+ stdio: ["ignore", "pipe", "pipe"],
50
+ timeout,
51
+ });
52
+ const status = JSON.parse(result.trim());
53
+ // Extract useful info from Tailscale status JSON
54
+ const self = status.Self || {};
55
+ const backendState = status.BackendState || "";
56
+ return {
57
+ installed: true,
58
+ running: backendState === "Running",
59
+ ip: self.TailscaleIPs?.[0],
60
+ dnsName: self.DNSName,
61
+ hostname: self.HostName,
62
+ tailnet: status.MagicsockName || status.TailnetName,
63
+ online: self.Online ?? false,
64
+ canServe: true, // If running, can serve
65
+ canFunnel: self.Capabilities?.map.includes("funnel") ?? false,
66
+ };
67
+ }
68
+ catch {
69
+ return {
70
+ installed: await isTailscaleInstalled(),
71
+ running: false,
72
+ canServe: false,
73
+ canFunnel: false,
74
+ online: false,
75
+ };
76
+ }
77
+ }
78
+ /**
79
+ * Auto-detect Tailscale configuration and recommend settings.
80
+ *
81
+ * This is a SAFE detection function - it only reads status and never
82
+ * modifies configuration. Recommendations are purely advisory.
83
+ *
84
+ * @returns Tailscale auto-detection result
85
+ */
86
+ export async function detectTailscaleConfig() {
87
+ const info = {
88
+ installed: await isTailscaleInstalled(),
89
+ running: false,
90
+ canServe: false,
91
+ canFunnel: false,
92
+ online: false,
93
+ };
94
+ if (!info.installed) {
95
+ return { detected: false };
96
+ }
97
+ // Get detailed status if installed
98
+ const status = await getTailscaleStatus();
99
+ Object.assign(info, status);
100
+ // Generate recommendations based on detected state
101
+ const recommended = info.online
102
+ ? {
103
+ mode: info.canFunnel ? "funnel" : "serve",
104
+ bind: "tailnet",
105
+ port: 18789,
106
+ reason: info.canFunnel
107
+ ? "Tailscale is online and supports funnel (public access)"
108
+ : "Tailscale is online - use serve for private tailnet access",
109
+ }
110
+ : info.running
111
+ ? {
112
+ mode: "off",
113
+ bind: "loopback",
114
+ port: 18789,
115
+ reason: "Tailscale is installed but offline - use loopback bind",
116
+ }
117
+ : undefined;
118
+ return {
119
+ detected: true,
120
+ info,
121
+ recommended,
122
+ };
123
+ }
124
+ /**
125
+ * Get a human-readable summary of Tailscale detection.
126
+ */
127
+ export function formatTailscaleDetection(result) {
128
+ if (!result.detected || !result.info) {
129
+ return "Tailscale: Not installed";
130
+ }
131
+ const { info, recommended } = result;
132
+ const parts = [];
133
+ parts.push(`Tailscale: ${info.installed ? "Installed" : "Not installed"}`);
134
+ parts.push(`Status: ${info.running ? (info.online ? "Online" : "Offline") : "Not running"}`);
135
+ if (info.online && info.ip) {
136
+ parts.push(`IP: ${info.ip}`);
137
+ }
138
+ if (info.dnsName) {
139
+ parts.push(`DNS: ${info.dnsName}`);
140
+ }
141
+ if (recommended) {
142
+ parts.push(`\nRecommendation: ${recommended.mode} (${recommended.reason})`);
143
+ }
144
+ return parts.join(" | ");
145
+ }
@@ -1,22 +1,67 @@
1
1
  import { formatLocationText } from "../../channels/location.js";
2
2
  const TELEGRAM_GENERAL_TOPIC_ID = 1;
3
+ /**
4
+ * Resolve the thread ID for Telegram forum topics.
5
+ * For non-forum groups, returns undefined even if messageThreadId is present
6
+ * (reply threads in regular groups should not create separate sessions).
7
+ * For forum groups, returns the topic ID (or General topic ID=1 if unspecified).
8
+ */
3
9
  export function resolveTelegramForumThreadId(params) {
4
- if (params.isForum && params.messageThreadId == null) {
10
+ // Non-forum groups: ignore message_thread_id (reply threads are not real topics)
11
+ if (!params.isForum) {
12
+ return undefined;
13
+ }
14
+ // Forum groups: use the topic ID, defaulting to General topic
15
+ if (params.messageThreadId == null) {
5
16
  return TELEGRAM_GENERAL_TOPIC_ID;
6
17
  }
7
- return params.messageThreadId ?? undefined;
18
+ return params.messageThreadId;
19
+ }
20
+ export function resolveTelegramThreadSpec(params) {
21
+ if (params.isGroup) {
22
+ const id = resolveTelegramForumThreadId({
23
+ isForum: params.isForum,
24
+ messageThreadId: params.messageThreadId,
25
+ });
26
+ return {
27
+ id,
28
+ scope: params.isForum ? "forum" : "none",
29
+ };
30
+ }
31
+ if (params.messageThreadId == null) {
32
+ return { scope: "dm" };
33
+ }
34
+ return {
35
+ id: params.messageThreadId,
36
+ scope: "dm",
37
+ };
8
38
  }
9
39
  /**
10
40
  * Build thread params for Telegram API calls (messages, media).
11
41
  * General forum topic (id=1) must be treated like a regular supergroup send:
12
42
  * Telegram rejects sendMessage/sendMedia with message_thread_id=1 ("thread not found").
43
+ *
44
+ * Accepts either a TelegramThreadSpec (preferred) or a raw messageThreadId number
45
+ * for backward compatibility with callers that haven't migrated yet.
13
46
  */
14
- export function buildTelegramThreadParams(messageThreadId) {
15
- if (messageThreadId == null) {
47
+ export function buildTelegramThreadParams(threadOrId) {
48
+ if (threadOrId == null) {
49
+ return undefined;
50
+ }
51
+ // Legacy call: raw number
52
+ if (typeof threadOrId === "number") {
53
+ const normalized = Math.trunc(threadOrId);
54
+ if (normalized === TELEGRAM_GENERAL_TOPIC_ID) {
55
+ return undefined;
56
+ }
57
+ return { message_thread_id: normalized };
58
+ }
59
+ // New call: TelegramThreadSpec
60
+ if (!threadOrId.id) {
16
61
  return undefined;
17
62
  }
18
- const normalized = Math.trunc(messageThreadId);
19
- if (normalized === TELEGRAM_GENERAL_TOPIC_ID) {
63
+ const normalized = Math.trunc(threadOrId.id);
64
+ if (normalized === TELEGRAM_GENERAL_TOPIC_ID && threadOrId.scope === "forum") {
20
65
  return undefined;
21
66
  }
22
67
  return { message_thread_id: normalized };
@@ -43,6 +88,19 @@ export function buildTelegramGroupPeerId(chatId, messageThreadId) {
43
88
  export function buildTelegramGroupFrom(chatId, messageThreadId) {
44
89
  return `telegram:group:${buildTelegramGroupPeerId(chatId, messageThreadId)}`;
45
90
  }
91
+ /**
92
+ * Build parentPeer for forum topic binding inheritance.
93
+ * When a message comes from a forum topic, the peer ID includes the topic suffix
94
+ * (e.g., `-1001234567890:topic:99`). To allow bindings configured for the base
95
+ * group ID to match, we provide the parent group as `parentPeer` so the routing
96
+ * layer can fall back to it when the exact peer doesn't match.
97
+ */
98
+ export function buildTelegramParentPeer(params) {
99
+ if (!params.isGroup || params.resolvedThreadId == null) {
100
+ return undefined;
101
+ }
102
+ return { kind: "group", id: String(params.chatId) };
103
+ }
46
104
  export function buildSenderName(msg) {
47
105
  const name = [msg.from?.first_name, msg.from?.last_name].filter(Boolean).join(" ").trim() ||
48
106
  msg.from?.username;
@@ -93,7 +151,7 @@ export function expandTextLinks(text, entities) {
93
151
  return text;
94
152
  const textLinks = entities
95
153
  .filter((entity) => entity.type === "text_link" && Boolean(entity.url))
96
- .sort((a, b) => b.offset - a.offset);
154
+ .toSorted((a, b) => b.offset - a.offset);
97
155
  if (textLinks.length === 0)
98
156
  return text;
99
157
  let result = text;
@@ -115,33 +173,53 @@ export function resolveTelegramReplyId(raw) {
115
173
  }
116
174
  export function describeReplyTarget(msg) {
117
175
  const reply = msg.reply_to_message;
118
- if (!reply)
119
- return null;
120
- const replyBody = (reply.text ?? reply.caption ?? "").trim();
121
- let body = replyBody;
122
- if (!body) {
123
- if (reply.photo)
124
- body = "<media:image>";
125
- else if (reply.video)
126
- body = "<media:video>";
127
- else if (reply.audio || reply.voice)
128
- body = "<media:audio>";
129
- else if (reply.document)
130
- body = "<media:document>";
131
- else {
132
- const locationData = extractTelegramLocation(reply);
133
- if (locationData)
134
- body = formatLocationText(locationData);
176
+ const externalReply = msg
177
+ .external_reply;
178
+ const quoteText = msg.quote?.text ??
179
+ externalReply?.quote?.text;
180
+ let body = "";
181
+ let kind = "reply";
182
+ if (typeof quoteText === "string") {
183
+ body = quoteText.trim();
184
+ if (body) {
185
+ kind = "quote";
135
186
  }
136
187
  }
137
- if (!body)
188
+ const replyLike = reply ?? externalReply;
189
+ if (!body && replyLike) {
190
+ const replyBody = (replyLike.text ?? replyLike.caption ?? "").trim();
191
+ body = replyBody;
192
+ if (!body) {
193
+ if (replyLike.photo) {
194
+ body = "<media:image>";
195
+ }
196
+ else if (replyLike.video) {
197
+ body = "<media:video>";
198
+ }
199
+ else if (replyLike.audio || replyLike.voice) {
200
+ body = "<media:audio>";
201
+ }
202
+ else if (replyLike.document) {
203
+ body = "<media:document>";
204
+ }
205
+ else {
206
+ const locationData = extractTelegramLocation(replyLike);
207
+ if (locationData) {
208
+ body = formatLocationText(locationData);
209
+ }
210
+ }
211
+ }
212
+ }
213
+ if (!body) {
138
214
  return null;
139
- const sender = buildSenderName(reply);
140
- const senderLabel = sender ? `${sender}` : "unknown sender";
215
+ }
216
+ const sender = replyLike ? buildSenderName(replyLike) : undefined;
217
+ const senderLabel = sender ?? "unknown sender";
141
218
  return {
142
- id: reply.message_id ? String(reply.message_id) : undefined,
219
+ id: replyLike?.message_id ? String(replyLike.message_id) : undefined,
143
220
  sender: senderLabel,
144
221
  body,
222
+ kind,
145
223
  };
146
224
  }
147
225
  function normalizeForwardedUserLabel(user) {
@@ -191,6 +269,7 @@ function buildForwardedContextFromChat(params) {
191
269
  return null;
192
270
  const signature = params.signature?.trim() || undefined;
193
271
  const from = signature ? `${display} (${signature})` : display;
272
+ const chatType = (params.chat.type?.trim() || undefined);
194
273
  return {
195
274
  from,
196
275
  date: params.date,
@@ -199,6 +278,8 @@ function buildForwardedContextFromChat(params) {
199
278
  fromUsername: username,
200
279
  fromTitle: title,
201
280
  fromSignature: signature,
281
+ fromChatType: chatType,
282
+ fromMessageId: params.messageId,
202
283
  };
203
284
  }
204
285
  function resolveForwardOrigin(origin, signature) {
@@ -2,21 +2,27 @@
2
2
  import { hasControlCommand } from "../auto-reply/command-detection.js";
3
3
  import { createInboundDebouncer, resolveInboundDebounceMs, } from "../auto-reply/inbound-debounce.js";
4
4
  import { buildCommandsPaginationKeyboard } from "../auto-reply/reply/commands-info.js";
5
+ import { buildModelsProviderData } from "../auto-reply/reply/commands-models.js";
6
+ import { resolveStoredModelOverride } from "../auto-reply/reply/model-selection.js";
5
7
  import { buildCommandsMessagePaginated } from "../auto-reply/status.js";
6
8
  import { listSkillCommandsForAgents } from "../auto-reply/skill-commands.js";
7
9
  import { resolveDefaultAgentId } from "../agents/agent-scope.js";
8
10
  import { loadConfig } from "../config/config.js";
11
+ import { loadSessionStore, resolveStorePath } from "../config/sessions.js";
12
+ import { resolveAgentRoute } from "../routing/resolve-route.js";
13
+ import { resolveThreadSessionKeys } from "../routing/session-key.js";
9
14
  import { writeConfigFile } from "../config/io.js";
10
15
  import { danger, logVerbose, warn } from "../globals.js";
11
16
  import { resolveMedia } from "./bot/delivery.js";
12
17
  import { withTelegramApiErrorLogging } from "./api-logging.js";
13
- import { resolveTelegramForumThreadId } from "./bot/helpers.js";
18
+ import { buildTelegramGroupPeerId, buildTelegramParentPeer, resolveTelegramForumThreadId, } from "./bot/helpers.js";
14
19
  import { firstDefined, isSenderAllowed, normalizeAllowFromWithStore } from "./bot-access.js";
15
20
  import { MEDIA_GROUP_TIMEOUT_MS } from "./bot-updates.js";
16
21
  import { migrateTelegramGroupConfig } from "./group-migration.js";
17
22
  import { resolveTelegramInlineButtonsScope } from "./inline-buttons.js";
18
23
  import { readTelegramAllowFromStore } from "./pairing-store.js";
19
24
  import { resolveChannelConfigWrites } from "../channels/plugins/config-writes.js";
25
+ import { buildModelsKeyboard, buildProviderKeyboard, calculateTotalPages, getModelsPageSize, parseModelCallbackData, } from "./model-buttons.js";
20
26
  import { buildInlineKeyboard } from "./send.js";
21
27
  export const registerTelegramHandlers = ({ cfg, accountId, bot, opts, runtime, mediaMaxBytes, telegramCfg, groupAllowFrom, resolveGroupPolicy, resolveTelegramGroupConfig, shouldSkipUpdate, processMessage, logger, }) => {
22
28
  const TELEGRAM_TEXT_FRAGMENT_START_THRESHOLD_CHARS = 4000;
@@ -72,6 +78,57 @@ export const registerTelegramHandlers = ({ cfg, accountId, bot, opts, runtime, m
72
78
  runtime.error?.(danger(`telegram debounce flush failed: ${String(err)}`));
73
79
  },
74
80
  });
81
+ const resolveTelegramSessionModel = (params) => {
82
+ const resolvedThreadId = params.resolvedThreadId ??
83
+ resolveTelegramForumThreadId({
84
+ isForum: params.isForum,
85
+ messageThreadId: params.messageThreadId,
86
+ });
87
+ const peerId = params.isGroup
88
+ ? buildTelegramGroupPeerId(params.chatId, resolvedThreadId)
89
+ : String(params.chatId);
90
+ const parentPeer = buildTelegramParentPeer({
91
+ isGroup: params.isGroup,
92
+ resolvedThreadId,
93
+ chatId: params.chatId,
94
+ });
95
+ const route = resolveAgentRoute({
96
+ cfg,
97
+ channel: "telegram",
98
+ accountId,
99
+ peer: {
100
+ kind: params.isGroup ? "group" : "dm",
101
+ id: peerId,
102
+ },
103
+ parentPeer,
104
+ });
105
+ const baseSessionKey = route.sessionKey;
106
+ const dmThreadId = !params.isGroup ? params.messageThreadId : undefined;
107
+ const threadKeys = dmThreadId != null
108
+ ? resolveThreadSessionKeys({ baseSessionKey, threadId: String(dmThreadId) })
109
+ : null;
110
+ const sessionKey = threadKeys?.sessionKey ?? baseSessionKey;
111
+ const storePath = resolveStorePath(cfg.session?.store, { agentId: route.agentId });
112
+ const store = loadSessionStore(storePath);
113
+ const entry = store[sessionKey];
114
+ const storedOverride = resolveStoredModelOverride({
115
+ sessionEntry: entry,
116
+ sessionStore: store,
117
+ sessionKey,
118
+ });
119
+ if (storedOverride) {
120
+ return storedOverride.provider
121
+ ? `${storedOverride.provider}/${storedOverride.model}`
122
+ : storedOverride.model;
123
+ }
124
+ const provider = entry?.modelProvider?.trim();
125
+ const model = entry?.model?.trim();
126
+ if (provider && model) {
127
+ return `${provider}/${model}`;
128
+ }
129
+ const modelCfg = cfg.agents?.defaults?.model;
130
+ return typeof modelCfg === "string" ? modelCfg : modelCfg?.primary;
131
+ };
75
132
  const processMediaGroup = async (entry) => {
76
133
  try {
77
134
  entry.messages.sort((a, b) => a.msg.message_id - b.msg.message_id);
@@ -205,7 +262,7 @@ export const registerTelegramHandlers = ({ cfg, accountId, bot, opts, runtime, m
205
262
  }
206
263
  }
207
264
  const defaultGroupPolicy = cfg.channels?.defaults?.groupPolicy;
208
- const groupPolicy = telegramCfg.groupPolicy ?? defaultGroupPolicy ?? "open";
265
+ const groupPolicy = firstDefined(topicConfig?.groupPolicy, groupConfig?.groupPolicy, telegramCfg.groupPolicy, defaultGroupPolicy, "open");
209
266
  if (groupPolicy === "disabled") {
210
267
  logVerbose(`Blocked telegram group message (groupPolicy: disabled)`);
211
268
  return;
@@ -293,6 +350,90 @@ export const registerTelegramHandlers = ({ cfg, accountId, bot, opts, runtime, m
293
350
  }
294
351
  return;
295
352
  }
353
+ // Model selection callback handler (mdl_prov, mdl_list_*, mdl_sel_*, mdl_back)
354
+ const modelCallback = parseModelCallbackData(data);
355
+ if (modelCallback) {
356
+ const modelData = await buildModelsProviderData(cfg);
357
+ const { byProvider, providers } = modelData;
358
+ const editMessageWithButtons = async (text, buttons) => {
359
+ const keyboard = buildInlineKeyboard(buttons);
360
+ try {
361
+ await bot.api.editMessageText(callbackMessage.chat.id, callbackMessage.message_id, text, keyboard ? { reply_markup: keyboard } : undefined);
362
+ }
363
+ catch (editErr) {
364
+ const errStr = String(editErr);
365
+ if (!errStr.includes("message is not modified")) {
366
+ throw editErr;
367
+ }
368
+ }
369
+ };
370
+ if (modelCallback.type === "providers" || modelCallback.type === "back") {
371
+ if (providers.length === 0) {
372
+ await editMessageWithButtons("No providers available.", []);
373
+ return;
374
+ }
375
+ const providerInfos = providers.map((p) => ({
376
+ id: p,
377
+ count: byProvider.get(p)?.size ?? 0,
378
+ }));
379
+ const buttons = buildProviderKeyboard(providerInfos);
380
+ await editMessageWithButtons("Select a provider:", buttons);
381
+ return;
382
+ }
383
+ if (modelCallback.type === "list") {
384
+ const { provider, page } = modelCallback;
385
+ const modelSet = byProvider.get(provider);
386
+ if (!modelSet || modelSet.size === 0) {
387
+ const providerInfos = providers.map((p) => ({
388
+ id: p,
389
+ count: byProvider.get(p)?.size ?? 0,
390
+ }));
391
+ const buttons = buildProviderKeyboard(providerInfos);
392
+ await editMessageWithButtons(`Unknown provider: ${provider}\n\nSelect a provider:`, buttons);
393
+ return;
394
+ }
395
+ const models = [...modelSet].toSorted();
396
+ const pageSize = getModelsPageSize();
397
+ const totalPages = calculateTotalPages(models.length, pageSize);
398
+ const safePage = Math.max(1, Math.min(page, totalPages));
399
+ const currentModel = resolveTelegramSessionModel({
400
+ chatId,
401
+ isGroup,
402
+ isForum,
403
+ messageThreadId,
404
+ resolvedThreadId,
405
+ });
406
+ const buttons = buildModelsKeyboard({
407
+ provider,
408
+ models,
409
+ currentModel,
410
+ currentPage: safePage,
411
+ totalPages,
412
+ pageSize,
413
+ });
414
+ const text = `Models (${provider}) — ${models.length} available`;
415
+ await editMessageWithButtons(text, buttons);
416
+ return;
417
+ }
418
+ if (modelCallback.type === "select") {
419
+ const { provider, model } = modelCallback;
420
+ const syntheticMessage = {
421
+ ...callbackMessage,
422
+ from: callback.from,
423
+ text: `/model ${provider}/${model}`,
424
+ caption: undefined,
425
+ caption_entities: undefined,
426
+ entities: undefined,
427
+ };
428
+ const getFile = typeof ctx.getFile === "function" ? ctx.getFile.bind(ctx) : async () => ({});
429
+ await processMessage({ message: syntheticMessage, me: ctx.me, getFile }, [], storeAllowFrom, {
430
+ forceWasMentioned: true,
431
+ messageIdOverride: callback.id,
432
+ });
433
+ return;
434
+ }
435
+ return;
436
+ }
296
437
  const syntheticMessage = {
297
438
  ...callbackMessage,
298
439
  from: callback.from,
@@ -403,7 +544,7 @@ export const registerTelegramHandlers = ({ cfg, accountId, bot, opts, runtime, m
403
544
  // - "disabled": block all group messages entirely
404
545
  // - "allowlist": only allow group messages from senders in groupAllowFrom/allowFrom
405
546
  const defaultGroupPolicy = cfg.channels?.defaults?.groupPolicy;
406
- const groupPolicy = telegramCfg.groupPolicy ?? defaultGroupPolicy ?? "open";
547
+ const groupPolicy = firstDefined(topicConfig?.groupPolicy, groupConfig?.groupPolicy, telegramCfg.groupPolicy, defaultGroupPolicy, "open");
407
548
  if (groupPolicy === "disabled") {
408
549
  logVerbose(`Blocked telegram group message (groupPolicy: disabled)`);
409
550
  return;