@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
@@ -5,23 +5,23 @@ import { resolveUserTimezone } from "../agents/date-time.js";
5
5
  import { resolveEffectiveMessagesConfig } from "../agents/identity.js";
6
6
  import { DEFAULT_HEARTBEAT_FILENAME } from "../agents/workspace.js";
7
7
  import { DEFAULT_HEARTBEAT_ACK_MAX_CHARS, DEFAULT_HEARTBEAT_EVERY, isHeartbeatContentEffectivelyEmpty, resolveHeartbeatPrompt as resolveHeartbeatPromptText, stripHeartbeatToken, } from "../auto-reply/heartbeat.js";
8
- import { HEARTBEAT_TOKEN } from "../auto-reply/tokens.js";
9
8
  import { getReplyFromConfig } from "../auto-reply/reply.js";
9
+ import { HEARTBEAT_TOKEN } from "../auto-reply/tokens.js";
10
10
  import { getChannelPlugin } from "../channels/plugins/index.js";
11
11
  import { parseDurationMs } from "../cli/parse-duration.js";
12
12
  import { loadConfig } from "../config/config.js";
13
13
  import { canonicalizeMainSessionAlias, loadSessionStore, resolveAgentIdFromSessionKey, resolveAgentMainSessionKey, resolveStorePath, saveSessionStore, updateSessionStore, } from "../config/sessions.js";
14
- import { formatErrorMessage } from "../infra/errors.js";
15
- import { peekSystemEvents } from "../infra/system-events.js";
16
14
  import { createSubsystemLogger } from "../logging/subsystem.js";
17
15
  import { getQueueSize } from "../process/command-queue.js";
18
- import { defaultRuntime } from "../runtime.js";
19
16
  import { normalizeAgentId, toAgentStoreSessionKey } from "../routing/session-key.js";
17
+ import { defaultRuntime } from "../runtime.js";
18
+ import { formatErrorMessage } from "./errors.js";
20
19
  import { emitHeartbeatEvent, resolveIndicatorType } from "./heartbeat-events.js";
21
20
  import { resolveHeartbeatVisibility } from "./heartbeat-visibility.js";
22
21
  import { requestHeartbeatNow, setHeartbeatWakeHandler, } from "./heartbeat-wake.js";
23
22
  import { deliverOutboundPayloads } from "./outbound/deliver.js";
24
23
  import { resolveHeartbeatDeliveryTarget, resolveHeartbeatSenderContext, } from "./outbound/targets.js";
24
+ import { peekSystemEvents } from "./system-events.js";
25
25
  const log = createSubsystemLogger("gateway/heartbeat");
26
26
  let heartbeatsEnabled = true;
