@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,20 +1,60 @@
1
1
  import fs from "node:fs";
2
2
  import os from "node:os";
3
3
  import path from "node:path";
4
- import { resolveSessionTranscriptPath } from "../config/sessions.js";
4
+ import { resolveSessionFilePath, resolveSessionTranscriptPath, resolveSessionTranscriptPathInDir, } from "../config/sessions.js";
5
5
  import { resolveRequiredHomeDir } from "../infra/home-dir.js";
6
+ import { hasInterSessionUserProvenance } from "../sessions/input-provenance.js";
6
7
  import { extractToolCallNames, hasToolCall } from "../utils/transcript-tools.js";
7
8
  import { stripEnvelope } from "./chat-sanitize.js";
9
+ const sessionTitleFieldsCache = new Map();
10
+ const MAX_SESSION_TITLE_FIELDS_CACHE_ENTRIES = 5000;
11
+ function readSessionTitleFieldsCacheKey(filePath, opts) {
12
+ const includeInterSession = opts?.includeInterSession === true ? "1" : "0";
13
+ return `${filePath}\t${includeInterSession}`;
14
+ }
15
+ function getCachedSessionTitleFields(cacheKey, stat) {
16
+ const cached = sessionTitleFieldsCache.get(cacheKey);
17
+ if (!cached) {
18
+ return null;
19
+ }
20
+ if (cached.mtimeMs !== stat.mtimeMs || cached.size !== stat.size) {
21
+ sessionTitleFieldsCache.delete(cacheKey);
22
+ return null;
23
+ }
24
+ // LRU bump
25
+ sessionTitleFieldsCache.delete(cacheKey);
26
+ sessionTitleFieldsCache.set(cacheKey, cached);
27
+ return {
28
+ firstUserMessage: cached.firstUserMessage,
29
+ lastMessagePreview: cached.lastMessagePreview,
30
+ };
31
+ }
32
+ function setCachedSessionTitleFields(cacheKey, stat, value) {
33
+ sessionTitleFieldsCache.set(cacheKey, {
34
+ ...value,
35
+ mtimeMs: stat.mtimeMs,
36
+ size: stat.size,
37
+ });
38
+ while (sessionTitleFieldsCache.size > MAX_SESSION_TITLE_FIELDS_CACHE_ENTRIES) {
39
+ const oldestKey = sessionTitleFieldsCache.keys().next().value;
40
+ if (typeof oldestKey !== "string" || !oldestKey) {
41
+ break;
42
+ }
43
+ sessionTitleFieldsCache.delete(oldestKey);
44
+ }
45
+ }
8
46
  export function readSessionMessages(sessionId, storePath, sessionFile) {
9
47
  const candidates = resolveSessionTranscriptCandidates(sessionId, storePath, sessionFile);
10
48
  const filePath = candidates.find((p) => fs.existsSync(p));
11
- if (!filePath)
49
+ if (!filePath) {
12
50
  return [];
51
+ }
13
52
  const lines = fs.readFileSync(filePath, "utf-8").split(/\r?\n/);
14
53
  const messages = [];
15
54
  for (const line of lines) {
16
- if (!line.trim())
55
+ if (!line.trim()) {
17
56
  continue;
57
+ }
18
58
  try {
19
59
  const parsed = JSON.parse(line);
20
60
  if (parsed?.message) {
@@ -45,18 +85,39 @@ export function readSessionMessages(sessionId, storePath, sessionFile) {
45
85
  }
46
86
  export function resolveSessionTranscriptCandidates(sessionId, storePath, sessionFile, agentId) {
47
87
  const candidates = [];
48
- if (sessionFile)
49
- candidates.push(sessionFile);
88
+ const pushCandidate = (resolve) => {
89
+ try {
90
+ candidates.push(resolve());
91
+ }
92
+ catch {
93
+ // Ignore invalid paths/IDs and keep scanning other safe candidates.
94
+ }
95
+ };
50
96
  if (storePath) {
51
- const dir = path.dirname(storePath);
52
- candidates.push(path.join(dir, `${sessionId}.jsonl`));
97
+ const sessionsDir = path.dirname(storePath);
98
+ if (sessionFile) {
99
+ pushCandidate(() => resolveSessionFilePath(sessionId, { sessionFile }, { sessionsDir, agentId }));
100
+ }
101
+ pushCandidate(() => resolveSessionTranscriptPathInDir(sessionId, sessionsDir));
102
+ }
103
+ else if (sessionFile) {
104
+ if (agentId) {
105
+ pushCandidate(() => resolveSessionFilePath(sessionId, { sessionFile }, { agentId }));
106
+ }
107
+ else {
108
+ const trimmed = sessionFile.trim();
109
+ if (trimmed) {
110
+ candidates.push(path.resolve(trimmed));
111
+ }
112
+ }
53
113
  }
54
114
  if (agentId) {
55
- candidates.push(resolveSessionTranscriptPath(sessionId, agentId));
115
+ pushCandidate(() => resolveSessionTranscriptPath(sessionId, agentId));
56
116
  }
57
117
  const home = resolveRequiredHomeDir(process.env, os.homedir);
58
- candidates.push(path.join(home, ".poolbot", "sessions", `${sessionId}.jsonl`));
59
- return candidates;
118
+ const legacyDir = path.join(home, ".poolbot", "sessions");
119
+ pushCandidate(() => resolveSessionTranscriptPathInDir(sessionId, legacyDir));
120
+ return Array.from(new Set(candidates));
60
121
  }
61
122
  export function archiveFileOnDisk(filePath, reason) {
62
123
  const ts = new Date().toISOString().replaceAll(":", "-");
@@ -64,6 +125,76 @@ export function archiveFileOnDisk(filePath, reason) {
64
125
  fs.renameSync(filePath, archived);
65
126
  return archived;
66
127
  }
128
+ /**
129
+ * Archives all transcript files for a given session.
130
+ * Best-effort: silently skips files that don't exist or fail to rename.
131
+ */
132
+ export function archiveSessionTranscripts(opts) {
133
+ const archived = [];
134
+ for (const candidate of resolveSessionTranscriptCandidates(opts.sessionId, opts.storePath, opts.sessionFile, opts.agentId)) {
135
+ if (!fs.existsSync(candidate)) {
136
+ continue;
137
+ }
138
+ try {
139
+ archived.push(archiveFileOnDisk(candidate, opts.reason));
140
+ }
141
+ catch {
142
+ // Best-effort.
143
+ }
144
+ }
145
+ return archived;
146
+ }
147
+ function restoreArchiveTimestamp(raw) {
148
+ const [datePart, timePart] = raw.split("T");
149
+ if (!datePart || !timePart) {
150
+ return raw;
151
+ }
152
+ return `${datePart}T${timePart.replace(/-/g, ":")}`;
153
+ }
154
+ function parseArchivedTimestamp(fileName, reason) {
155
+ const marker = `.${reason}.`;
156
+ const index = fileName.lastIndexOf(marker);
157
+ if (index < 0) {
158
+ return null;
159
+ }
160
+ const raw = fileName.slice(index + marker.length);
161
+ if (!raw) {
162
+ return null;
163
+ }
164
+ const timestamp = Date.parse(restoreArchiveTimestamp(raw));
165
+ return Number.isNaN(timestamp) ? null : timestamp;
166
+ }
167
+ export async function cleanupArchivedSessionTranscripts(opts) {
168
+ if (!Number.isFinite(opts.olderThanMs) || opts.olderThanMs < 0) {
169
+ return { removed: 0, scanned: 0 };
170
+ }
171
+ const now = opts.nowMs ?? Date.now();
172
+ const reason = opts.reason ?? "deleted";
173
+ const directories = Array.from(new Set(opts.directories.map((dir) => path.resolve(dir))));
174
+ let removed = 0;
175
+ let scanned = 0;
176
+ for (const dir of directories) {
177
+ const entries = await fs.promises.readdir(dir).catch(() => []);
178
+ for (const entry of entries) {
179
+ const timestamp = parseArchivedTimestamp(entry, reason);
180
+ if (timestamp == null) {
181
+ continue;
182
+ }
183
+ scanned += 1;
184
+ if (now - timestamp <= opts.olderThanMs) {
185
+ continue;
186
+ }
187
+ const fullPath = path.join(dir, entry);
188
+ const stat = await fs.promises.stat(fullPath).catch(() => null);
189
+ if (!stat?.isFile()) {
190
+ continue;
191
+ }
192
+ await fs.promises.rm(fullPath).catch(() => undefined);
193
+ removed += 1;
194
+ }
195
+ }
196
+ return { removed, scanned };
197
+ }
67
198
  function jsonUtf8Bytes(value) {
68
199
  try {
69
200
  return Buffer.byteLength(JSON.stringify(value), "utf8");
@@ -73,8 +204,9 @@ function jsonUtf8Bytes(value) {
73
204
  }
74
205
  }
75
206
  export function capArrayByJsonBytes(items, maxBytes) {
76
- if (items.length === 0)
207
+ if (items.length === 0) {
77
208
  return { items, bytes: 2 };
209
+ }
78
210
  const parts = items.map((item) => jsonUtf8Bytes(item));
79
211
  let bytes = 2 + parts.reduce((a, b) => a + b, 0) + (items.length - 1);
80
212
  let start = 0;
@@ -86,113 +218,210 @@ export function capArrayByJsonBytes(items, maxBytes) {
86
218
  return { items: next, bytes };
87
219
  }
88
220
  const MAX_LINES_TO_SCAN = 10;
221
+ export function readSessionTitleFieldsFromTranscript(sessionId, storePath, sessionFile, agentId, opts) {
222
+ const candidates = resolveSessionTranscriptCandidates(sessionId, storePath, sessionFile, agentId);
223
+ const filePath = candidates.find((p) => fs.existsSync(p));
224
+ if (!filePath) {
225
+ return { firstUserMessage: null, lastMessagePreview: null };
226
+ }
227
+ let stat;
228
+ try {
229
+ stat = fs.statSync(filePath);
230
+ }
231
+ catch {
232
+ return { firstUserMessage: null, lastMessagePreview: null };
233
+ }
234
+ const cacheKey = readSessionTitleFieldsCacheKey(filePath, opts);
235
+ const cached = getCachedSessionTitleFields(cacheKey, stat);
236
+ if (cached) {
237
+ return cached;
238
+ }
239
+ if (stat.size === 0) {
240
+ const empty = { firstUserMessage: null, lastMessagePreview: null };
241
+ setCachedSessionTitleFields(cacheKey, stat, empty);
242
+ return empty;
243
+ }
244
+ let fd = null;
245
+ try {
246
+ fd = fs.openSync(filePath, "r");
247
+ const size = stat.size;
248
+ // Head (first user message)
249
+ let firstUserMessage = null;
250
+ try {
251
+ const chunk = readTranscriptHeadChunk(fd);
252
+ if (chunk) {
253
+ firstUserMessage = extractFirstUserMessageFromTranscriptChunk(chunk, opts);
254
+ }
255
+ }
256
+ catch {
257
+ // ignore head read errors
258
+ }
259
+ // Tail (last message preview)
260
+ let lastMessagePreview = null;
261
+ try {
262
+ lastMessagePreview = readLastMessagePreviewFromOpenTranscript({ fd, size });
263
+ }
264
+ catch {
265
+ // ignore tail read errors
266
+ }
267
+ const result = { firstUserMessage, lastMessagePreview };
268
+ setCachedSessionTitleFields(cacheKey, stat, result);
269
+ return result;
270
+ }
271
+ catch {
272
+ return { firstUserMessage: null, lastMessagePreview: null };
273
+ }
274
+ finally {
275
+ if (fd !== null) {
276
+ try {
277
+ fs.closeSync(fd);
278
+ }
279
+ catch {
280
+ /* ignore */
281
+ }
282
+ }
283
+ }
284
+ }
89
285
  function extractTextFromContent(content) {
90
- if (typeof content === "string")
286
+ if (typeof content === "string") {
91
287
  return content.trim() || null;
92
- if (!Array.isArray(content))
288
+ }
289
+ if (!Array.isArray(content)) {
93
290
  return null;
291
+ }
94
292
  for (const part of content) {
95
- if (!part || typeof part.text !== "string")
293
+ if (!part || typeof part.text !== "string") {
96
294
  continue;
295
+ }
97
296
  if (part.type === "text" || part.type === "output_text" || part.type === "input_text") {
98
297
  const trimmed = part.text.trim();
99
- if (trimmed)
298
+ if (trimmed) {
100
299
  return trimmed;
300
+ }
301
+ }
302
+ }
303
+ return null;
304
+ }
305
+ function readTranscriptHeadChunk(fd, maxBytes = 8192) {
306
+ const buf = Buffer.alloc(maxBytes);
307
+ const bytesRead = fs.readSync(fd, buf, 0, buf.length, 0);
308
+ if (bytesRead <= 0) {
309
+ return null;
310
+ }
311
+ return buf.toString("utf-8", 0, bytesRead);
312
+ }
313
+ function extractFirstUserMessageFromTranscriptChunk(chunk, opts) {
314
+ const lines = chunk.split(/\r?\n/).slice(0, MAX_LINES_TO_SCAN);
315
+ for (const line of lines) {
316
+ if (!line.trim()) {
317
+ continue;
318
+ }
319
+ try {
320
+ const parsed = JSON.parse(line);
321
+ const msg = parsed?.message;
322
+ if (msg?.role !== "user") {
323
+ continue;
324
+ }
325
+ if (opts?.includeInterSession !== true && hasInterSessionUserProvenance(msg)) {
326
+ continue;
327
+ }
328
+ const text = extractTextFromContent(msg.content);
329
+ if (text) {
330
+ return text;
331
+ }
332
+ }
333
+ catch {
334
+ // skip malformed lines
101
335
  }
102
336
  }
103
337
  return null;
104
338
  }
105
- export function readFirstUserMessageFromTranscript(sessionId, storePath, sessionFile, agentId) {
339
+ export function readFirstUserMessageFromTranscript(sessionId, storePath, sessionFile, agentId, opts) {
106
340
  const candidates = resolveSessionTranscriptCandidates(sessionId, storePath, sessionFile, agentId);
107
341
  const filePath = candidates.find((p) => fs.existsSync(p));
108
- if (!filePath)
342
+ if (!filePath) {
109
343
  return null;
344
+ }
110
345
  let fd = null;
111
346
  try {
112
347
  fd = fs.openSync(filePath, "r");
113
- const buf = Buffer.alloc(8192);
114
- const bytesRead = fs.readSync(fd, buf, 0, buf.length, 0);
115
- if (bytesRead === 0)
348
+ const chunk = readTranscriptHeadChunk(fd);
349
+ if (!chunk) {
116
350
  return null;
117
- const chunk = buf.toString("utf-8", 0, bytesRead);
118
- const lines = chunk.split(/\r?\n/).slice(0, MAX_LINES_TO_SCAN);
119
- for (const line of lines) {
120
- if (!line.trim())
121
- continue;
122
- try {
123
- const parsed = JSON.parse(line);
124
- const msg = parsed?.message;
125
- if (msg?.role === "user") {
126
- const text = extractTextFromContent(msg.content);
127
- if (text)
128
- return text;
129
- }
130
- }
131
- catch {
132
- // skip malformed lines
133
- }
134
351
  }
352
+ return extractFirstUserMessageFromTranscriptChunk(chunk, opts);
135
353
  }
136
354
  catch {
137
355
  // file read error
138
356
  }
139
357
  finally {
140
- if (fd !== null)
358
+ if (fd !== null) {
141
359
  fs.closeSync(fd);
360
+ }
142
361
  }
143
362
  return null;
144
363
  }
145
364
  const LAST_MSG_MAX_BYTES = 16384;
146
365
  const LAST_MSG_MAX_LINES = 20;
366
+ function readLastMessagePreviewFromOpenTranscript(params) {
367
+ const readStart = Math.max(0, params.size - LAST_MSG_MAX_BYTES);
368
+ const readLen = Math.min(params.size, LAST_MSG_MAX_BYTES);
369
+ const buf = Buffer.alloc(readLen);
370
+ fs.readSync(params.fd, buf, 0, readLen, readStart);
371
+ const chunk = buf.toString("utf-8");
372
+ const lines = chunk.split(/\r?\n/).filter((l) => l.trim());
373
+ const tailLines = lines.slice(-LAST_MSG_MAX_LINES);
374
+ for (let i = tailLines.length - 1; i >= 0; i--) {
375
+ const line = tailLines[i];
376
+ try {
377
+ const parsed = JSON.parse(line);
378
+ const msg = parsed?.message;
379
+ if (msg?.role !== "user" && msg?.role !== "assistant") {
380
+ continue;
381
+ }
382
+ const text = extractTextFromContent(msg.content);
383
+ if (text) {
384
+ return text;
385
+ }
386
+ }
387
+ catch {
388
+ // skip malformed
389
+ }
390
+ }
391
+ return null;
392
+ }
147
393
  export function readLastMessagePreviewFromTranscript(sessionId, storePath, sessionFile, agentId) {
148
394
  const candidates = resolveSessionTranscriptCandidates(sessionId, storePath, sessionFile, agentId);
149
395
  const filePath = candidates.find((p) => fs.existsSync(p));
150
- if (!filePath)
396
+ if (!filePath) {
151
397
  return null;
398
+ }
152
399
  let fd = null;
153
400
  try {
154
401
  fd = fs.openSync(filePath, "r");
155
402
  const stat = fs.fstatSync(fd);
156
403
  const size = stat.size;
157
- if (size === 0)
404
+ if (size === 0) {
158
405
  return null;
159
- const readStart = Math.max(0, size - LAST_MSG_MAX_BYTES);
160
- const readLen = Math.min(size, LAST_MSG_MAX_BYTES);
161
- const buf = Buffer.alloc(readLen);
162
- fs.readSync(fd, buf, 0, readLen, readStart);
163
- const chunk = buf.toString("utf-8");
164
- const lines = chunk.split(/\r?\n/).filter((l) => l.trim());
165
- const tailLines = lines.slice(-LAST_MSG_MAX_LINES);
166
- for (let i = tailLines.length - 1; i >= 0; i--) {
167
- const line = tailLines[i];
168
- try {
169
- const parsed = JSON.parse(line);
170
- const msg = parsed?.message;
171
- if (msg?.role === "user" || msg?.role === "assistant") {
172
- const text = extractTextFromContent(msg.content);
173
- if (text)
174
- return text;
175
- }
176
- }
177
- catch {
178
- // skip malformed
179
- }
180
406
  }
407
+ return readLastMessagePreviewFromOpenTranscript({ fd, size });
181
408
  }
182
409
  catch {
183
410
  // file error
184
411
  }
185
412
  finally {
186
- if (fd !== null)
413
+ if (fd !== null) {
187
414
  fs.closeSync(fd);
415
+ }
188
416
  }
189
417
  return null;
190
418
  }
191
419
  const PREVIEW_READ_SIZES = [64 * 1024, 256 * 1024, 1024 * 1024];
192
420
  const PREVIEW_MAX_LINES = 200;
193
421
  function normalizeRole(role, isTool) {
194
- if (isTool)
422
+ if (isTool) {
195
423
  return "tool";
424
+ }
196
425
  switch ((role ?? "").toLowerCase()) {
197
426
  case "user":
198
427
  return "user";
@@ -207,10 +436,12 @@ function normalizeRole(role, isTool) {
207
436
  }
208
437
  }
209
438
  function truncatePreviewText(text, maxChars) {
210
- if (maxChars <= 0 || text.length <= maxChars)
439
+ if (maxChars <= 0 || text.length <= maxChars) {
211
440
  return text;
212
- if (maxChars <= 3)
441
+ }
442
+ if (maxChars <= 3) {
213
443
  return text.slice(0, maxChars);
444
+ }
214
445
  return `${text.slice(0, maxChars - 3)}...`;
215
446
  }
216
447
  function extractPreviewText(message) {
@@ -239,12 +470,14 @@ function extractToolNames(message) {
239
470
  return extractToolCallNames(message);
240
471
  }
241
472
  function extractMediaSummary(message) {
242
- if (!Array.isArray(message.content))
473
+ if (!Array.isArray(message.content)) {
243
474
  return null;
475
+ }
244
476
  for (const entry of message.content) {
245
477
  const raw = typeof entry?.type === "string" ? entry.type.trim().toLowerCase() : "";
246
- if (!raw || raw === "text" || raw === "toolcall" || raw === "tool_call")
478
+ if (!raw || raw === "text" || raw === "toolcall" || raw === "tool_call") {
247
479
  continue;
480
+ }
248
481
  return `[${raw}]`;
249
482
  }
250
483
  return null;
@@ -261,26 +494,30 @@ function buildPreviewItems(messages, maxItems, maxChars) {
261
494
  const shown = toolNames.slice(0, 2);
262
495
  const overflow = toolNames.length - shown.length;
263
496
  text = `call ${shown.join(", ")}`;
264
- if (overflow > 0)
497
+ if (overflow > 0) {
265
498
  text += ` +${overflow}`;
499
+ }
266
500
  }
267
501
  }
268
502
  if (!text) {
269
503
  text = extractMediaSummary(message);
270
504
  }
271
- if (!text)
505
+ if (!text) {
272
506
  continue;
507
+ }
273
508
  let trimmed = text.trim();
274
- if (!trimmed)
509
+ if (!trimmed) {
275
510
  continue;
511
+ }
276
512
  if (role === "user") {
277
513
  trimmed = stripEnvelope(trimmed);
278
514
  }
279
515
  trimmed = truncatePreviewText(trimmed, maxChars);
280
516
  items.push({ role, text: trimmed });
281
517
  }
282
- if (items.length <= maxItems)
518
+ if (items.length <= maxItems) {
283
519
  return items;
520
+ }
284
521
  return items.slice(-maxItems);
285
522
  }
286
523
  function readRecentMessagesFromTranscript(filePath, maxMessages, readBytes) {
@@ -289,8 +526,9 @@ function readRecentMessagesFromTranscript(filePath, maxMessages, readBytes) {
289
526
  fd = fs.openSync(filePath, "r");
290
527
  const stat = fs.fstatSync(fd);
291
528
  const size = stat.size;
292
- if (size === 0)
529
+ if (size === 0) {
293
530
  return [];
531
+ }
294
532
  const readStart = Math.max(0, size - readBytes);
295
533
  const readLen = Math.min(size, readBytes);
296
534
  const buf = Buffer.alloc(readLen);
@@ -306,8 +544,9 @@ function readRecentMessagesFromTranscript(filePath, maxMessages, readBytes) {
306
544
  const msg = parsed?.message;
307
545
  if (msg && typeof msg === "object") {
308
546
  collected.push(msg);
309
- if (collected.length >= maxMessages)
547
+ if (collected.length >= maxMessages) {
310
548
  break;
549
+ }
311
550
  }
312
551
  }
313
552
  catch {
@@ -320,15 +559,17 @@ function readRecentMessagesFromTranscript(filePath, maxMessages, readBytes) {
320
559
  return [];
321
560
  }
322
561
  finally {
323
- if (fd !== null)
562
+ if (fd !== null) {
324
563
  fs.closeSync(fd);
564
+ }
325
565
  }
326
566
  }
327
567
  export function readSessionPreviewItemsFromTranscript(sessionId, storePath, sessionFile, agentId, maxItems, maxChars) {
328
568
  const candidates = resolveSessionTranscriptCandidates(sessionId, storePath, sessionFile, agentId);
329
569
  const filePath = candidates.find((p) => fs.existsSync(p));
330
- if (!filePath)
570
+ if (!filePath) {
331
571
  return [];
572
+ }
332
573
  const boundedItems = Math.max(1, Math.min(maxItems, 50));
333
574
  const boundedChars = Math.max(20, Math.min(maxChars, 2000));
334
575
  for (const readSize of PREVIEW_READ_SIZES) {