@poolzin/pool-bot 2026.2.21 → 2026.2.22

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 (369) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/dist/agents/api-key-rotation.js +47 -0
  3. package/dist/agents/apply-patch-update.js +19 -9
  4. package/dist/agents/apply-patch.js +72 -47
  5. package/dist/agents/bash-tools.exec.js +141 -559
  6. package/dist/agents/cli-backends.js +49 -6
  7. package/dist/agents/cli-runner/helpers.js +69 -152
  8. package/dist/agents/cli-runner.js +70 -19
  9. package/dist/agents/identity.js +20 -1
  10. package/dist/agents/image-sanitization.js +9 -0
  11. package/dist/agents/live-auth-keys.js +123 -26
  12. package/dist/agents/live-model-filter.js +13 -4
  13. package/dist/agents/model-catalog.js +40 -9
  14. package/dist/agents/model-forward-compat.js +60 -23
  15. package/dist/agents/model-selection.js +134 -41
  16. package/dist/agents/pi-auth-json.js +2 -2
  17. package/dist/agents/pi-embedded-helpers/bootstrap.js +65 -15
  18. package/dist/agents/pi-embedded-helpers/errors.js +140 -15
  19. package/dist/agents/pi-embedded-helpers/images.js +22 -12
  20. package/dist/agents/pi-embedded-helpers.js +2 -2
  21. package/dist/agents/pi-embedded-runner/abort.js +10 -3
  22. package/dist/agents/pi-embedded-runner/compact.js +230 -32
  23. package/dist/agents/pi-embedded-runner/extra-params.js +203 -12
  24. package/dist/agents/pi-embedded-runner/google.js +109 -19
  25. package/dist/agents/pi-embedded-runner/history.js +35 -17
  26. package/dist/agents/pi-embedded-runner/run/attempt.js +386 -95
  27. package/dist/agents/pi-embedded-runner/run/images.js +81 -55
  28. package/dist/agents/pi-embedded-runner/run/payloads.js +89 -39
  29. package/dist/agents/pi-embedded-runner/run.js +193 -25
  30. package/dist/agents/pi-embedded-runner/run.overflow-compaction.mocks.shared.js +2 -2
  31. package/dist/agents/pi-embedded-runner/runs.js +17 -8
  32. package/dist/agents/pi-embedded-runner/tool-result-context-guard.js +262 -0
  33. package/dist/agents/pi-embedded-runner.js +1 -1
  34. package/dist/agents/pi-embedded-subscribe.handlers.tools.js +180 -10
  35. package/dist/agents/pi-embedded-subscribe.js +37 -0
  36. package/dist/agents/pi-embedded-subscribe.tools.js +127 -30
  37. package/dist/agents/pi-model-discovery.js +9 -2
  38. package/dist/agents/pi-tool-definition-adapter.js +60 -8
  39. package/dist/agents/pi-tools.before-tool-call.js +1 -1
  40. package/dist/agents/pi-tools.js +113 -94
  41. package/dist/agents/pi-tools.read.js +337 -38
  42. package/dist/agents/poolbot-tools.js +14 -5
  43. package/dist/agents/sandbox/docker.js +10 -5
  44. package/dist/agents/sandbox/registry.js +96 -46
  45. package/dist/agents/sandbox/sanitize-env-vars.js +82 -0
  46. package/dist/agents/sandbox-paths.js +43 -10
  47. package/dist/agents/session-tool-result-guard-wrapper.js +23 -11
  48. package/dist/agents/session-tool-result-guard.js +39 -39
  49. package/dist/agents/session-transcript-repair.js +36 -33
  50. package/dist/agents/session-write-lock.js +62 -44
  51. package/dist/agents/skills/frontmatter.js +49 -88
  52. package/dist/agents/skills/workspace.js +335 -28
  53. package/dist/agents/subagent-announce.js +508 -174
  54. package/dist/agents/subagent-registry.js +45 -4
  55. package/dist/agents/subagent-spawn.js +16 -33
  56. package/dist/agents/system-prompt-report.js +27 -10
  57. package/dist/agents/system-prompt.js +26 -32
  58. package/dist/agents/tool-call-id.js +69 -17
  59. package/dist/agents/tool-display-common.js +1 -1
  60. package/dist/agents/tool-images.js +64 -31
  61. package/dist/agents/tools/canvas-tool.js +17 -11
  62. package/dist/agents/tools/common.js +37 -19
  63. package/dist/agents/tools/cron-tool.js +40 -38
  64. package/dist/agents/tools/gateway.js +70 -2
  65. package/dist/agents/tools/message-tool.js +181 -40
  66. package/dist/agents/tools/nodes-tool.js +128 -36
  67. package/dist/agents/tools/nodes-utils.js +12 -38
  68. package/dist/agents/tools/session-status-tool.js +24 -71
  69. package/dist/agents/tools/sessions-helpers.js +38 -210
  70. package/dist/agents/tools/sessions-spawn-tool.js +28 -198
  71. package/dist/agents/tools/telegram-actions.js +58 -7
  72. package/dist/agents/tools/web-fetch-utils.js +112 -7
  73. package/dist/agents/tools/web-fetch.js +279 -175
  74. package/dist/agents/tools/web-shared.js +71 -8
  75. package/dist/agents/usage.js +25 -16
  76. package/dist/auto-reply/commands-registry.data.js +85 -11
  77. package/dist/auto-reply/dispatch.js +40 -21
  78. package/dist/auto-reply/reply/abort.js +102 -33
  79. package/dist/auto-reply/reply/commands-core.js +82 -33
  80. package/dist/auto-reply/reply/commands-export-session.js +1 -1
  81. package/dist/auto-reply/reply/commands-info.js +41 -12
  82. package/dist/auto-reply/reply/commands-subagents.js +352 -100
  83. package/dist/auto-reply/reply/commands-system-prompt.js +2 -2
  84. package/dist/auto-reply/reply/dispatch-from-config.js +100 -29
  85. package/dist/auto-reply/reply/elevated-unavailable.js +1 -1
  86. package/dist/auto-reply/reply/inbound-meta.js +12 -1
  87. package/dist/auto-reply/reply/mentions.js +18 -11
  88. package/dist/auto-reply/reply/normalize-reply.js +17 -8
  89. package/dist/auto-reply/reply/reply-dispatcher.js +62 -10
  90. package/dist/auto-reply/reply/session.js +102 -21
  91. package/dist/auto-reply/reply/streaming-directives.js +16 -5
  92. package/dist/auto-reply/status.js +73 -50
  93. package/dist/browser/extension-relay.js +3 -3
  94. package/dist/browser/http-auth.js +1 -1
  95. package/dist/browser/paths.js +2 -2
  96. package/dist/build-info.json +3 -3
  97. package/dist/channels/allowlist-match.js +20 -0
  98. package/dist/channels/allowlists/resolve-utils.js +65 -2
  99. package/dist/channels/chat-type.js +8 -4
  100. package/dist/channels/dock.js +127 -35
  101. package/dist/channels/draft-stream-loop.js +6 -2
  102. package/dist/channels/plugins/actions/telegram.js +42 -18
  103. package/dist/channels/plugins/allowlist-match.js +1 -1
  104. package/dist/channels/plugins/group-mentions.js +51 -41
  105. package/dist/channels/plugins/message-action-names.js +2 -0
  106. package/dist/channels/plugins/message-actions.js +24 -5
  107. package/dist/channels/plugins/normalize/discord.js +26 -4
  108. package/dist/channels/plugins/normalize/signal.js +35 -22
  109. package/dist/channels/plugins/onboarding/helpers.js +8 -26
  110. package/dist/channels/plugins/outbound/imessage.js +15 -14
  111. package/dist/channels/registry.js +20 -7
  112. package/dist/cli/acp-cli.js +7 -5
  113. package/dist/cli/browser-cli-extension.js +25 -12
  114. package/dist/cli/browser-cli-state.cookies-storage.js +25 -6
  115. package/dist/cli/browser-cli-state.js +101 -145
  116. package/dist/cli/command-options.js +28 -0
  117. package/dist/cli/completion-cli.js +6 -6
  118. package/dist/cli/cron-cli/register.cron-add.js +25 -1
  119. package/dist/cli/cron-cli/register.cron-edit.js +44 -0
  120. package/dist/cli/cron-cli/shared.js +7 -1
  121. package/dist/cli/daemon-cli/lifecycle-core.js +23 -21
  122. package/dist/cli/daemon-cli/lifecycle.js +23 -247
  123. package/dist/cli/daemon-cli/register-service-commands.js +25 -4
  124. package/dist/cli/daemon-cli.js +1 -0
  125. package/dist/cli/devices-cli.js +33 -20
  126. package/dist/cli/gateway-cli/register.js +37 -105
  127. package/dist/cli/gateway-cli/run.js +49 -11
  128. package/dist/cli/nodes-camera.js +59 -4
  129. package/dist/cli/nodes-cli/register.camera.js +27 -24
  130. package/dist/cli/nodes-cli/rpc.js +21 -38
  131. package/dist/cli/qr-cli.js +2 -2
  132. package/dist/cli/skills-cli.format.js +2 -2
  133. package/dist/cli/update-cli/progress.js +2 -2
  134. package/dist/cli/update-cli/restart-helper.js +28 -7
  135. package/dist/cli/update-cli/shared.js +7 -7
  136. package/dist/cli/update-cli/status.js +1 -1
  137. package/dist/cli/update-cli/update-command.js +14 -8
  138. package/dist/cli/update-cli/wizard.js +2 -2
  139. package/dist/cli/update-cli.js +21 -1027
  140. package/dist/commands/auth-choice.apply.anthropic.js +10 -2
  141. package/dist/commands/channels/add-mutators.js +3 -35
  142. package/dist/commands/channels/add.js +39 -51
  143. package/dist/commands/config-validation.js +1 -1
  144. package/dist/commands/configure.gateway-auth.js +52 -15
  145. package/dist/commands/configure.gateway.js +84 -40
  146. package/dist/commands/doctor-completion.js +3 -3
  147. package/dist/commands/doctor-config-flow.js +536 -16
  148. package/dist/commands/doctor-gateway-services.js +103 -79
  149. package/dist/commands/doctor-memory-search.js +9 -9
  150. package/dist/commands/doctor-platform-notes.js +57 -30
  151. package/dist/commands/doctor-prompter.js +26 -15
  152. package/dist/commands/doctor-session-locks.js +1 -1
  153. package/dist/commands/doctor.js +21 -9
  154. package/dist/commands/model-picker.js +120 -95
  155. package/dist/commands/models/set.js +2 -21
  156. package/dist/commands/models/shared.js +65 -37
  157. package/dist/commands/onboard-helpers.js +81 -39
  158. package/dist/commands/openai-codex-oauth.js +1 -1
  159. package/dist/commands/sessions.js +52 -53
  160. package/dist/commands/status.summary.js +52 -34
  161. package/dist/commands/test-wizard-helpers.js +2 -2
  162. package/dist/config/defaults.js +79 -42
  163. package/dist/config/group-policy.js +50 -18
  164. package/dist/config/includes.js +37 -10
  165. package/dist/config/schema.help.js +5 -4
  166. package/dist/config/schema.hints.js +2 -2
  167. package/dist/config/schema.labels.js +1 -0
  168. package/dist/config/sessions/group.js +12 -11
  169. package/dist/config/sessions/paths.js +137 -11
  170. package/dist/config/sessions/store.js +185 -65
  171. package/dist/config/sessions/types.js +15 -1
  172. package/dist/config/sessions.js +1 -0
  173. package/dist/config/telegram-custom-commands.js +3 -2
  174. package/dist/config/types.js +2 -0
  175. package/dist/config/zod-schema.agent-defaults.js +6 -27
  176. package/dist/config/zod-schema.agent-runtime.js +171 -79
  177. package/dist/config/zod-schema.providers-core.js +138 -65
  178. package/dist/config/zod-schema.session.js +49 -22
  179. package/dist/control-ui/assets/index-HRr1grwl.js.map +1 -1
  180. package/dist/cron/isolated-agent/run.js +224 -57
  181. package/dist/cron/normalize.js +48 -45
  182. package/dist/cron/run-log.js +14 -0
  183. package/dist/cron/service/jobs.js +190 -28
  184. package/dist/cron/service/normalize.js +29 -11
  185. package/dist/cron/service/store.js +30 -44
  186. package/dist/cron/service/timer.js +182 -96
  187. package/dist/cron/service.js +3 -0
  188. package/dist/cron/stagger.js +37 -0
  189. package/dist/daemon/inspect.js +132 -92
  190. package/dist/daemon/runtime-paths.js +25 -4
  191. package/dist/daemon/service-audit.js +47 -16
  192. package/dist/discord/accounts.js +23 -20
  193. package/dist/discord/monitor/agent-components.js +1115 -219
  194. package/dist/discord/monitor/allow-list.js +114 -34
  195. package/dist/discord/monitor/listeners.js +204 -97
  196. package/dist/discord/monitor/message-handler.js +21 -10
  197. package/dist/discord/monitor/message-handler.preflight.js +195 -101
  198. package/dist/discord/monitor/message-handler.process.js +384 -123
  199. package/dist/discord/monitor/message-utils.js +86 -23
  200. package/dist/discord/monitor/native-command.js +77 -57
  201. package/dist/discord/monitor/provider.js +122 -117
  202. package/dist/discord/monitor/reply-context.js +20 -16
  203. package/dist/discord/monitor/reply-delivery.js +40 -8
  204. package/dist/discord/monitor/rest-fetch.js +22 -0
  205. package/dist/discord/monitor/threading.js +117 -24
  206. package/dist/discord/send.js +2 -1
  207. package/dist/discord/send.outbound.js +124 -11
  208. package/dist/discord/send.shared.js +112 -72
  209. package/dist/discord/voice-message.js +3 -3
  210. package/dist/gateway/auth.js +119 -44
  211. package/dist/gateway/call.js +76 -34
  212. package/dist/gateway/channel-health-monitor.js +57 -50
  213. package/dist/gateway/client.js +63 -29
  214. package/dist/gateway/control-ui-contract.js +1 -1
  215. package/dist/gateway/gateway-config-prompts.shared.js +2 -2
  216. package/dist/gateway/net.js +109 -1
  217. package/dist/gateway/protocol/index.js +5 -8
  218. package/dist/gateway/protocol/schema/agent.js +19 -1
  219. package/dist/gateway/protocol/schema/channels.js +21 -0
  220. package/dist/gateway/protocol/schema/cron.js +43 -30
  221. package/dist/gateway/protocol/schema/protocol-schemas.js +6 -11
  222. package/dist/gateway/protocol/schema/sessions.js +5 -1
  223. package/dist/gateway/protocol/schema.js +0 -1
  224. package/dist/gateway/server/presence-events.js +12 -0
  225. package/dist/gateway/server/ws-connection/message-handler.js +203 -212
  226. package/dist/gateway/server/ws-connection.js +58 -21
  227. package/dist/gateway/server-broadcast.js +18 -13
  228. package/dist/gateway/server-cron.js +177 -10
  229. package/dist/gateway/server-methods/agent-job.js +131 -38
  230. package/dist/gateway/server-methods/send.js +60 -14
  231. package/dist/gateway/server-methods/sessions.js +160 -96
  232. package/dist/gateway/server-methods/system.js +5 -7
  233. package/dist/gateway/server-methods-list.js +8 -0
  234. package/dist/gateway/server-methods.js +24 -8
  235. package/dist/gateway/server-node-events.js +278 -68
  236. package/dist/gateway/session-utils.fs.js +316 -75
  237. package/dist/gateway/session-utils.js +224 -70
  238. package/dist/gateway/sessions-patch.js +63 -20
  239. package/dist/gateway/test-temp-config.js +1 -1
  240. package/dist/gateway/tools-invoke-http.js +118 -70
  241. package/dist/gateway/ws-log.js +135 -107
  242. package/dist/hooks/frontmatter.js +36 -82
  243. package/dist/hooks/install.js +149 -139
  244. package/dist/hooks/internal-hooks.js +29 -4
  245. package/dist/hooks/plugin-hooks.js +2 -1
  246. package/dist/imessage/monitor/deliver.js +10 -4
  247. package/dist/imessage/monitor/monitor-provider.js +138 -375
  248. package/dist/imessage/monitor/runtime.js +4 -8
  249. package/dist/imessage/send.js +65 -19
  250. package/dist/infra/exec-approvals-allowlist.js +7 -0
  251. package/dist/infra/exec-approvals.js +35 -920
  252. package/dist/infra/exec-safe-bin-trust.js +64 -0
  253. package/dist/infra/heartbeat-runner.js +207 -134
  254. package/dist/infra/heartbeat-wake.js +183 -22
  255. package/dist/infra/install-source-utils.js +47 -0
  256. package/dist/infra/net/ssrf.js +170 -36
  257. package/dist/infra/outbound/deliver.js +224 -58
  258. package/dist/infra/outbound/message-action-spec.js +12 -5
  259. package/dist/infra/outbound/outbound-session.js +27 -25
  260. package/dist/infra/poolbot-root.js +32 -22
  261. package/dist/infra/ports.js +14 -11
  262. package/dist/infra/skills-remote.js +48 -37
  263. package/dist/infra/system-events.js +25 -11
  264. package/dist/infra/system-presence.js +26 -33
  265. package/dist/infra/tmp-poolbot-dir.js +81 -2
  266. package/dist/infra/wsl.js +37 -1
  267. package/dist/line/bot-message-context.js +163 -191
  268. package/dist/logging/subsystem.js +59 -22
  269. package/dist/markdown/ir.js +124 -50
  270. package/dist/media/store.js +1 -1
  271. package/dist/media-understanding/runner.entries.js +42 -25
  272. package/dist/media-understanding/runner.js +53 -488
  273. package/dist/memory/embeddings-gemini.js +53 -38
  274. package/dist/memory/manager-embedding-ops.js +48 -69
  275. package/dist/pairing/pairing-store.js +178 -119
  276. package/dist/plugin-sdk/index.js +34 -6
  277. package/dist/plugins/hooks.js +135 -14
  278. package/dist/plugins/install.js +190 -152
  279. package/dist/polls.js +11 -0
  280. package/dist/routing/resolve-route.js +190 -56
  281. package/dist/routing/session-key.js +38 -22
  282. package/dist/runtime.js +35 -9
  283. package/dist/security/audit-channel.js +1 -1
  284. package/dist/sessions/session-key-utils.js +29 -11
  285. package/dist/shared/frontmatter.js +5 -5
  286. package/dist/shared/node-list-types.js +1 -0
  287. package/dist/shared/string-normalization.js +15 -0
  288. package/dist/signal/monitor/event-handler.js +68 -36
  289. package/dist/signal/send.js +29 -37
  290. package/dist/slack/monitor/allow-list.js +10 -11
  291. package/dist/slack/monitor/commands.js +14 -3
  292. package/dist/slack/monitor/events/interactions.js +4 -4
  293. package/dist/slack/monitor/media.js +224 -16
  294. package/dist/slack/monitor/message-handler/dispatch.js +247 -13
  295. package/dist/slack/monitor/message-handler/prepare.js +128 -45
  296. package/dist/slack/monitor/slash.js +357 -144
  297. package/dist/slack/streaming.js +77 -0
  298. package/dist/telegram/accounts.js +40 -13
  299. package/dist/telegram/allowed-updates.js +3 -0
  300. package/dist/telegram/bot/delivery.js +129 -66
  301. package/dist/telegram/bot/helpers.js +136 -122
  302. package/dist/telegram/bot-handlers.js +600 -339
  303. package/dist/telegram/bot-message-context.js +115 -73
  304. package/dist/telegram/bot-message-dispatch.js +235 -104
  305. package/dist/telegram/bot-native-command-menu.js +3 -1
  306. package/dist/telegram/bot-native-commands.js +213 -193
  307. package/dist/telegram/bot.js +24 -132
  308. package/dist/telegram/draft-stream.js +84 -75
  309. package/dist/telegram/format.js +150 -6
  310. package/dist/telegram/send.js +415 -255
  311. package/dist/telegram/targets.js +21 -2
  312. package/dist/telegram/update-offset-store.js +19 -3
  313. package/dist/terminal/restore.js +5 -2
  314. package/dist/test-utils/fetch-mock.js +5 -0
  315. package/dist/version.js +18 -5
  316. package/dist/web/auto-reply/monitor/broadcast.js +7 -3
  317. package/dist/web/auto-reply/monitor/on-message.js +6 -3
  318. package/dist/web/inbound/media.js +34 -8
  319. package/dist/web/inbound/monitor.js +34 -17
  320. package/dist/web/inbound/send-api.js +18 -17
  321. package/dist/web/outbound.js +12 -5
  322. package/dist/wizard/clack-prompter.js +40 -7
  323. package/extensions/bluebubbles/package.json +1 -1
  324. package/extensions/copilot-proxy/package.json +1 -1
  325. package/extensions/diagnostics-otel/package.json +1 -1
  326. package/extensions/discord/package.json +1 -1
  327. package/extensions/feishu/package.json +1 -1
  328. package/extensions/google-antigravity-auth/package.json +1 -1
  329. package/extensions/google-gemini-cli-auth/package.json +1 -1
  330. package/extensions/googlechat/package.json +1 -1
  331. package/extensions/imessage/package.json +1 -1
  332. package/extensions/irc/package.json +1 -1
  333. package/extensions/line/package.json +1 -1
  334. package/extensions/llm-task/package.json +1 -1
  335. package/extensions/lobster/package.json +1 -1
  336. package/extensions/matrix/CHANGELOG.md +5 -0
  337. package/extensions/matrix/package.json +1 -1
  338. package/extensions/mattermost/package.json +1 -1
  339. package/extensions/memory-core/package.json +1 -1
  340. package/extensions/memory-lancedb/package.json +1 -1
  341. package/extensions/minimax-portal-auth/package.json +1 -1
  342. package/extensions/msteams/CHANGELOG.md +5 -0
  343. package/extensions/msteams/package.json +1 -1
  344. package/extensions/nextcloud-talk/package.json +1 -1
  345. package/extensions/nostr/CHANGELOG.md +5 -0
  346. package/extensions/nostr/package.json +1 -1
  347. package/extensions/open-prose/package.json +1 -1
  348. package/extensions/openai-codex-auth/package.json +1 -1
  349. package/extensions/signal/package.json +1 -1
  350. package/extensions/slack/package.json +1 -1
  351. package/extensions/telegram/package.json +1 -1
  352. package/extensions/tlon/package.json +1 -1
  353. package/extensions/twitch/CHANGELOG.md +5 -0
  354. package/extensions/twitch/package.json +1 -1
  355. package/extensions/voice-call/CHANGELOG.md +5 -0
  356. package/extensions/voice-call/package.json +1 -1
  357. package/extensions/whatsapp/package.json +1 -1
  358. package/extensions/zalo/CHANGELOG.md +5 -0
  359. package/extensions/zalo/package.json +1 -1
  360. package/extensions/zalouser/CHANGELOG.md +5 -0
  361. package/extensions/zalouser/package.json +1 -1
  362. package/package.json +1 -1
  363. package/skills/apple-reminders/SKILL.md +100 -49
  364. package/skills/coding-agent/SKILL.md +34 -28
  365. package/skills/github/SKILL.md +131 -16
  366. package/skills/imsg/SKILL.md +112 -15
  367. package/skills/openhue/SKILL.md +101 -19
  368. package/skills/tmux/SKILL.md +111 -79
  369. package/skills/weather/SKILL.md +88 -25