27
27
  export function setHeartbeatsEnabled(enabled) {
@@ -35,6 +35,11 @@ const ACTIVE_HOURS_TIME_PATTERN = /^([01]\d|2[0-3]|24):([0-5]\d)$/;
35
35
  const EXEC_EVENT_PROMPT = "An async command you ran earlier has completed. The result is shown in the system messages above. " +
36
36
  "Please relay the command output to the user in a helpful way. If the command succeeded, share the relevant output. " +
37
37
  "If it failed, explain what went wrong.";
38
+ // Prompt used when a scheduled cron job has fired and injected a system event.
39
+ // This overrides the standard heartbeat prompt so the model relays the scheduled
40
+ // reminder instead of responding with "HEARTBEAT_OK".
41
+ const CRON_EVENT_PROMPT = "A scheduled reminder has been triggered. The reminder message is shown in the system messages above. " +
42
+ "Please relay this reminder to the user in a helpful and friendly way.";
38
43
  function resolveActiveHoursTimezone(cfg, raw) {
39
44
  const trimmed = raw?.trim();
40
45
  if (!trimmed || trimmed === "user") {
@@ -53,16 +58,19 @@ function resolveActiveHoursTimezone(cfg, raw) {
53
58
  }
54
59
  }
55
60
  function parseActiveHoursTime(opts, raw) {
56
- if (!raw || !ACTIVE_HOURS_TIME_PATTERN.test(raw))
61
+ if (!raw || !ACTIVE_HOURS_TIME_PATTERN.test(raw)) {
57
62
  return null;
63
+ }
58
64
  const [hourStr, minuteStr] = raw.split(":");
59
65
  const hour = Number(hourStr);
60
66
  const minute = Number(minuteStr);
61
- if (!Number.isFinite(hour) || !Number.isFinite(minute))
67
+ if (!Number.isFinite(hour) || !Number.isFinite(minute)) {
62
68
  return null;
69
+ }
63
70
  if (hour === 24) {
64
- if (!opts.allow24 || minute !== 0)
71
+ if (!opts.allow24 || minute !== 0) {
65
72
  return null;
73
+ }
66
74
  return 24 * 60;
67
75
  }
68
76
  return hour * 60 + minute;
@@ -77,13 +85,15 @@ function resolveMinutesInTimeZone(nowMs, timeZone) {
77
85
  }).formatToParts(new Date(nowMs));
78
86
  const map = {};
79
87
  for (const part of parts) {
80
- if (part.type !== "literal")
88
+ if (part.type !== "literal") {
81
89
  map[part.type] = part.value;
90
+ }
82
91
  }
83
92
  const hour = Number(map.hour);
84
93
  const minute = Number(map.minute);
85
- if (!Number.isFinite(hour) || !Number.isFinite(minute))
94
+ if (!Number.isFinite(hour) || !Number.isFinite(minute)) {
86
95
  return null;
96
+ }
87
97
  return hour * 60 + minute;
88
98
  }
89
99
  catch {
@@ -92,18 +102,22 @@ function resolveMinutesInTimeZone(nowMs, timeZone) {
92
102
  }
93
103
  function isWithinActiveHours(cfg, heartbeat, nowMs) {
94
104
  const active = heartbeat?.activeHours;
95
- if (!active)
105
+ if (!active) {
96
106
  return true;
107
+ }
97
108
  const startMin = parseActiveHoursTime({ allow24: false }, active.start);
98
109
  const endMin = parseActiveHoursTime({ allow24: true }, active.end);
99
- if (startMin === null || endMin === null)
110
+ if (startMin === null || endMin === null) {
100
111
  return true;
101
- if (startMin === endMin)
112
+ }
113
+ if (startMin === endMin) {
102
114
  return true;
115
+ }
103
116
  const timeZone = resolveActiveHoursTimezone(cfg, active.timezone);
104
117
  const currentMin = resolveMinutesInTimeZone(nowMs ?? Date.now(), timeZone);
105
- if (currentMin === null)
118
+ if (currentMin === null) {
106
119
  return true;
120
+ }
107
121
  if (endMin > startMin) {
108
122
  return currentMin >= startMin && currentMin < endMin;
109
123
  }
@@ -124,11 +138,13 @@ export function isHeartbeatEnabledForAgent(cfg, agentId) {
124
138
  }
125
139
  function resolveHeartbeatConfig(cfg, agentId) {
126
140
  const defaults = cfg.agents?.defaults?.heartbeat;
127
- if (!agentId)
141
+ if (!agentId) {
128
142
  return defaults;
143
+ }
129
144
  const overrides = resolveAgentConfig(cfg, agentId)?.heartbeat;
130
- if (!defaults && !overrides)
145
+ if (!defaults && !overrides) {
131
146
  return overrides;
147
+ }
132
148
  return { ...defaults, ...overrides };
133
149
  }
134
150
  export function resolveHeartbeatSummaryForAgent(cfg, agentId) {
@@ -185,11 +201,13 @@ export function resolveHeartbeatIntervalMs(cfg, overrideEvery, heartbeat) {
185
201
  heartbeat?.every ??
186
202
  cfg.agents?.defaults?.heartbeat?.every ??
187
203
  DEFAULT_HEARTBEAT_EVERY;
188
- if (!raw)
204
+ if (!raw) {
189
205
  return null;
206
+ }
190
207
  const trimmed = String(raw).trim();
191
- if (!trimmed)
208
+ if (!trimmed) {
192
209
  return null;
210
+ }
193
211
  let ms;
194
212
  try {
195
213
  ms = parseDurationMs(trimmed, { defaultUnit: "m" });
@@ -197,8 +215,9 @@ export function resolveHeartbeatIntervalMs(cfg, overrideEvery, heartbeat) {
197
215
  catch {
198
216
  return null;
199
217
  }
200
- if (ms <= 0)
218
+ if (ms <= 0) {
201
219
  return null;
220
+ }
202
221
  return ms;
203
222
  }
204
223
  export function resolveHeartbeatPrompt(cfg, heartbeat) {
@@ -215,7 +234,9 @@ function resolveHeartbeatSession(cfg, agentId, heartbeat) {
215
234
  const resolvedAgentId = normalizeAgentId(agentId ?? resolveDefaultAgentId(cfg));
216
235
  const mainSessionKey = scope === "global" ? "global" : resolveAgentMainSessionKey({ cfg, agentId: resolvedAgentId });
217
236
  const storeAgentId = scope === "global" ? resolveDefaultAgentId(cfg) : resolvedAgentId;
218
- const storePath = resolveStorePath(sessionCfg?.store, { agentId: storeAgentId });
237
+ const storePath = resolveStorePath(sessionCfg?.store, {
238
+ agentId: storeAgentId,
239
+ });
219
240
  const store = loadSessionStore(storePath);
220
241
  const mainEntry = store[mainSessionKey];
221
242
  if (scope === "global") {
@@ -242,20 +263,28 @@ function resolveHeartbeatSession(cfg, agentId, heartbeat) {
242
263
  if (canonical !== "global") {
243
264
  const sessionAgentId = resolveAgentIdFromSessionKey(canonical);
244
265
  if (sessionAgentId === normalizeAgentId(resolvedAgentId)) {
245
- return { sessionKey: canonical, storePath, store, entry: store[canonical] };
266
+ return {
267
+ sessionKey: canonical,
268
+ storePath,
269
+ store,
270
+ entry: store[canonical],
271
+ };
246
272
  }
247
273
  }
248
274
  return { sessionKey: mainSessionKey, storePath, store, entry: mainEntry };
249
275
  }
250
276
  function resolveHeartbeatReplyPayload(replyResult) {
251
- if (!replyResult)
277
+ if (!replyResult) {
252
278
  return undefined;
253
- if (!Array.isArray(replyResult))
279
+ }
280
+ if (!Array.isArray(replyResult)) {
254
281
  return replyResult;
282
+ }
255
283
  for (let idx = replyResult.length - 1; idx >= 0; idx -= 1) {
256
284
  const payload = replyResult[idx];
257
- if (!payload)
285
+ if (!payload) {
258
286
  continue;
287
+ }
259
288
  if (payload.text || payload.mediaUrl || (payload.mediaUrls && payload.mediaUrls.length > 0)) {
260
289
  return payload;
261
290
  }
@@ -271,22 +300,27 @@ function resolveHeartbeatReasoningPayloads(replyResult) {
271
300
  }
272
301
  async function restoreHeartbeatUpdatedAt(params) {
273
302
  const { storePath, sessionKey, updatedAt } = params;
274
- if (typeof updatedAt !== "number")
303
+ if (typeof updatedAt !== "number") {
275
304
  return;
305
+ }
276
306
  const store = loadSessionStore(storePath);
277
307
  const entry = store[sessionKey];
278
- if (!entry)
308
+ if (!entry) {
279
309
  return;
310
+ }
280
311
  const nextUpdatedAt = Math.max(entry.updatedAt ?? 0, updatedAt);
281
- if (entry.updatedAt === nextUpdatedAt)
312
+ if (entry.updatedAt === nextUpdatedAt) {
282
313
  return;
314
+ }
283
315
  await updateSessionStore(storePath, (nextStore) => {
284
316
  const nextEntry = nextStore[sessionKey] ?? entry;
285
- if (!nextEntry)
317
+ if (!nextEntry) {
286
318
  return;
319
+ }
287
320
  const resolvedUpdatedAt = Math.max(nextEntry.updatedAt ?? 0, updatedAt);
288
- if (nextEntry.updatedAt === resolvedUpdatedAt)
321
+ if (nextEntry.updatedAt === resolvedUpdatedAt) {
289
322
  return;
323
+ }
290
324
  nextStore[sessionKey] = { ...nextEntry, updatedAt: resolvedUpdatedAt };
291
325
  });
292
326
  }
@@ -332,13 +366,17 @@ export async function runHeartbeatOnce(opts) {
332
366
  }
333
367
  // Skip heartbeat if HEARTBEAT.md exists but has no actionable content.
334
368
  // This saves API calls/costs when the file is effectively empty (only comments/headers).
335
- // EXCEPTION: Don't skip for exec events - they have pending system events to process.
369
+ // EXCEPTION: Don't skip for exec events or cron events - they have pending system events
370
+ // to process regardless of HEARTBEAT.md content.
336
371
  const isExecEventReason = opts.reason === "exec-event";
372
+ const isCronEventReason = Boolean(opts.reason?.startsWith("cron:"));
337
373
  const workspaceDir = resolveAgentWorkspaceDir(cfg, agentId);
338
374
  const heartbeatFilePath = path.join(workspaceDir, DEFAULT_HEARTBEAT_FILENAME);
339
375
  try {
340
376
  const heartbeatFileContent = await fs.readFile(heartbeatFilePath, "utf-8");
341
- if (isHeartbeatContentEffectivelyEmpty(heartbeatFileContent) && !isExecEventReason) {
377
+ if (isHeartbeatContentEffectivelyEmpty(heartbeatFileContent) &&
378
+ !isExecEventReason &&
379
+ !isCronEventReason) {
342
380
  emitHeartbeatEvent({
343
381
  status: "skipped",
344
382
  reason: "empty-heartbeat-file",
@@ -354,6 +392,20 @@ export async function runHeartbeatOnce(opts) {
354
392
  const { entry, sessionKey, storePath } = resolveHeartbeatSession(cfg, agentId, heartbeat);
355
393
  const previousUpdatedAt = entry?.updatedAt;
356
394
  const delivery = resolveHeartbeatDeliveryTarget({ cfg, entry, heartbeat });
395
+ const heartbeatAccountId = heartbeat?.accountId?.trim();
396
+ if (delivery.reason === "unknown-account") {
397
+ log.warn("heartbeat: unknown accountId", {
398
+ accountId: delivery.accountId ?? heartbeatAccountId ?? null,
399
+ target: heartbeat?.target ?? "last",
400
+ });
401
+ }
402
+ else if (heartbeatAccountId) {
403
+ log.info("heartbeat: using explicit accountId", {
404
+ accountId: delivery.accountId ?? heartbeatAccountId,
405
+ target: heartbeat?.target ?? "last",
406
+ channel: delivery.channel,
407
+ });
408
+ }
357
409
  const visibility = delivery.channel !== "none"
358
410
  ? resolveHeartbeatVisibility({
359
411
  cfg,
@@ -362,19 +414,28 @@ export async function runHeartbeatOnce(opts) {
362
414
  })
363
415
  : { showOk: false, showAlerts: true, useIndicator: true };
364
416
  const { sender } = resolveHeartbeatSenderContext({ cfg, entry, delivery });
365
- const responsePrefix = resolveEffectiveMessagesConfig(cfg, agentId).responsePrefix;
366
- // Check if this is an exec event with pending exec completion system events.
417
+ const responsePrefix = resolveEffectiveMessagesConfig(cfg, agentId, {
418
+ channel: delivery.channel !== "none" ? delivery.channel : undefined,
419
+ accountId: delivery.accountId,
420
+ }).responsePrefix;
421
+ // Check if this is an exec event or cron event with pending system events.
367
422
  // If so, use a specialized prompt that instructs the model to relay the result
368
423
  // instead of the standard heartbeat prompt with "reply HEARTBEAT_OK".
369
424
  const isExecEvent = opts.reason === "exec-event";
370
- const pendingEvents = isExecEvent ? peekSystemEvents(sessionKey) : [];
425
+ const isCronEvent = Boolean(opts.reason?.startsWith("cron:"));
426
+ const pendingEvents = isExecEvent || isCronEvent ? peekSystemEvents(sessionKey) : [];
371
427
  const hasExecCompletion = pendingEvents.some((evt) => evt.includes("Exec finished"));
372
- const prompt = hasExecCompletion ? EXEC_EVENT_PROMPT : resolveHeartbeatPrompt(cfg, heartbeat);
428
+ const hasCronEvents = isCronEvent && pendingEvents.length > 0;
429
+ const prompt = hasExecCompletion
430
+ ? EXEC_EVENT_PROMPT
431
+ : hasCronEvents
432
+ ? CRON_EVENT_PROMPT
433
+ : resolveHeartbeatPrompt(cfg, heartbeat);
373
434
  const ctx = {
374
435
  Body: prompt,
375
436
  From: sender,
376
437
  To: sender,
377
- Provider: hasExecCompletion ? "exec-event" : "heartbeat",
438
+ Provider: hasExecCompletion ? "exec-event" : hasCronEvents ? "cron-event" : "heartbeat",
378
439
  SessionKey: sessionKey,
379
440
  };
380
441
  if (!visibility.showAlerts && !visibility.showOk && !visibility.useIndicator) {
@@ -383,14 +444,16 @@ export async function runHeartbeatOnce(opts) {
383
444
  reason: "alerts-disabled",
384
445
  durationMs: Date.now() - startedAt,
385
446
  channel: delivery.channel !== "none" ? delivery.channel : undefined,
447
+ accountId: delivery.accountId,
386
448
  });
387
449
  return { status: "skipped", reason: "alerts-disabled" };
388
450
  }
389
451
  const heartbeatOkText = responsePrefix ? `${responsePrefix} ${HEARTBEAT_TOKEN}` : HEARTBEAT_TOKEN;
390
452
  const canAttemptHeartbeatOk = Boolean(visibility.showOk && delivery.channel !== "none" && delivery.to);
391
453
  const maybeSendHeartbeatOk = async () => {
392
- if (!canAttemptHeartbeatOk || delivery.channel === "none" || !delivery.to)
454
+ if (!canAttemptHeartbeatOk || delivery.channel === "none" || !delivery.to) {
393
455
  return false;
456
+ }
394
457
  const heartbeatPlugin = getChannelPlugin(delivery.channel);
395
458
  if (heartbeatPlugin?.heartbeat?.checkReady) {
396
459
  const readiness = await heartbeatPlugin.heartbeat.checkReady({
@@ -398,8 +461,9 @@ export async function runHeartbeatOnce(opts) {
398
461
  accountId: delivery.accountId,
399
462
  deps: opts.deps,
400
463
  });
401
- if (!readiness.ok)
464
+ if (!readiness.ok) {
402
465
  return false;
466
+ }
403
467
  }
404
468
  await deliverOutboundPayloads({
405
469
  cfg,
@@ -431,6 +495,7 @@ export async function runHeartbeatOnce(opts) {
431
495
  reason: opts.reason,
432
496
  durationMs: Date.now() - startedAt,
433
497
  channel: delivery.channel !== "none" ? delivery.channel : undefined,
498
+ accountId: delivery.accountId,
434
499
  silent: !okSent,
435
500
  indicatorType: visibility.useIndicator ? resolveIndicatorType("ok-empty") : undefined,
436
501
  });
@@ -462,6 +527,7 @@ export async function runHeartbeatOnce(opts) {
462
527
  reason: opts.reason,
463
528
  durationMs: Date.now() - startedAt,
464
529
  channel: delivery.channel !== "none" ? delivery.channel : undefined,
530
+ accountId: delivery.accountId,
465
531
  silent: !okSent,
466
532
  indicatorType: visibility.useIndicator ? resolveIndicatorType("ok-token") : undefined,
467
533
  });
@@ -491,6 +557,7 @@ export async function runHeartbeatOnce(opts) {
491
557
  durationMs: Date.now() - startedAt,
492
558
  hasMedia: false,
493
559
  channel: delivery.channel !== "none" ? delivery.channel : undefined,
560
+ accountId: delivery.accountId,
494
561
  });
495
562
  return { status: "ran", durationMs: Date.now() - startedAt };
496
563
  }
@@ -508,11 +575,16 @@ export async function runHeartbeatOnce(opts) {
508
575
  preview: previewText?.slice(0, 200),
509
576
  durationMs: Date.now() - startedAt,
510
577
  hasMedia: mediaUrls.length > 0,
578
+ accountId: delivery.accountId,
511
579
  });
512
580
  return { status: "ran", durationMs: Date.now() - startedAt };
513
581
  }
514
582
  if (!visibility.showAlerts) {
515
- await restoreHeartbeatUpdatedAt({ storePath, sessionKey, updatedAt: previousUpdatedAt });
583
+ await restoreHeartbeatUpdatedAt({
584
+ storePath,
585
+ sessionKey,
586
+ updatedAt: previousUpdatedAt,
587
+ });
516
588
  emitHeartbeatEvent({
517
589
  status: "skipped",
518
590
  reason: "alerts-disabled",
@@ -520,6 +592,7 @@ export async function runHeartbeatOnce(opts) {
520
592
  durationMs: Date.now() - startedAt,
521
593
  channel: delivery.channel,
522
594
  hasMedia: mediaUrls.length > 0,
595
+ accountId: delivery.accountId,
523
596
  indicatorType: visibility.useIndicator ? resolveIndicatorType("sent") : undefined,
524
597
  });
525
598
  return { status: "ran", durationMs: Date.now() - startedAt };
@@ -540,6 +613,7 @@ export async function runHeartbeatOnce(opts) {
540
613
  durationMs: Date.now() - startedAt,
541
614
  hasMedia: mediaUrls.length > 0,
542
615
  channel: delivery.channel,
616
+ accountId: delivery.accountId,
543
617
  });
544
618
  log.info("heartbeat: channel not ready", {
545
619
  channel: delivery.channel,
@@ -586,6 +660,7 @@ export async function runHeartbeatOnce(opts) {
586
660
  durationMs: Date.now() - startedAt,
587
661
  hasMedia: mediaUrls.length > 0,
588
662
  channel: delivery.channel,
663
+ accountId: delivery.accountId,
589
664
  indicatorType: visibility.useIndicator ? resolveIndicatorType("sent") : undefined,
590
665
  });
591
666
  return { status: "ran", durationMs: Date.now() - startedAt };
@@ -597,6 +672,7 @@ export async function runHeartbeatOnce(opts) {
597
672
  reason,
598
673
  durationMs: Date.now() - startedAt,
599
674
  channel: delivery.channel !== "none" ? delivery.channel : undefined,
675
+ accountId: delivery.accountId,
600
676
  indicatorType: visibility.useIndicator ? resolveIndicatorType("failed") : undefined,
601
677
  });
602
678
  log.error(`heartbeat failed: ${reason}`, { error: reason });
@@ -624,22 +700,26 @@ export function startHeartbeatRunner(opts) {
624
700
  return now + intervalMs;
625
701
  };
626
702
  const scheduleNext = () => {
627
- if (state.stopped)
703
+ if (state.stopped) {
628
704
  return;
705
+ }
629
706
  if (state.timer) {
630
707
  clearTimeout(state.timer);
631
708
  state.timer = null;
632
709
  }
633
- if (state.agents.size === 0)
710
+ if (state.agents.size === 0) {
634
711
  return;
712
+ }
635
713
  const now = Date.now();
636
714
  let nextDue = Number.POSITIVE_INFINITY;
637
715
  for (const agent of state.agents.values()) {
638
- if (agent.nextDueMs < nextDue)
716
+ if (agent.nextDueMs < nextDue) {
639
717
  nextDue = agent.nextDueMs;
718
+ }
640
719
  }
641
- if (!Number.isFinite(nextDue))
720
+ if (!Number.isFinite(nextDue)) {
642
721
  return;
722
+ }
643
723
  const delay = Math.max(0, nextDue - now);
644
724
  state.timer = setTimeout(() => {
645
725
  requestHeartbeatNow({ reason: "interval", coalesceMs: 0 });
@@ -647,8 +727,9 @@ export function startHeartbeatRunner(opts) {
647
727
  state.timer.unref?.();
648
728
  };
649
729
  const updateConfig = (cfg) => {
650
- if (state.stopped)
730
+ if (state.stopped) {
651
731
  return;
732
+ }
652
733
  const now = Date.now();
653
734
  const prevAgents = state.agents;
654
735
  const prevEnabled = prevAgents.size > 0;
@@ -656,8 +737,9 @@ export function startHeartbeatRunner(opts) {
656
737
  const intervals = [];
657
738
  for (const agent of resolveHeartbeatAgents(cfg)) {
658
739
  const intervalMs = resolveHeartbeatIntervalMs(cfg, undefined, agent.heartbeat);
659
- if (!intervalMs)
740
+ if (!intervalMs) {
660
741
  continue;
742
+ }
661
743
  intervals.push(intervalMs);
662
744
  const prevState = prevAgents.get(agent.agentId);
663
745
  const nextDueMs = resolveNextDue(now, intervalMs, prevState);
@@ -693,10 +775,16 @@ export function startHeartbeatRunner(opts) {
693
775
  };
694
776
  const run = async (params) => {
695
777
  if (!heartbeatsEnabled) {
696
- return { status: "skipped", reason: "disabled" };
778
+ return {
779
+ status: "skipped",
780
+ reason: "disabled",
781
+ };
697
782
  }
698
783
  if (state.agents.size === 0) {
699
- return { status: "skipped", reason: "disabled" };
784
+ return {
785
+ status: "skipped",
786
+ reason: "disabled",
787
+ };
700
788
  }
701
789
  const reason = params?.reason;
702
790
  const isInterval = reason === "interval";
@@ -721,12 +809,14 @@ export function startHeartbeatRunner(opts) {
721
809
  agent.lastRunMs = now;
722
810
  agent.nextDueMs = now + agent.intervalMs;
723
811
  }
724
- if (res.status === "ran")
812
+ if (res.status === "ran") {
725
813
  ran = true;
814
+ }
726
815
  }
727
816
  scheduleNext();
728
- if (ran)
817
+ if (ran) {
729
818
  return { status: "ran", durationMs: Date.now() - startedAt };
819
+ }
730
820
  return { status: "skipped", reason: isInterval ? "not-due" : "disabled" };
731
821
  };
732
822
  setHeartbeatWakeHandler(async (params) => run({ reason: params.reason }));
@@ -734,8 +824,9 @@ export function startHeartbeatRunner(opts) {
734
824
  const cleanup = () => {
735
825
  state.stopped = true;
736
826
  setHeartbeatWakeHandler(null);
737
- if (state.timer)
827
+ if (state.timer) {
738
828
  clearTimeout(state.timer);
829
+ }
739
830
  state.timer = null;
740
831
  };
741
832
  opts.abortSignal?.addEventListener("abort", cleanup, { once: true });
@@ -0,0 +1,54 @@
1
+ import os from "node:os";
2
+ import path from "node:path";
3
+ function normalize(value) {
4
+ const trimmed = value?.trim();
5
+ return trimmed ? trimmed : undefined;
6
+ }
7
+ export function resolveEffectiveHomeDir(env = process.env, homedir = os.homedir) {
8
+ const raw = resolveRawHomeDir(env, homedir);
9
+ return raw ? path.resolve(raw) : undefined;
10
+ }
11
+ function resolveRawHomeDir(env, homedir) {
12
+ const explicitHome = normalize(env.CLAWDBOT_HOME);
13
+ if (explicitHome) {
14
+ if (explicitHome === "~" || explicitHome.startsWith("~/") || explicitHome.startsWith("~\\")) {
15
+ const fallbackHome = normalize(env.HOME) ?? normalize(env.USERPROFILE) ?? normalizeSafe(homedir);
16
+ if (fallbackHome) {
17
+ return explicitHome.replace(/^~(?=$|[\\/])/, fallbackHome);
18
+ }
19
+ return undefined;
20
+ }
21
+ return explicitHome;
22
+ }
23
+ const envHome = normalize(env.HOME);
24
+ if (envHome) {
25
+ return envHome;
26
+ }
27
+ const userProfile = normalize(env.USERPROFILE);
28
+ if (userProfile) {
29
+ return userProfile;
30
+ }
31
+ return normalizeSafe(homedir);
32
+ }
33
+ function normalizeSafe(homedir) {
34
+ try {
35
+ return normalize(homedir());
36
+ }
37
+ catch {
38
+ return undefined;
39
+ }
40
+ }
41
+ export function resolveRequiredHomeDir(env = process.env, homedir = os.homedir) {
42
+ return resolveEffectiveHomeDir(env, homedir) ?? path.resolve(process.cwd());
43
+ }
44
+ export function expandHomePrefix(input, opts) {
45
+ if (!input.startsWith("~")) {
46
+ return input;
47
+ }
48
+ const home = normalize(opts?.home) ??
49
+ resolveEffectiveHomeDir(opts?.env ?? process.env, opts?.homedir ?? os.homedir);
50
+ if (!home) {
51
+ return input;
52
+ }
53
+ return input.replace(/^~(?=$|[\\/])/, home);
54
+ }
@@ -0,0 +1,122 @@
1
+ import { closeDispatcher, createPinnedDispatcher, resolvePinnedHostname, resolvePinnedHostnameWithPolicy, } from "./ssrf.js";
2
+ const DEFAULT_MAX_REDIRECTS = 3;
3
+ function isRedirectStatus(status) {
4
+ return status === 301 || status === 302 || status === 303 || status === 307 || status === 308;
5
+ }
6
+ function buildAbortSignal(params) {
7
+ const { timeoutMs, signal } = params;
8
+ if (!timeoutMs && !signal) {
9
+ return { signal: undefined, cleanup: () => { } };
10
+ }
11
+ if (!timeoutMs) {
12
+ return { signal, cleanup: () => { } };
13
+ }
14
+ const controller = new AbortController();
15
+ const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
16
+ const onAbort = () => controller.abort();
17
+ if (signal) {
18
+ if (signal.aborted) {
19
+ controller.abort();
20
+ }
21
+ else {
22
+ signal.addEventListener("abort", onAbort, { once: true });
23
+ }
24
+ }
25
+ const cleanup = () => {
26
+ clearTimeout(timeoutId);
27
+ if (signal) {
28
+ signal.removeEventListener("abort", onAbort);
29
+ }
30
+ };
31
+ return { signal: controller.signal, cleanup };
32
+ }
33
+ export async function fetchWithSsrFGuard(params) {
34
+ const fetcher = params.fetchImpl ?? globalThis.fetch;
35
+ if (!fetcher) {
36
+ throw new Error("fetch is not available");
37
+ }
38
+ const maxRedirects = typeof params.maxRedirects === "number" && Number.isFinite(params.maxRedirects)
39
+ ? Math.max(0, Math.floor(params.maxRedirects))
40
+ : DEFAULT_MAX_REDIRECTS;
41
+ const { signal, cleanup } = buildAbortSignal({
42
+ timeoutMs: params.timeoutMs,
43
+ signal: params.signal,
44
+ });
45
+ let released = false;
46
+ const release = async (dispatcher) => {
47
+ if (released) {
48
+ return;
49
+ }
50
+ released = true;
51
+ cleanup();
52
+ await closeDispatcher(dispatcher ?? undefined);
53
+ };
54
+ const visited = new Set();
55
+ let currentUrl = params.url;
56
+ let redirectCount = 0;
57
+ while (true) {
58
+ let parsedUrl;
59
+ try {
60
+ parsedUrl = new URL(currentUrl);
61
+ }
62
+ catch {
63
+ await release();
64
+ throw new Error("Invalid URL: must be http or https");
65
+ }
66
+ if (!["http:", "https:"].includes(parsedUrl.protocol)) {
67
+ await release();
68
+ throw new Error("Invalid URL: must be http or https");
69
+ }
70
+ let dispatcher = null;
71
+ try {
72
+ const usePolicy = Boolean(params.policy?.allowPrivateNetwork || params.policy?.allowedHostnames?.length);
73
+ const pinned = usePolicy
74
+ ? await resolvePinnedHostnameWithPolicy(parsedUrl.hostname, {
75
+ lookupFn: params.lookupFn,
76
+ policy: params.policy,
77
+ })
78
+ : await resolvePinnedHostname(parsedUrl.hostname, params.lookupFn);
79
+ if (params.pinDns !== false) {
80
+ dispatcher = createPinnedDispatcher(pinned);
81
+ }
82
+ const init = {
83
+ ...(params.init ? { ...params.init } : {}),
84
+ redirect: "manual",
85
+ ...(dispatcher ? { dispatcher } : {}),
86
+ ...(signal ? { signal } : {}),
87
+ };
88
+ const response = await fetcher(parsedUrl.toString(), init);
89
+ if (isRedirectStatus(response.status)) {
90
+ const location = response.headers.get("location");
91
+ if (!location) {
92
+ await release(dispatcher);
93
+ throw new Error(`Redirect missing location header (${response.status})`);
94
+ }
95
+ redirectCount += 1;
96
+ if (redirectCount > maxRedirects) {
97
+ await release(dispatcher);
98
+ throw new Error(`Too many redirects (limit: ${maxRedirects})`);
99
+ }
100
+ const nextUrl = new URL(location, parsedUrl).toString();
101
+ if (visited.has(nextUrl)) {
102
+ await release(dispatcher);
103
+ throw new Error("Redirect loop detected");
104
+ }
105
+ visited.add(nextUrl);
106
+ void response.body?.cancel();
107
+ await closeDispatcher(dispatcher);
108
+ currentUrl = nextUrl;
109
+ continue;
110
+ }
111
+ return {
112
+ response,
113
+ finalUrl: currentUrl,
114
+ release: async () => release(dispatcher),
115
+ };
116
+ }
117
+ catch (err) {
118
+ await release(dispatcher);
119
+ throw err;
120
+ }
121
+ }
122
+ }