@@ -1,46 +1,172 @@
1
1
  import os from "node:os";
2
2
  import path from "node:path";
3
+ import { expandHomePrefix, resolveRequiredHomeDir } from "../../infra/home-dir.js";
3
4
  import { DEFAULT_AGENT_ID, normalizeAgentId } from "../../routing/session-key.js";
4
5
  import { resolveStateDir } from "../paths.js";
5
- function resolveAgentSessionsDir(agentId, env = process.env, homedir = os.homedir) {
6
+ function resolveAgentSessionsDir(agentId, env = process.env, homedir = () => resolveRequiredHomeDir(env, os.homedir)) {
6
7
  const root = resolveStateDir(env, homedir);
7
8
  const id = normalizeAgentId(agentId ?? DEFAULT_AGENT_ID);
8
9
  return path.join(root, "agents", id, "sessions");
9
10
  }
10
- export function resolveSessionTranscriptsDir(env = process.env, homedir = os.homedir) {
11
+ export function resolveSessionTranscriptsDir(env = process.env, homedir = () => resolveRequiredHomeDir(env, os.homedir)) {
11
12
  return resolveAgentSessionsDir(DEFAULT_AGENT_ID, env, homedir);
12
13
  }
13
- export function resolveSessionTranscriptsDirForAgent(agentId, env = process.env, homedir = os.homedir) {
14
+ export function resolveSessionTranscriptsDirForAgent(agentId, env = process.env, homedir = () => resolveRequiredHomeDir(env, os.homedir)) {
14
15
  return resolveAgentSessionsDir(agentId, env, homedir);
15
16
  }
16
17
  export function resolveDefaultSessionStorePath(agentId) {
17
18
  return path.join(resolveAgentSessionsDir(agentId), "sessions.json");
18
19
  }
19
- export function resolveSessionTranscriptPath(sessionId, agentId, topicId) {
20
+ export function resolveSessionFilePathOptions(params) {
21
+ const agentId = params.agentId?.trim();
22
+ const storePath = params.storePath?.trim();
23
+ if (storePath) {
24
+ const sessionsDir = path.dirname(path.resolve(storePath));
25
+ return agentId ? { sessionsDir, agentId } : { sessionsDir };
26
+ }
27
+ if (agentId) {
28
+ return { agentId };
29
+ }
30
+ return undefined;
31
+ }
32
+ export const SAFE_SESSION_ID_RE = /^[a-z0-9][a-z0-9._-]{0,127}$/i;
33
+ export function validateSessionId(sessionId) {
34
+ const trimmed = sessionId.trim();
35
+ if (!SAFE_SESSION_ID_RE.test(trimmed)) {
36
+ throw new Error(`Invalid session ID: ${sessionId}`);
37
+ }
38
+ return trimmed;
39
+ }
40
+ function resolveSessionsDir(opts) {
41
+ const sessionsDir = opts?.sessionsDir?.trim();
42
+ if (sessionsDir) {
43
+ return path.resolve(sessionsDir);
44
+ }
45
+ return resolveAgentSessionsDir(opts?.agentId);
46
+ }
47
+ function resolvePathFromAgentSessionsDir(agentSessionsDir, candidateAbsPath) {
48
+ const agentBase = path.resolve(agentSessionsDir);
49
+ const relative = path.relative(agentBase, candidateAbsPath);
50
+ if (!relative || relative.startsWith("..") || path.isAbsolute(relative)) {
51
+ return undefined;
52
+ }
53
+ return path.resolve(agentBase, relative);
54
+ }
55
+ function resolveSiblingAgentSessionsDir(baseSessionsDir, agentId) {
56
+ const resolvedBase = path.resolve(baseSessionsDir);
57
+ if (path.basename(resolvedBase) !== "sessions") {
58
+ return undefined;
59
+ }
60
+ const baseAgentDir = path.dirname(resolvedBase);
61
+ const baseAgentsDir = path.dirname(baseAgentDir);
62
+ if (path.basename(baseAgentsDir) !== "agents") {
63
+ return undefined;
64
+ }
65
+ const rootDir = path.dirname(baseAgentsDir);
66
+ return path.join(rootDir, "agents", normalizeAgentId(agentId), "sessions");
67
+ }
68
+ function extractAgentIdFromAbsoluteSessionPath(candidateAbsPath) {
69
+ const normalized = path.normalize(path.resolve(candidateAbsPath));
70
+ const parts = normalized.split(path.sep).filter(Boolean);
71
+ const sessionsIndex = parts.lastIndexOf("sessions");
72
+ if (sessionsIndex < 2 || parts[sessionsIndex - 2] !== "agents") {
73
+ return undefined;
74
+ }
75
+ const agentId = parts[sessionsIndex - 1];
76
+ return agentId || undefined;
77
+ }
78
+ function resolvePathWithinSessionsDir(sessionsDir, candidate, opts) {
79
+ const trimmed = candidate.trim();
80
+ if (!trimmed) {
81
+ throw new Error("Session file path must not be empty");
82
+ }
83
+ const resolvedBase = path.resolve(sessionsDir);
84
+ // Normalize absolute paths that are within the sessions directory.
85
+ // Older versions stored absolute sessionFile paths in sessions.json;
86
+ // convert them to relative so the containment check passes.
87
+ const normalized = path.isAbsolute(trimmed) ? path.relative(resolvedBase, trimmed) : trimmed;
88
+ if (normalized.startsWith("..") && path.isAbsolute(trimmed)) {
89
+ const tryAgentFallback = (agentId) => {
90
+ const normalizedAgentId = normalizeAgentId(agentId);
91
+ const siblingSessionsDir = resolveSiblingAgentSessionsDir(resolvedBase, normalizedAgentId);
92
+ if (siblingSessionsDir) {
93
+ const siblingResolved = resolvePathFromAgentSessionsDir(siblingSessionsDir, trimmed);
94
+ if (siblingResolved) {
95
+ return siblingResolved;
96
+ }
97
+ }
98
+ return resolvePathFromAgentSessionsDir(resolveAgentSessionsDir(normalizedAgentId), trimmed);
99
+ };
100
+ const explicitAgentId = opts?.agentId?.trim();
101
+ if (explicitAgentId) {
102
+ const resolvedFromAgent = tryAgentFallback(explicitAgentId);
103
+ if (resolvedFromAgent) {
104
+ return resolvedFromAgent;
105
+ }
106
+ }
107
+ const extractedAgentId = extractAgentIdFromAbsoluteSessionPath(trimmed);
108
+ if (extractedAgentId) {
109
+ const resolvedFromPath = tryAgentFallback(extractedAgentId);
110
+ if (resolvedFromPath) {
111
+ return resolvedFromPath;
112
+ }
113
+ // The path structurally matches .../agents/<agentId>/sessions/...
114
+ // Accept it even if the root directory differs from the current env
115
+ // (e.g., POOLBOT_STATE_DIR changed between session creation and resolution).
116
+ // The structural pattern provides sufficient containment guarantees.
117
+ return path.resolve(trimmed);
118
+ }
119
+ }
120
+ if (!normalized || normalized.startsWith("..") || path.isAbsolute(normalized)) {
121
+ throw new Error("Session file path must be within sessions directory");
122
+ }
123
+ return path.resolve(resolvedBase, normalized);
124
+ }
125
+ export function resolveSessionTranscriptPathInDir(sessionId, sessionsDir, topicId) {
126
+ const safeSessionId = validateSessionId(sessionId);
20
127
  const safeTopicId = typeof topicId === "string"
21
128
  ? encodeURIComponent(topicId)
22
129
  : typeof topicId === "number"
23
130
  ? String(topicId)
24
131
  : undefined;
25
- const fileName = safeTopicId !== undefined ? `${sessionId}-topic-${safeTopicId}.jsonl` : `${sessionId}.jsonl`;
26
- return path.join(resolveAgentSessionsDir(agentId), fileName);
132
+ const fileName = safeTopicId !== undefined
133
+ ? `${safeSessionId}-topic-${safeTopicId}.jsonl`
134
+ : `${safeSessionId}.jsonl`;
135
+ return resolvePathWithinSessionsDir(sessionsDir, fileName);
136
+ }
137
+ export function resolveSessionTranscriptPath(sessionId, agentId, topicId) {
138
+ return resolveSessionTranscriptPathInDir(sessionId, resolveAgentSessionsDir(agentId), topicId);
27
139
  }
28
140
  export function resolveSessionFilePath(sessionId, entry, opts) {
141
+ const sessionsDir = resolveSessionsDir(opts);
29
142
  const candidate = entry?.sessionFile?.trim();
30
- return candidate ? candidate : resolveSessionTranscriptPath(sessionId, opts?.agentId);
143
+ if (candidate) {
144
+ return resolvePathWithinSessionsDir(sessionsDir, candidate, { agentId: opts?.agentId });
145
+ }
146
+ return resolveSessionTranscriptPathInDir(sessionId, sessionsDir);
31
147
  }
32
148
  export function resolveStorePath(store, opts) {
33
149
  const agentId = normalizeAgentId(opts?.agentId ?? DEFAULT_AGENT_ID);
34
- if (!store)
150
+ if (!store) {
35
151
  return resolveDefaultSessionStorePath(agentId);
152
+ }
36
153
  if (store.includes("{agentId}")) {
37
154
  const expanded = store.replaceAll("{agentId}", agentId);
38
155
  if (expanded.startsWith("~")) {
39
- return path.resolve(expanded.replace(/^~(?=$|[\\/])/, os.homedir()));
156
+ return path.resolve(expandHomePrefix(expanded, {
157
+ home: resolveRequiredHomeDir(process.env, os.homedir),
158
+ env: process.env,
159
+ homedir: os.homedir,
160
+ }));
40
161
  }
41
162
  return path.resolve(expanded);
42
163
  }
43
- if (store.startsWith("~"))
44
- return path.resolve(store.replace(/^~(?=$|[\\/])/, os.homedir()));
164
+ if (store.startsWith("~")) {
165
+ return path.resolve(expandHomePrefix(store, {
166
+ home: resolveRequiredHomeDir(process.env, os.homedir),
167
+ env: process.env,
168
+ homedir: os.homedir,
169
+ }));
170
+ }
45
171
  return path.resolve(store);
46
172
  }
@@ -1,9 +1,10 @@
1
- import JSON5 from "json5";
2
1
  import crypto from "node:crypto";
3
2
  import fs from "node:fs";
4
3
  import path from "node:path";
4
+ import { acquireSessionWriteLock } from "../../agents/session-write-lock.js";
5
5
  import { parseByteSize } from "../../cli/parse-bytes.js";
6
6
  import { parseDurationMs } from "../../cli/parse-duration.js";
7
+ import { archiveSessionTranscripts, cleanupArchivedSessionTranscripts, } from "../../gateway/session-utils.fs.js";
7
8
  import { createSubsystemLogger } from "../../logging/subsystem.js";
8
9
  import { deliveryContextFromSession, mergeDeliveryContext, normalizeDeliveryContext, normalizeSessionDeliveryFields, } from "../../utils/delivery-context.js";
9
10
  import { getFileMtimeMs, isCacheEnabled, resolveCacheTtlMs } from "../cache-utils.js";
@@ -18,7 +19,7 @@ function isSessionStoreRecord(value) {
18
19
  }
19
20
  function getSessionStoreTtl() {
20
21
  return resolveCacheTtlMs({
21
- envValue: process.env.POOLBOT_SESSION_CACHE_TTL_MS || process.env.CLAWDBOT_SESSION_CACHE_TTL_MS,
22
+ envValue: process.env.POOLBOT_SESSION_CACHE_TTL_MS,
22
23
  defaultTtlMs: DEFAULT_SESSION_STORE_TTL_MS,
23
24
  });
24
25
  }
@@ -84,6 +85,19 @@ function normalizeSessionStore(store) {
84
85
  }
85
86
  export function clearSessionStoreCacheForTest() {
86
87
  SESSION_STORE_CACHE.clear();
88
+ for (const queue of LOCK_QUEUES.values()) {
89
+ for (const task of queue.pending) {
90
+ task.reject(new Error("session store queue cleared for test"));
91
+ }
92
+ }
93
+ LOCK_QUEUES.clear();
94
+ }
95
+ /** Expose lock queue size for tests. */
96
+ export function getSessionStoreLockQueueSizeForTest() {
97
+ return LOCK_QUEUES.size;
98
+ }
99
+ export async function withSessionStoreLockForTest(storePath, fn, opts = {}) {
100
+ return await withSessionStoreLock(storePath, fn, opts);
87
101
  }
88
102
  export function loadSessionStore(storePath, opts = {}) {
89
103
  // Check cache first if enabled
@@ -98,19 +112,39 @@ export function loadSessionStore(storePath, opts = {}) {
98
112
  invalidateSessionStoreCache(storePath);
99
113
  }
100
114
  }
101
- // Cache miss or disabled - load from disk
115
+ // Cache miss or disabled - load from disk.
116
+ // Retry up to 3 times when the file is empty or unparseable. On Windows the
117
+ // temp-file + rename write is not fully atomic: a concurrent reader can briefly
118
+ // observe a 0-byte file (between truncate and write) or a stale/locked state.
119
+ // A short synchronous backoff (50 ms via `Atomics.wait`) is enough for the
120
+ // writer to finish.
102
121
  let store = {};
103
122
  let mtimeMs = getFileMtimeMs(storePath);
104
- try {
105
- const raw = fs.readFileSync(storePath, "utf-8");
106
- const parsed = JSON5.parse(raw);
107
- if (isSessionStoreRecord(parsed)) {
108
- store = parsed;
123
+ const maxReadAttempts = process.platform === "win32" ? 3 : 1;
124
+ const retryBuf = maxReadAttempts > 1 ? new Int32Array(new SharedArrayBuffer(4)) : undefined;
125
+ for (let attempt = 0; attempt < maxReadAttempts; attempt++) {
126
+ try {
127
+ const raw = fs.readFileSync(storePath, "utf-8");
128
+ if (raw.length === 0 && attempt < maxReadAttempts - 1) {
129
+ // File is empty — likely caught mid-write; retry after a brief pause.
130
+ Atomics.wait(retryBuf, 0, 0, 50);
131
+ continue;
132
+ }
133
+ const parsed = JSON.parse(raw);
134
+ if (isSessionStoreRecord(parsed)) {
135
+ store = parsed;
136
+ }
137
+ mtimeMs = getFileMtimeMs(storePath) ?? mtimeMs;
138
+ break;
139
+ }
140
+ catch {
141
+ // File missing, locked, or transiently corrupt — retry on Windows.
142
+ if (attempt < maxReadAttempts - 1) {
143
+ Atomics.wait(retryBuf, 0, 0, 50);
144
+ continue;
145
+ }
146
+ // Final attempt failed; proceed with an empty store.
109
147
  }
110
- mtimeMs = getFileMtimeMs(storePath) ?? mtimeMs;
111
- }
112
- catch {
113
- // ignore missing/invalid store; we'll recreate it
114
148
  }
115
149
  // Best-effort migration: message provider → channel naming.
116
150
  for (const entry of Object.values(store)) {
@@ -216,6 +250,7 @@ export function pruneStaleEntries(store, overrideMaxAgeMs, opts = {}) {
216
250
  let pruned = 0;
217
251
  for (const [key, entry] of Object.entries(store)) {
218
252
  if (entry?.updatedAt != null && entry.updatedAt < cutoffMs) {
253
+ opts.onPruned?.({ key, entry });
219
254
  delete store[key];
220
255
  pruned++;
221
256
  }
@@ -376,19 +411,71 @@ async function saveSessionStoreUnlocked(storePath, store, opts) {
376
411
  }
377
412
  else {
378
413
  // Prune stale entries and cap total count before serializing.
379
- pruneStaleEntries(store, maintenance.pruneAfterMs);
414
+ const prunedSessionFiles = new Map();
415
+ pruneStaleEntries(store, maintenance.pruneAfterMs, {
416
+ onPruned: ({ entry }) => {
417
+ if (!prunedSessionFiles.has(entry.sessionId) || entry.sessionFile) {
418
+ prunedSessionFiles.set(entry.sessionId, entry.sessionFile);
419
+ }
420
+ },
421
+ });
380
422
  capEntryCount(store, maintenance.maxEntries);
423
+ const archivedDirs = new Set();
424
+ for (const [sessionId, sessionFile] of prunedSessionFiles) {
425
+ const archived = archiveSessionTranscripts({
426
+ sessionId,
427
+ storePath,
428
+ sessionFile,
429
+ reason: "deleted",
430
+ });
431
+ for (const archivedPath of archived) {
432
+ archivedDirs.add(path.dirname(archivedPath));
433
+ }
434
+ }
435
+ if (archivedDirs.size > 0) {
436
+ await cleanupArchivedSessionTranscripts({
437
+ directories: [...archivedDirs],
438
+ olderThanMs: maintenance.pruneAfterMs,
439
+ reason: "deleted",
440
+ });
441
+ }
381
442
  // Rotate the on-disk file if it exceeds the size threshold.
382
443
  await rotateSessionFile(storePath, maintenance.rotateBytes);
383
444
  }
384
445
  }
385
446
  await fs.promises.mkdir(path.dirname(storePath), { recursive: true });
386
447
  const json = JSON.stringify(store, null, 2);
387
- // Windows: avoid atomic rename swaps (can be flaky under concurrent access).
388
- // We serialize writers via the session-store lock instead.
448
+ // Windows: use temp-file + rename for atomic writes, same as other platforms.
449
+ // Direct `writeFile` truncates the target to 0 bytes before writing, which
450
+ // allows concurrent `readFileSync` calls (from unlocked `loadSessionStore`)
451
+ // to observe an empty file and lose the session store contents.
389
452
  if (process.platform === "win32") {
453
+ const tmp = `${storePath}.${process.pid}.${crypto.randomUUID()}.tmp`;
390
454
  try {
391
- await fs.promises.writeFile(storePath, json, "utf-8");
455
+ await fs.promises.writeFile(tmp, json, "utf-8");
456
+ // Retry rename up to 5 times with increasing backoff — rename can fail
457
+ // on Windows when the target is locked by a concurrent reader. We do
458
+ // NOT fall back to writeFile or copyFile because both use CREATE_ALWAYS
459
+ // on Windows, which truncates the target to 0 bytes before writing —
460
+ // reintroducing the exact race this fix addresses. If all attempts
461
+ // fail, the temp file is cleaned up and the next save cycle (which is
462
+ // serialized by the write lock) will succeed.
463
+ for (let i = 0; i < 5; i++) {
464
+ try {
465
+ await fs.promises.rename(tmp, storePath);
466
+ break;
467
+ }
468
+ catch {
469
+ if (i < 4) {
470
+ await new Promise((r) => setTimeout(r, 50 * (i + 1)));
471
+ }
472
+ // Final attempt failed — skip this save. The write lock ensures
473
+ // the next save will retry with fresh data. Log for diagnostics.
474
+ if (i === 4) {
475
+ console.warn(`[session-store] rename failed after 5 attempts: ${storePath}`);
476
+ }
477
+ }
478
+ }
392
479
  }
393
480
  catch (err) {
394
481
  const code = err && typeof err === "object" && "code" in err
@@ -399,6 +486,9 @@ async function saveSessionStoreUnlocked(storePath, store, opts) {
399
486
  }
400
487
  throw err;
401
488
  }
489
+ finally {
490
+ await fs.promises.rm(tmp, { force: true }).catch(() => undefined);
491
+ }
402
492
  return;
403
493
  }
404
494
  const tmp = `${storePath}.${process.pid}.${crypto.randomUUID()}.tmp`;
@@ -451,67 +541,97 @@ export async function updateSessionStore(storePath, mutator, opts) {
451
541
  return result;
452
542
  });
453
543
  }
454
- async function withSessionStoreLock(storePath, fn, opts = {}) {
455
- const timeoutMs = opts.timeoutMs ?? 10_000;
456
- const pollIntervalMs = opts.pollIntervalMs ?? 25;
457
- const staleMs = opts.staleMs ?? 30_000;
458
- const lockPath = `${storePath}.lock`;
459
- const startedAt = Date.now();
460
- await fs.promises.mkdir(path.dirname(storePath), { recursive: true });
461
- while (true) {
462
- try {
463
- const handle = await fs.promises.open(lockPath, "wx");
464
- try {
465
- await handle.writeFile(JSON.stringify({ pid: process.pid, startedAt: Date.now() }), "utf-8");
466
- }
467
- catch {
468
- // best-effort
544
+ const LOCK_QUEUES = new Map();
545
+ function lockTimeoutError(storePath) {
546
+ return new Error(`timeout waiting for session store lock: ${storePath}`);
547
+ }
548
+ function getOrCreateLockQueue(storePath) {
549
+ const existing = LOCK_QUEUES.get(storePath);
550
+ if (existing) {
551
+ return existing;
552
+ }
553
+ const created = { running: false, pending: [] };
554
+ LOCK_QUEUES.set(storePath, created);
555
+ return created;
556
+ }
557
+ async function drainSessionStoreLockQueue(storePath) {
558
+ const queue = LOCK_QUEUES.get(storePath);
559
+ if (!queue || queue.running) {
560
+ return;
561
+ }
562
+ queue.running = true;
563
+ try {
564
+ while (queue.pending.length > 0) {
565
+ const task = queue.pending.shift();
566
+ if (!task) {
567
+ continue;
469
568
  }
470
- await handle.close();
471
- break;
472
- }
473
- catch (err) {
474
- const code = err && typeof err === "object" && "code" in err
475
- ? String(err.code)
476
- : null;
477
- if (code === "ENOENT") {
478
- // Store directory may be deleted/recreated in tests while writes are in-flight.
479
- // Best-effort: recreate the parent dir and retry until timeout.
480
- await fs.promises
481
- .mkdir(path.dirname(storePath), { recursive: true })
482
- .catch(() => undefined);
483
- await new Promise((r) => setTimeout(r, pollIntervalMs));
569
+ const remainingTimeoutMs = task.timeoutMs ?? Number.POSITIVE_INFINITY;
570
+ if (task.timeoutMs != null && remainingTimeoutMs <= 0) {
571
+ task.reject(lockTimeoutError(storePath));
484
572
  continue;
485
573
  }
486
- if (code !== "EEXIST") {
487
- throw err;
574
+ let lock;
575
+ let result;
576
+ let failed;
577
+ let hasFailure = false;
578
+ try {
579
+ lock = await acquireSessionWriteLock({
580
+ sessionFile: storePath,
581
+ timeoutMs: remainingTimeoutMs,
582
+ staleMs: task.staleMs,
583
+ });
584
+ result = await task.fn();
488
585
  }
489
- const now = Date.now();
490
- if (now - startedAt > timeoutMs) {
491
- throw new Error(`timeout acquiring session store lock: ${lockPath}`, { cause: err });
586
+ catch (err) {
587
+ hasFailure = true;
588
+ failed = err;
492
589
  }
493
- // Best-effort stale lock eviction (e.g. crashed process).
494
- try {
495
- const st = await fs.promises.stat(lockPath);
496
- const ageMs = now - st.mtimeMs;
497
- if (ageMs > staleMs) {
498
- await fs.promises.unlink(lockPath);
499
- continue;
500
- }
590
+ finally {
591
+ await lock?.release().catch(() => undefined);
501
592
  }
502
- catch {
503
- // ignore
593
+ if (hasFailure) {
594
+ task.reject(failed);
595
+ continue;
504
596
  }
505
- await new Promise((r) => setTimeout(r, pollIntervalMs));
597
+ task.resolve(result);
506
598
  }
507
599
  }
508
- try {
509
- return await fn();
510
- }
511
600
  finally {
512
- await fs.promises.unlink(lockPath).catch(() => undefined);
601
+ queue.running = false;
602
+ if (queue.pending.length === 0) {
603
+ LOCK_QUEUES.delete(storePath);
604
+ }
605
+ else {
606
+ queueMicrotask(() => {
607
+ void drainSessionStoreLockQueue(storePath);
608
+ });
609
+ }
513
610
  }
514
611
  }
612
+ async function withSessionStoreLock(storePath, fn, opts = {}) {
613
+ if (!storePath || typeof storePath !== "string") {
614
+ throw new Error(`withSessionStoreLock: storePath must be a non-empty string, got ${JSON.stringify(storePath)}`);
615
+ }
616
+ const timeoutMs = opts.timeoutMs ?? 10_000;
617
+ const staleMs = opts.staleMs ?? 30_000;
618
+ // `pollIntervalMs` is retained for API compatibility with older lock options.
619
+ void opts.pollIntervalMs;
620
+ const hasTimeout = timeoutMs > 0 && Number.isFinite(timeoutMs);
621
+ const queue = getOrCreateLockQueue(storePath);
622
+ const promise = new Promise((resolve, reject) => {
623
+ const task = {
624
+ fn: async () => await fn(),
625
+ resolve: (value) => resolve(value),
626
+ reject,
627
+ timeoutMs: hasTimeout ? timeoutMs : undefined,
628
+ staleMs,
629
+ };
630
+ queue.pending.push(task);
631
+ void drainSessionStoreLockQueue(storePath);
632
+ });
633
+ return await promise;
634
+ }
515
635
  export async function updateSessionStoreEntry(params) {
516
636
  const { storePath, sessionKey, update } = params;
517
637
  return await withSessionStoreLock(storePath, async () => {
@@ -2,10 +2,24 @@ import crypto from "node:crypto";
2
2
  export function mergeSessionEntry(existing, patch) {
3
3
  const sessionId = patch.sessionId ?? existing?.sessionId ?? crypto.randomUUID();
4
4
  const updatedAt = Math.max(existing?.updatedAt ?? 0, patch.updatedAt ?? 0, Date.now());
5
- if (!existing)
5
+ if (!existing) {
6
6
  return { ...patch, sessionId, updatedAt };
7
+ }
7
8
  return { ...existing, ...patch, sessionId, updatedAt };
8
9
  }
10
+ export function resolveFreshSessionTotalTokens(entry) {
11
+ const total = entry?.totalTokens;
12
+ if (typeof total !== "number" || !Number.isFinite(total) || total < 0) {
13
+ return undefined;
14
+ }
15
+ if (entry?.totalTokensFresh === false) {
16
+ return undefined;
17
+ }
18
+ return total;
19
+ }
20
+ export function isSessionTotalTokensFresh(entry) {
21
+ return resolveFreshSessionTotalTokens(entry) !== undefined;
22
+ }
9
23
  export const DEFAULT_RESET_TRIGGER = "/new";
10
24
  export const DEFAULT_RESET_TRIGGERS = ["/new", "/reset"];
11
25
  export const DEFAULT_IDLE_MINUTES = 60;
@@ -7,3 +7,4 @@ export * from "./sessions/session-key.js";
7
7
  export * from "./sessions/store.js";
8
8
  export * from "./sessions/types.js";
9
9
  export * from "./sessions/transcript.js";
10
+ export * from "./sessions/delivery-info.js";
@@ -1,10 +1,11 @@
1
1
  export const TELEGRAM_COMMAND_NAME_PATTERN = /^[a-z0-9_]{1,32}$/;
2
2
  export function normalizeTelegramCommandName(value) {
3
3
  const trimmed = value.trim();
4
- if (!trimmed)
4
+ if (!trimmed) {
5
5
  return "";
6
+ }
6
7
  const withoutSlash = trimmed.startsWith("/") ? trimmed.slice(1) : trimmed;
7
- return withoutSlash.trim().toLowerCase();
8
+ return withoutSlash.trim().toLowerCase().replace(/-/g, "_");
8
9
  }
9
10
  export function normalizeTelegramCommandDescription(value) {
10
11
  return value.trim();
@@ -13,6 +13,7 @@ export * from "./types.googlechat.js";
13
13
  export * from "./types.gateway.js";
14
14
  export * from "./types.hooks.js";
15
15
  export * from "./types.imessage.js";
16
+ export * from "./types.irc.js";
16
17
  export * from "./types.messages.js";
17
18
  export * from "./types.models.js";
18
19
  export * from "./types.node-host.js";
@@ -27,3 +28,4 @@ export * from "./types.telegram.js";
27
28
  export * from "./types.tts.js";
28
29
  export * from "./types.tools.js";
29
30
  export * from "./types.whatsapp.js";
31
+ export * from "./types.memory.js";
@@ -1,5 +1,5 @@
1
1
  import { z } from "zod";
2
- import { HeartbeatSchema, MemorySearchSchema, SandboxBrowserSchema, SandboxDockerSchema, SandboxPruneSchema, } from "./zod-schema.agent-runtime.js";
2
+ import { HeartbeatSchema, AgentSandboxSchema, AgentModelSchema, MemorySearchSchema, } from "./zod-schema.agent-runtime.js";
3
3
  import { BlockStreamingChunkSchema, BlockStreamingCoalesceSchema, CliBackendSchema, HumanDelaySchema, } from "./zod-schema.core.js";
4
4
  export const AgentDefaultsSchema = z
5
5
  .object({
@@ -32,6 +32,7 @@ export const AgentDefaultsSchema = z
32
32
  repoRoot: z.string().optional(),
33
33
  skipBootstrap: z.boolean().optional(),
34
34
  bootstrapMaxChars: z.number().int().positive().optional(),
35
+ bootstrapTotalMaxChars: z.number().int().positive().optional(),
35
36
  userTimezone: z.string().optional(),
36
37
  timeFormat: z.union([z.literal("auto"), z.literal("12"), z.literal("24")]).optional(),
37
38
  envelopeTimezone: z.string().optional(),
@@ -111,6 +112,7 @@ export const AgentDefaultsSchema = z
111
112
  humanDelay: HumanDelaySchema.optional(),
112
113
  timeoutSeconds: z.number().int().positive().optional(),
113
114
  mediaMaxMb: z.number().positive().optional(),
115
+ imageMaxDimensionPx: z.number().int().positive().optional(),
114
116
  typingIntervalSeconds: z.number().int().positive().optional(),
115
117
  typingMode: z
116
118
  .union([
@@ -138,37 +140,14 @@ export const AgentDefaultsSchema = z
138
140
  .min(1)
139
141
  .max(20)
140
142
  .optional()
141
- .describe("Maximum active children a single requester session may spawn. Default: 5."),
143
+ .describe("Maximum number of active children a single agent session can spawn (default: 5)."),
142
144
  archiveAfterMinutes: z.number().int().positive().optional(),
143
- model: z
144
- .union([
145
- z.string(),
146
- z
147
- .object({
148
- primary: z.string().optional(),
149
- fallbacks: z.array(z.string()).optional(),
150
- })
151
- .strict(),
152
- ])
153
- .optional(),
145
+ model: AgentModelSchema.optional(),
154
146
  thinking: z.string().optional(),
155
147
  })
156
148
  .strict()
157
149
  .optional(),
158
- sandbox: z
159
- .object({
160
- mode: z.union([z.literal("off"), z.literal("non-main"), z.literal("all")]).optional(),
161
- workspaceAccess: z.union([z.literal("none"), z.literal("ro"), z.literal("rw")]).optional(),
162
- sessionToolsVisibility: z.union([z.literal("spawned"), z.literal("all")]).optional(),
163
- scope: z.union([z.literal("session"), z.literal("agent"), z.literal("shared")]).optional(),
164
- perSession: z.boolean().optional(),
165
- workspaceRoot: z.string().optional(),
166
- docker: SandboxDockerSchema,
167
- browser: SandboxBrowserSchema,
168
- prune: SandboxPruneSchema,
169
- })
170
- .strict()
171
- .optional(),
150
+ sandbox: AgentSandboxSchema,
172
151
  })
173
152
  .strict()
174
153
  .optional();