@poolzin/pool-bot 2026.2.0 → 2026.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (258) hide show
  1. package/CHANGELOG.md +118 -0
  2. package/README-header.png +0 -0
  3. package/dist/agents/bash-tools.exec.js +76 -25
  4. package/dist/agents/cli-runner/helpers.js +9 -11
  5. package/dist/agents/context.js +1 -1
  6. package/dist/agents/identity.js +47 -7
  7. package/dist/agents/memory-search.js +25 -8
  8. package/dist/agents/model-catalog.js +1 -1
  9. package/dist/agents/model-selection.js +21 -0
  10. package/dist/agents/pi-embedded-block-chunker.js +117 -42
  11. package/dist/agents/pi-embedded-helpers/errors.js +183 -78
  12. package/dist/agents/pi-embedded-helpers.js +1 -1
  13. package/dist/agents/pi-embedded-runner/compact.js +8 -10
  14. package/dist/agents/pi-embedded-runner/model.js +62 -3
  15. package/dist/agents/pi-embedded-runner/run/attempt.js +21 -11
  16. package/dist/agents/pi-embedded-runner/run.js +199 -46
  17. package/dist/agents/pi-embedded-runner/system-prompt.js +10 -2
  18. package/dist/agents/pi-embedded-subscribe.js +118 -29
  19. package/dist/agents/pi-tools.js +10 -5
  20. package/dist/agents/poolbot-tools.js +15 -10
  21. package/dist/agents/sandbox-paths.js +31 -0
  22. package/dist/agents/session-tool-result-guard.js +94 -15
  23. package/dist/agents/shell-utils.js +51 -0
  24. package/dist/agents/skills/bundled-context.js +23 -0
  25. package/dist/agents/skills/bundled-dir.js +41 -7
  26. package/dist/agents/skills-install.js +60 -23
  27. package/dist/agents/subagent-announce.js +79 -34
  28. package/dist/agents/tool-policy.conformance.js +14 -0
  29. package/dist/agents/tool-policy.js +24 -0
  30. package/dist/agents/tools/cron-tool.js +166 -19
  31. package/dist/agents/tools/discord-actions-presence.js +78 -0
  32. package/dist/agents/tools/image-tool.js +1 -1
  33. package/dist/agents/tools/message-tool.js +56 -2
  34. package/dist/agents/tools/sessions-history-tool.js +69 -1
  35. package/dist/agents/tools/web-search.js +211 -42
  36. package/dist/agents/usage.js +23 -1
  37. package/dist/agents/workspace-run.js +67 -0
  38. package/dist/agents/workspace-templates.js +44 -0
  39. package/dist/auto-reply/command-auth.js +121 -6
  40. package/dist/auto-reply/envelope.js +74 -82
  41. package/dist/auto-reply/reply/commands-compact.js +1 -0
  42. package/dist/auto-reply/reply/commands-context-report.js +1 -0
  43. package/dist/auto-reply/reply/commands-context.js +1 -0
  44. package/dist/auto-reply/reply/commands-models.js +107 -60
  45. package/dist/auto-reply/reply/commands-ptt.js +171 -0
  46. package/dist/auto-reply/reply/get-reply-run.js +2 -1
  47. package/dist/auto-reply/reply/inbound-context.js +5 -1
  48. package/dist/auto-reply/reply/mentions.js +1 -1
  49. package/dist/auto-reply/reply/model-selection.js +3 -3
  50. package/dist/auto-reply/thinking.js +88 -43
  51. package/dist/browser/bridge-server.js +13 -0
  52. package/dist/browser/cdp.helpers.js +38 -24
  53. package/dist/browser/client-fetch.js +50 -7
  54. package/dist/browser/config.js +1 -10
  55. package/dist/browser/extension-relay.js +101 -40
  56. package/dist/browser/pw-ai.js +1 -1
  57. package/dist/browser/pw-session.js +143 -8
  58. package/dist/browser/pw-tools-core.interactions.js +125 -27
  59. package/dist/browser/pw-tools-core.responses.js +1 -1
  60. package/dist/browser/pw-tools-core.state.js +1 -1
  61. package/dist/browser/routes/agent.act.js +86 -41
  62. package/dist/browser/routes/dispatcher.js +4 -4
  63. package/dist/browser/screenshot.js +1 -1
  64. package/dist/browser/server.js +13 -0
  65. package/dist/build-info.json +3 -3
  66. package/dist/canvas-host/a2ui/index.html +28 -28
  67. package/dist/channels/reply-prefix.js +8 -1
  68. package/dist/cli/cron-cli/register.cron-add.js +61 -40
  69. package/dist/cli/cron-cli/register.cron-edit.js +60 -34
  70. package/dist/cli/cron-cli/shared.js +56 -41
  71. package/dist/cli/dns-cli.js +26 -14
  72. package/dist/cli/gateway-cli/register.js +37 -19
  73. package/dist/cli/memory-cli.js +5 -5
  74. package/dist/cli/parse-bytes.js +37 -0
  75. package/dist/cli/update-cli.js +173 -52
  76. package/dist/commands/agent.js +1 -0
  77. package/dist/commands/auth-choice.apply.oauth.js +1 -1
  78. package/dist/commands/doctor-config-flow.js +61 -5
  79. package/dist/commands/doctor-state-migrations.js +1 -1
  80. package/dist/commands/health.js +1 -1
  81. package/dist/commands/model-allowlist.js +29 -0
  82. package/dist/commands/model-picker.js +2 -1
  83. package/dist/commands/models/list.registry.js +1 -1
  84. package/dist/commands/models/list.status-command.js +43 -23
  85. package/dist/commands/models/shared.js +15 -0
  86. package/dist/commands/onboard-custom.js +384 -0
  87. package/dist/commands/onboard-non-interactive/local/auth-choice-inference.js +35 -0
  88. package/dist/commands/onboard-non-interactive/local/auth-choice.js +6 -3
  89. package/dist/commands/onboard-skills.js +63 -38
  90. package/dist/commands/openai-model-default.js +41 -0
  91. package/dist/compat/legacy-names.js +2 -0
  92. package/dist/config/defaults.js +3 -2
  93. package/dist/config/paths.js +136 -35
  94. package/dist/config/plugin-auto-enable.js +21 -5
  95. package/dist/config/redact-snapshot.js +153 -0
  96. package/dist/config/schema.field-metadata.js +590 -0
  97. package/dist/config/schema.js +2 -2
  98. package/dist/config/sessions/store.js +291 -23
  99. package/dist/config/zod-schema.agent-defaults.js +3 -0
  100. package/dist/config/zod-schema.agent-runtime.js +13 -2
  101. package/dist/config/zod-schema.providers-core.js +142 -0
  102. package/dist/config/zod-schema.session.js +3 -0
  103. package/dist/control-ui/assets/{index-CIRDm-Lu.css → index-CSfXd2LO.css} +1 -1
  104. package/dist/control-ui/assets/{index-CmNMuoem.js → index-HRr1grwl.js} +446 -413
  105. package/dist/control-ui/assets/index-HRr1grwl.js.map +1 -0
  106. package/dist/control-ui/index.html +4 -4
  107. package/dist/cron/delivery.js +57 -0
  108. package/dist/cron/isolated-agent/delivery-target.js +18 -3
  109. package/dist/cron/isolated-agent/helpers.js +22 -5
  110. package/dist/cron/isolated-agent/run.js +172 -63
  111. package/dist/cron/isolated-agent/session.js +2 -0
  112. package/dist/cron/normalize.js +356 -28
  113. package/dist/cron/parse.js +10 -5
  114. package/dist/cron/run-log.js +35 -10
  115. package/dist/cron/schedule.js +41 -6
  116. package/dist/cron/service/jobs.js +208 -35
  117. package/dist/cron/service/ops.js +72 -16
  118. package/dist/cron/service/state.js +2 -0
  119. package/dist/cron/service/store.js +386 -14
  120. package/dist/cron/service/timer.js +390 -147
  121. package/dist/cron/session-reaper.js +86 -0
  122. package/dist/cron/store.js +23 -8
  123. package/dist/cron/validate-timestamp.js +43 -0
  124. package/dist/discord/monitor/agent-components.js +438 -0
  125. package/dist/discord/monitor/allow-list.js +28 -5
  126. package/dist/discord/monitor/gateway-registry.js +29 -0
  127. package/dist/discord/monitor/native-command.js +44 -23
  128. package/dist/discord/monitor/sender-identity.js +45 -0
  129. package/dist/discord/pluralkit.js +27 -0
  130. package/dist/discord/send.outbound.js +92 -5
  131. package/dist/discord/send.shared.js +60 -23
  132. package/dist/discord/targets.js +84 -1
  133. package/dist/entry.js +15 -9
  134. package/dist/extensionAPI.js +8 -0
  135. package/dist/gateway/control-ui.js +8 -1
  136. package/dist/gateway/hooks-mapping.js +3 -0
  137. package/dist/gateway/hooks.js +65 -0
  138. package/dist/gateway/net.js +96 -31
  139. package/dist/gateway/node-command-policy.js +50 -15
  140. package/dist/gateway/origin-check.js +56 -0
  141. package/dist/gateway/protocol/client-info.js +9 -0
  142. package/dist/gateway/protocol/index.js +9 -2
  143. package/dist/gateway/protocol/schema/agents-models-skills.js +71 -1
  144. package/dist/gateway/protocol/schema/cron.js +22 -10
  145. package/dist/gateway/protocol/schema/protocol-schemas.js +16 -2
  146. package/dist/gateway/protocol/schema/sessions.js +12 -0
  147. package/dist/gateway/server/hooks.js +1 -1
  148. package/dist/gateway/server-broadcast.js +26 -9
  149. package/dist/gateway/server-chat.js +112 -23
  150. package/dist/gateway/server-discovery-runtime.js +10 -2
  151. package/dist/gateway/server-http.js +109 -11
  152. package/dist/gateway/server-methods/agent-timestamp.js +60 -0
  153. package/dist/gateway/server-methods/agents.js +321 -2
  154. package/dist/gateway/server-methods/usage.js +559 -16
  155. package/dist/gateway/server-runtime-state.js +22 -8
  156. package/dist/gateway/server-startup-memory.js +16 -0
  157. package/dist/gateway/server.impl.js +5 -1
  158. package/dist/gateway/session-utils.fs.js +23 -25
  159. package/dist/gateway/session-utils.js +20 -10
  160. package/dist/gateway/sessions-patch.js +7 -22
  161. package/dist/gateway/test-helpers.mocks.js +11 -7
  162. package/dist/gateway/test-helpers.server.js +35 -2
  163. package/dist/imessage/constants.js +2 -0
  164. package/dist/imessage/monitor/deliver.js +4 -1
  165. package/dist/imessage/monitor/monitor-provider.js +51 -1
  166. package/dist/infra/bonjour-discovery.js +131 -70
  167. package/dist/infra/control-ui-assets.js +134 -12
  168. package/dist/infra/errors.js +12 -0
  169. package/dist/infra/exec-approvals.js +266 -57
  170. package/dist/infra/format-time/format-datetime.js +79 -0
  171. package/dist/infra/format-time/format-duration.js +81 -0
  172. package/dist/infra/format-time/format-relative.js +80 -0
  173. package/dist/infra/heartbeat-runner.js +140 -49
  174. package/dist/infra/home-dir.js +54 -0
  175. package/dist/infra/net/fetch-guard.js +122 -0
  176. package/dist/infra/net/ssrf.js +65 -29
  177. package/dist/infra/outbound/abort.js +14 -0
  178. package/dist/infra/outbound/message-action-runner.js +77 -13
  179. package/dist/infra/outbound/outbound-session.js +143 -37
  180. package/dist/infra/poolbot-root.js +43 -1
  181. package/dist/infra/session-cost-usage.js +631 -41
  182. package/dist/infra/state-migrations.js +317 -47
  183. package/dist/infra/update-global.js +35 -0
  184. package/dist/infra/update-runner.js +149 -43
  185. package/dist/infra/warning-filter.js +65 -0
  186. package/dist/infra/widearea-dns.js +30 -9
  187. package/dist/logging/redact-identifier.js +12 -0
  188. package/dist/media/fetch.js +81 -58
  189. package/dist/media/store.js +2 -0
  190. package/dist/media-understanding/apply.js +403 -3
  191. package/dist/media-understanding/attachments.js +38 -27
  192. package/dist/media-understanding/defaults.js +16 -0
  193. package/dist/media-understanding/providers/deepgram/audio.js +22 -14
  194. package/dist/media-understanding/providers/google/audio.js +24 -17
  195. package/dist/media-understanding/providers/google/video.js +24 -17
  196. package/dist/media-understanding/providers/image.js +3 -3
  197. package/dist/media-understanding/providers/index.js +4 -1
  198. package/dist/media-understanding/providers/openai/audio.js +22 -14
  199. package/dist/media-understanding/providers/shared.js +16 -11
  200. package/dist/media-understanding/providers/zai/index.js +6 -0
  201. package/dist/media-understanding/runner.js +158 -90
  202. package/dist/memory/batch-voyage.js +277 -0
  203. package/dist/memory/embeddings-voyage.js +75 -0
  204. package/dist/memory/embeddings.js +28 -16
  205. package/dist/memory/internal.js +101 -18
  206. package/dist/memory/manager.js +154 -48
  207. package/dist/memory/search-manager.js +173 -0
  208. package/dist/memory/session-files.js +9 -3
  209. package/dist/node-host/runner.js +34 -24
  210. package/dist/node-host/with-timeout.js +27 -0
  211. package/dist/plugins/commands.js +5 -1
  212. package/dist/plugins/config-state.js +86 -7
  213. package/dist/plugins/source-display.js +51 -0
  214. package/dist/process/exec.js +20 -2
  215. package/dist/routing/resolve-route.js +12 -0
  216. package/dist/routing/session-key.js +15 -0
  217. package/dist/runtime.js +2 -0
  218. package/dist/security/audit-extra.async.js +601 -0
  219. package/dist/security/audit-extra.js +2 -830
  220. package/dist/security/audit-extra.sync.js +505 -0
  221. package/dist/security/channel-metadata.js +34 -0
  222. package/dist/security/external-content.js +88 -6
  223. package/dist/security/skill-scanner.js +330 -0
  224. package/dist/sessions/session-key-utils.js +7 -0
  225. package/dist/signal/monitor/event-handler.js +80 -1
  226. package/dist/slack/monitor/media.js +85 -15
  227. package/dist/tailscale/detect.js +1 -2
  228. package/dist/telegram/bot/helpers.js +109 -28
  229. package/dist/telegram/bot-handlers.js +144 -3
  230. package/dist/telegram/bot-message-context.js +37 -10
  231. package/dist/telegram/bot-message-dispatch.js +54 -17
  232. package/dist/telegram/bot-native-commands.js +86 -29
  233. package/dist/telegram/bot.js +30 -29
  234. package/dist/telegram/model-buttons.js +163 -0
  235. package/dist/telegram/monitor.js +110 -85
  236. package/dist/telegram/send.js +129 -47
  237. package/dist/terminal/restore.js +45 -0
  238. package/dist/test-helpers/state-dir-env.js +16 -0
  239. package/dist/tts/tts.js +12 -6
  240. package/dist/tui/tui-session-actions.js +166 -54
  241. package/dist/utils/fetch-timeout.js +20 -0
  242. package/dist/utils/normalize-secret-input.js +19 -0
  243. package/dist/utils/transcript-tools.js +58 -0
  244. package/dist/utils.js +45 -14
  245. package/dist/version.js +42 -5
  246. package/dist/wizard/clack-prompter.js +9 -6
  247. package/extensions/googlechat/node_modules/.bin/poolbot +21 -0
  248. package/extensions/googlechat/package.json +2 -2
  249. package/extensions/line/node_modules/.bin/poolbot +21 -0
  250. package/extensions/line/package.json +1 -1
  251. package/extensions/matrix/node_modules/.bin/poolbot +21 -0
  252. package/extensions/matrix/package.json +1 -1
  253. package/extensions/memory-core/node_modules/.bin/poolbot +21 -0
  254. package/extensions/memory-core/package.json +4 -1
  255. package/extensions/twitch/node_modules/.bin/poolbot +21 -0
  256. package/extensions/twitch/package.json +1 -1
  257. package/package.json +183 -24
  258. package/dist/control-ui/assets/index-CmNMuoem.js.map +0 -1
@@ -1,14 +1,14 @@
1
1
  import { WebSocketServer } from "ws";
2
2
  import { CANVAS_HOST_PATH } from "../canvas-host/a2ui.js";
3
3
  import { createCanvasHostHandler } from "../canvas-host/server.js";
4
- import { createGatewayHooksRequestHandler } from "./server/hooks.js";
5
- import { listenGatewayHttpServer } from "./server/http-listen.js";
6
4
  import { resolveGatewayListenHosts } from "./net.js";
7
- import { createGatewayPluginRequestHandler } from "./server/plugins-http.js";
8
5
  import { createGatewayBroadcaster } from "./server-broadcast.js";
9
- import { createChatRunState } from "./server-chat.js";
6
+ import { createChatRunState, createToolEventRecipientRegistry, } from "./server-chat.js";
10
7
  import { MAX_PAYLOAD_BYTES } from "./server-constants.js";
11
8
  import { attachGatewayUpgradeHandler, createGatewayHttpServer } from "./server-http.js";
9
+ import { createGatewayHooksRequestHandler } from "./server/hooks.js";
10
+ import { listenGatewayHttpServer } from "./server/http-listen.js";
11
+ import { createGatewayPluginRequestHandler } from "./server/plugins-http.js";
12
12
  export async function createGatewayRuntimeState(params) {
13
13
  let canvasHost = null;
14
14
  if (params.canvasHostEnabled) {
@@ -29,6 +29,10 @@ export async function createGatewayRuntimeState(params) {
29
29
  params.logCanvas.warn(`canvas host failed to start: ${String(err)}`);
30
30
  }
31
31
  }
32
+ // Create clients set before HTTP server so it can be passed to both
33
+ // createGatewayHttpServer (for canvas auth) and attachGatewayUpgradeHandler
34
+ const clients = new Set();
35
+ const { broadcast, broadcastToConnIds } = createGatewayBroadcaster({ clients });
32
36
  const handleHooksRequest = createGatewayHooksRequestHandler({
33
37
  deps: params.deps,
34
38
  getHooksConfig: params.hooksConfig,
@@ -46,8 +50,10 @@ export async function createGatewayRuntimeState(params) {
46
50
  for (const host of bindHosts) {
47
51
  const httpServer = createGatewayHttpServer({
48
52
  canvasHost,
53
+ clients,
49
54
  controlUiEnabled: params.controlUiEnabled,
50
55
  controlUiBasePath: params.controlUiBasePath,
56
+ controlUiRoot: params.controlUiRoot,
51
57
  openAiChatCompletionsEnabled: params.openAiChatCompletionsEnabled,
52
58
  openResponsesEnabled: params.openResponsesEnabled,
53
59
  openResponsesConfig: params.openResponsesConfig,
@@ -66,8 +72,9 @@ export async function createGatewayRuntimeState(params) {
66
72
  httpBindHosts.push(host);
67
73
  }
68
74
  catch (err) {
69
- if (host === bindHosts[0])
75
+ if (host === bindHosts[0]) {
70
76
  throw err;
77
+ }
71
78
  params.log.warn(`gateway: failed to bind loopback alias ${host}:${params.port} (${String(err)})`);
72
79
  }
73
80
  }
@@ -80,10 +87,14 @@ export async function createGatewayRuntimeState(params) {
80
87
  maxPayload: MAX_PAYLOAD_BYTES,
81
88
  });
82
89
  for (const server of httpServers) {
83
- attachGatewayUpgradeHandler({ httpServer: server, wss, canvasHost });
90
+ attachGatewayUpgradeHandler({
91
+ httpServer: server,
92
+ wss,
93
+ canvasHost,
94
+ clients,
95
+ resolvedAuth: params.resolvedAuth,
96
+ });
84
97
  }
85
- const clients = new Set();
86
- const { broadcast } = createGatewayBroadcaster({ clients });
87
98
  const agentRunSeq = new Map();
88
99
  const dedupe = new Map();
89
100
  const chatRunState = createChatRunState();
@@ -93,6 +104,7 @@ export async function createGatewayRuntimeState(params) {
93
104
  const addChatRun = chatRunRegistry.add;
94
105
  const removeChatRun = chatRunRegistry.remove;
95
106
  const chatAbortControllers = new Map();
107
+ const toolEventRecipients = createToolEventRecipientRegistry();
96
108
  return {
97
109
  canvasHost,
98
110
  httpServer,
@@ -101,6 +113,7 @@ export async function createGatewayRuntimeState(params) {
101
113
  wss,
102
114
  clients,
103
115
  broadcast,
116
+ broadcastToConnIds,
104
117
  agentRunSeq,
105
118
  dedupe,
106
119
  chatRunState,
@@ -109,5 +122,6 @@ export async function createGatewayRuntimeState(params) {
109
122
  addChatRun,
110
123
  removeChatRun,
111
124
  chatAbortControllers,
125
+ toolEventRecipients,
112
126
  };
113
127
  }
@@ -0,0 +1,16 @@
1
+ import { resolveDefaultAgentId } from "../agents/agent-scope.js";
2
+ import { resolveMemoryBackendConfig } from "../memory/backend-config.js";
3
+ import { getMemorySearchManager } from "../memory/index.js";
4
+ export async function startGatewayMemoryBackend(params) {
5
+ const agentId = resolveDefaultAgentId(params.cfg);
6
+ const resolved = resolveMemoryBackendConfig({ cfg: params.cfg, agentId });
7
+ if (resolved.backend !== "qmd" || !resolved.qmd) {
8
+ return;
9
+ }
10
+ const { manager, error } = await getMemorySearchManager({ cfg: params.cfg, agentId });
11
+ if (!manager) {
12
+ params.log.warn(`qmd memory startup initialization failed for agent "${agentId}": ${error ?? "unknown error"}`);
13
+ return;
14
+ }
15
+ params.log.info?.(`qmd memory startup initialization armed for agent "${agentId}"`);
16
+ }
@@ -156,7 +156,7 @@ export async function startGatewayServer(port = 18789, opts = {}) {
156
156
  if (cfgAtStart.gateway?.tls?.enabled && !gatewayTls.enabled) {
157
157
  throw new Error(gatewayTls.error ?? "gateway tls: failed to enable");
158
158
  }
159
- const { canvasHost, httpServer, httpServers, httpBindHosts, wss, clients, broadcast, agentRunSeq, dedupe, chatRunState, chatRunBuffers, chatDeltaSentAt, addChatRun, removeChatRun, chatAbortControllers, } = await createGatewayRuntimeState({
159
+ const { canvasHost, httpServer, httpServers, httpBindHosts, wss, clients, broadcast, agentRunSeq, dedupe, chatRunState, chatRunBuffers, chatDeltaSentAt, addChatRun, removeChatRun, chatAbortControllers, broadcastToConnIds, toolEventRecipients, } = await createGatewayRuntimeState({
160
160
  cfg: cfgAtStart,
161
161
  bindHost,
162
162
  port,
@@ -257,11 +257,13 @@ export async function startGatewayServer(port = 18789, opts = {}) {
257
257
  });
258
258
  const agentUnsub = onAgentEvent(createAgentEventHandler({
259
259
  broadcast,
260
+ broadcastToConnIds,
260
261
  nodeSendToSession,
261
262
  agentRunSeq,
262
263
  chatRunState,
263
264
  resolveSessionKeyForRun,
264
265
  clearAgentRunContext,
266
+ toolEventRecipients,
265
267
  }));
266
268
  const heartbeatUnsub = onHeartbeatEvent((evt) => {
267
269
  broadcast("heartbeat", evt, { dropIfSlow: true });
@@ -304,6 +306,7 @@ export async function startGatewayServer(port = 18789, opts = {}) {
304
306
  incrementPresenceVersion,
305
307
  getHealthVersion,
306
308
  broadcast,
309
+ broadcastToConnIds,
307
310
  nodeSendToSession,
308
311
  nodeSendToAllSubscribed,
309
312
  nodeSubscribe,
@@ -318,6 +321,7 @@ export async function startGatewayServer(port = 18789, opts = {}) {
318
321
  chatDeltaSentAt: chatRunState.deltaSentAt,
319
322
  addChatRun,
320
323
  removeChatRun,
324
+ registerToolEventRecipient: toolEventRecipients.add,
321
325
  dedupe,
322
326
  wizardSessions,
323
327
  findRunningWizard,
@@ -2,6 +2,8 @@ import fs from "node:fs";
2
2
  import os from "node:os";
3
3
  import path from "node:path";
4
4
  import { resolveSessionTranscriptPath } from "../config/sessions.js";
5
+ import { resolveRequiredHomeDir } from "../infra/home-dir.js";
6
+ import { extractToolCallNames, hasToolCall } from "../utils/transcript-tools.js";
5
7
  import { stripEnvelope } from "./chat-sanitize.js";
6
8
  export function readSessionMessages(sessionId, storePath, sessionFile) {
7
9
  const candidates = resolveSessionTranscriptCandidates(sessionId, storePath, sessionFile);
@@ -17,6 +19,22 @@ export function readSessionMessages(sessionId, storePath, sessionFile) {
17
19
  const parsed = JSON.parse(line);
18
20
  if (parsed?.message) {
19
21
  messages.push(parsed.message);
22
+ continue;
23
+ }
24
+ // Compaction entries are not "message" records, but they're useful context for debugging.
25
+ // Emit a lightweight synthetic message that the Web UI can render as a divider.
26
+ if (parsed?.type === "compaction") {
27
+ const ts = typeof parsed.timestamp === "string" ? Date.parse(parsed.timestamp) : Number.NaN;
28
+ const timestamp = Number.isFinite(ts) ? ts : Date.now();
29
+ messages.push({
30
+ role: "system",
31
+ content: [{ type: "text", text: "Compaction" }],
32
+ timestamp,
33
+ __poolbot: {
34
+ kind: "compaction",
35
+ id: typeof parsed.id === "string" ? parsed.id : undefined,
36
+ },
37
+ });
20
38
  }
21
39
  }
22
40
  catch {
@@ -36,7 +54,8 @@ export function resolveSessionTranscriptCandidates(sessionId, storePath, session
36
54
  if (agentId) {
37
55
  candidates.push(resolveSessionTranscriptPath(sessionId, agentId));
38
56
  }
39
- candidates.push(path.join(os.homedir(), ".poolbot", "sessions", `${sessionId}.jsonl`));
57
+ const home = resolveRequiredHomeDir(process.env, os.homedir);
58
+ candidates.push(path.join(home, ".poolbot", "sessions", `${sessionId}.jsonl`));
40
59
  return candidates;
41
60
  }
42
61
  export function archiveFileOnDisk(filePath, reason) {
@@ -214,31 +233,10 @@ function extractPreviewText(message) {
214
233
  return null;
215
234
  }
216
235
  function isToolCall(message) {
217
- if (message.toolName || message.tool_name)
218
- return true;
219
- if (!Array.isArray(message.content))
220
- return false;
221
- return message.content.some((entry) => {
222
- if (entry?.name)
223
- return true;
224
- const raw = typeof entry?.type === "string" ? entry.type.toLowerCase() : "";
225
- return raw === "toolcall" || raw === "tool_call";
226
- });
236
+ return hasToolCall(message);
227
237
  }
228
238
  function extractToolNames(message) {
229
- const names = [];
230
- if (Array.isArray(message.content)) {
231
- for (const entry of message.content) {
232
- if (typeof entry?.name === "string" && entry.name.trim()) {
233
- names.push(entry.name.trim());
234
- }
235
- }
236
- }
237
- const toolName = typeof message.toolName === "string" ? message.toolName : message.tool_name;
238
- if (typeof toolName === "string" && toolName.trim()) {
239
- names.push(toolName.trim());
240
- }
241
- return names;
239
+ return extractToolCallNames(message);
242
240
  }
243
241
  function extractMediaSummary(message) {
244
242
  if (!Array.isArray(message.content))
@@ -316,7 +314,7 @@ function readRecentMessagesFromTranscript(filePath, maxMessages, readBytes) {
316
314
  // skip malformed lines
317
315
  }
318
316
  }
319
- return collected.reverse();
317
+ return collected.toReversed();
320
318
  }
321
319
  catch {
322
320
  return [];
@@ -3,11 +3,12 @@ import path from "node:path";
3
3
  import { resolveAgentWorkspaceDir, resolveDefaultAgentId } from "../agents/agent-scope.js";
4
4
  import { lookupContextTokens } from "../agents/context.js";
5
5
  import { DEFAULT_CONTEXT_TOKENS, DEFAULT_MODEL, DEFAULT_PROVIDER } from "../agents/defaults.js";
6
- import { resolveConfiguredModelRef } from "../agents/model-selection.js";
6
+ import { resolveConfiguredModelRef, resolveDefaultModelForAgent, } from "../agents/model-selection.js";
7
7
  import { loadConfig } from "../config/config.js";
8
8
  import { resolveStateDir } from "../config/paths.js";
9
9
  import { buildGroupDisplayName, canonicalizeMainSessionAlias, loadSessionStore, resolveMainSessionKey, resolveStorePath, } from "../config/sessions.js";
10
10
  import { normalizeAgentId, normalizeMainKey, parseAgentSessionKey, } from "../routing/session-key.js";
11
+ import { isCronRunSessionKey } from "../sessions/session-key-utils.js";
11
12
  import { normalizeSessionDeliveryFields } from "../utils/delivery-context.js";
12
13
  import { readFirstUserMessageFromTranscript, readLastMessagePreviewFromTranscript, } from "./session-utils.fs.js";
13
14
  export { archiveFileOnDisk, capArrayByJsonBytes, readFirstUserMessageFromTranscript, readLastMessagePreviewFromTranscript, readSessionPreviewItemsFromTranscript, readSessionMessages, resolveSessionTranscriptCandidates, } from "./session-utils.fs.js";
@@ -374,12 +375,14 @@ export function getSessionDefaults(cfg) {
374
375
  contextTokens: contextTokens ?? null,
375
376
  };
376
377
  }
377
- export function resolveSessionModelRef(cfg, entry) {
378
- const resolved = resolveConfiguredModelRef({
379
- cfg,
380
- defaultProvider: DEFAULT_PROVIDER,
381
- defaultModel: DEFAULT_MODEL,
382
- });
378
+ export function resolveSessionModelRef(cfg, entry, agentId) {
379
+ const resolved = agentId
380
+ ? resolveDefaultModelForAgent({ cfg, agentId })
381
+ : resolveConfiguredModelRef({
382
+ cfg,
383
+ defaultProvider: DEFAULT_PROVIDER,
384
+ defaultModel: DEFAULT_MODEL,
385
+ });
383
386
  let provider = resolved.provider;
384
387
  let model = resolved.model;
385
388
  const storedModelOverride = entry?.modelOverride?.trim();
@@ -405,6 +408,8 @@ export function listSessionsFromStore(params) {
405
408
  : undefined;
406
409
  let sessions = Object.entries(store)
407
410
  .filter(([key]) => {
411
+ if (isCronRunSessionKey(key))
412
+ return false;
408
413
  if (!includeGlobal && key === "global")
409
414
  return false;
410
415
  if (!includeUnknown && key === "unknown")
@@ -458,6 +463,11 @@ export function listSessionsFromStore(params) {
458
463
  entry?.label ??
459
464
  originLabel;
460
465
  const deliveryFields = normalizeSessionDeliveryFields(entry);
466
+ const parsedAgent = parseAgentSessionKey(key);
467
+ const sessionAgentId = normalizeAgentId(parsedAgent?.agentId ?? resolveDefaultAgentId(cfg));
468
+ const resolvedModel = resolveSessionModelRef(cfg, entry, sessionAgentId);
469
+ const modelProvider = resolvedModel.provider ?? DEFAULT_PROVIDER;
470
+ const model = resolvedModel.model ?? DEFAULT_MODEL;
461
471
  return {
462
472
  key,
463
473
  entry,
@@ -483,8 +493,8 @@ export function listSessionsFromStore(params) {
483
493
  outputTokens: entry?.outputTokens,
484
494
  totalTokens: total,
485
495
  responseUsage: entry?.responseUsage,
486
- modelProvider: entry?.modelProvider,
487
- model: entry?.model,
496
+ modelProvider,
497
+ model,
488
498
  contextTokens: entry?.contextTokens,
489
499
  deliveryContext: deliveryFields.deliveryContext,
490
500
  lastChannel: deliveryFields.lastChannel ?? entry?.lastChannel,
@@ -492,7 +502,7 @@ export function listSessionsFromStore(params) {
492
502
  lastAccountId: deliveryFields.lastAccountId ?? entry?.lastAccountId,
493
503
  };
494
504
  })
495
- .sort((a, b) => (b.updatedAt ?? 0) - (a.updatedAt ?? 0));
505
+ .toSorted((a, b) => (b.updatedAt ?? 0) - (a.updatedAt ?? 0));
496
506
  if (search) {
497
507
  sessions = sessions.filter((s) => {
498
508
  const fields = [s.displayName, s.label, s.subject, s.sessionId, s.key];
@@ -1,9 +1,9 @@
1
1
  import { randomUUID } from "node:crypto";
2
- import { DEFAULT_MODEL, DEFAULT_PROVIDER } from "../agents/defaults.js";
3
- import { resolveAllowedModelRef, resolveConfiguredModelRef } from "../agents/model-selection.js";
2
+ import { resolveAllowedModelRef, resolveDefaultModelForAgent } from "../agents/model-selection.js";
3
+ import { resolveDefaultAgentId } from "../agents/agent-scope.js";
4
4
  import { normalizeGroupActivation } from "../auto-reply/group-activation.js";
5
5
  import { formatThinkingLevels, formatXHighModelHint, normalizeElevatedLevel, normalizeReasoningLevel, normalizeThinkLevel, normalizeUsageDisplay, supportsXHighThinking, } from "../auto-reply/thinking.js";
6
- import { isSubagentSessionKey } from "../routing/session-key.js";
6
+ import { isSubagentSessionKey, normalizeAgentId, parseAgentSessionKey, } from "../routing/session-key.js";
7
7
  import { applyVerboseOverride, parseVerboseOverride } from "../sessions/level-overrides.js";
8
8
  import { normalizeSendPolicy } from "../sessions/send-policy.js";
9
9
  import { parseSessionLabel } from "../sessions/session-label.js";
@@ -36,6 +36,9 @@ function normalizeExecAsk(raw) {
36
36
  export async function applySessionsPatchToStore(params) {
37
37
  const { cfg, store, storeKey, patch } = params;
38
38
  const now = Date.now();
39
+ const parsedAgent = parseAgentSessionKey(storeKey);
40
+ const sessionAgentId = normalizeAgentId(parsedAgent?.agentId ?? resolveDefaultAgentId(cfg));
41
+ const resolvedDefault = resolveDefaultModelForAgent({ cfg, agentId: sessionAgentId });
39
42
  const existing = store[storeKey];
40
43
  const next = existing
41
44
  ? {
@@ -89,19 +92,11 @@ export async function applySessionsPatchToStore(params) {
89
92
  else if (raw !== undefined) {
90
93
  const normalized = normalizeThinkLevel(String(raw));
91
94
  if (!normalized) {
92
- const resolvedDefault = resolveConfiguredModelRef({
93
- cfg,
94
- defaultProvider: DEFAULT_PROVIDER,
95
- defaultModel: DEFAULT_MODEL,
96
- });
97
95
  const hintProvider = existing?.providerOverride?.trim() || resolvedDefault.provider;
98
96
  const hintModel = existing?.modelOverride?.trim() || resolvedDefault.model;
99
97
  return invalid(`invalid thinkingLevel (use ${formatThinkingLevels(hintProvider, hintModel, "|")})`);
100
98
  }
101
- if (normalized === "off")
102
- delete next.thinkingLevel;
103
- else
104
- next.thinkingLevel = normalized;
99
+ next.thinkingLevel = normalized;
105
100
  }
106
101
  }
107
102
  if ("verboseLevel" in patch) {
@@ -205,11 +200,6 @@ export async function applySessionsPatchToStore(params) {
205
200
  }
206
201
  if ("model" in patch) {
207
202
  const raw = patch.model;
208
- const resolvedDefault = resolveConfiguredModelRef({
209
- cfg,
210
- defaultProvider: DEFAULT_PROVIDER,
211
- defaultModel: DEFAULT_MODEL,
212
- });
213
203
  if (raw === null) {
214
204
  applyModelOverrideToSessionEntry({
215
205
  entry: next,
@@ -254,11 +244,6 @@ export async function applySessionsPatchToStore(params) {
254
244
  }
255
245
  }
256
246
  if (next.thinkingLevel === "xhigh") {
257
- const resolvedDefault = resolveConfiguredModelRef({
258
- cfg,
259
- defaultProvider: DEFAULT_PROVIDER,
260
- defaultModel: DEFAULT_MODEL,
261
- });
262
247
  const effectiveProvider = next.providerOverride ?? resolvedDefault.provider;
263
248
  const effectiveModel = next.modelOverride ?? resolvedDefault.model;
264
249
  if (!supportsXHighThinking(effectiveProvider, effectiveModel)) {
@@ -197,17 +197,21 @@ export const testState = {
197
197
  export const testIsNixMode = hoisted.testIsNixMode;
198
198
  export const sessionStoreSaveDelayMs = hoisted.sessionStoreSaveDelayMs;
199
199
  export const embeddedRunMock = hoisted.embeddedRunMock;
200
- vi.mock("@mariozechner/pi-coding-agent", async () => {
201
- const actual = await vi.importActual("@mariozechner/pi-coding-agent");
202
- return {
203
- ...actual,
204
- discoverModels: (...args) => {
200
+ vi.mock("../agents/pi-model-discovery.js", async () => {
201
+ const actual = await vi.importActual("../agents/pi-model-discovery.js");
202
+ class MockModelRegistry extends actual.ModelRegistry {
203
+ getAll() {
205
204
  if (!piSdkMock.enabled) {
206
- return actual.discoverModels(...args);
205
+ return super.getAll();
207
206
  }
208
207
  piSdkMock.discoverCalls += 1;
208
+ // Cast to expected type for testing purposes
209
209
  return piSdkMock.models;
210
- },
210
+ }
211
+ }
212
+ return {
213
+ ...actual,
214
+ ModelRegistry: MockModelRegistry,
211
215
  };
212
216
  });
213
217
  vi.mock("../cron/isolated-agent.js", () => ({
@@ -26,6 +26,7 @@ let previousConfigPath;
26
26
  let previousSkipBrowserControl;
27
27
  let previousSkipGmailWatcher;
28
28
  let previousSkipCanvasHost;
29
+ let previousBundledPluginsDir;
29
30
  let tempHome;
30
31
  let tempConfigRoot;
31
32
  export async function writeSessionStore(params) {
@@ -56,6 +57,7 @@ async function setupGatewayTestHome() {
56
57
  previousSkipBrowserControl = process.env.CLAWDBOT_SKIP_BROWSER_CONTROL_SERVER;
57
58
  previousSkipGmailWatcher = process.env.CLAWDBOT_SKIP_GMAIL_WATCHER;
58
59
  previousSkipCanvasHost = process.env.CLAWDBOT_SKIP_CANVAS_HOST;
60
+ previousBundledPluginsDir = process.env.CLAWDBOT_BUNDLED_PLUGINS_DIR;
59
61
  tempHome = await fs.mkdtemp(path.join(os.tmpdir(), "poolbot-gateway-home-"));
60
62
  process.env.HOME = tempHome;
61
63
  process.env.USERPROFILE = tempHome;
@@ -66,6 +68,9 @@ function applyGatewaySkipEnv() {
66
68
  process.env.CLAWDBOT_SKIP_BROWSER_CONTROL_SERVER = "1";
67
69
  process.env.CLAWDBOT_SKIP_GMAIL_WATCHER = "1";
68
70
  process.env.CLAWDBOT_SKIP_CANVAS_HOST = "1";
71
+ process.env.CLAWDBOT_BUNDLED_PLUGINS_DIR = tempHome
72
+ ? path.join(tempHome, "poolbot-test-no-bundled-extensions")
73
+ : "poolbot-test-no-bundled-extensions";
69
74
  }
70
75
  async function resetGatewayTestState(options) {
71
76
  // Some tests intentionally use fake timers; ensure they don't leak into gateway suites.
@@ -146,6 +151,10 @@ async function cleanupGatewayTestHome(options) {
146
151
  delete process.env.CLAWDBOT_SKIP_CANVAS_HOST;
147
152
  else
148
153
  process.env.CLAWDBOT_SKIP_CANVAS_HOST = previousSkipCanvasHost;
154
+ if (previousBundledPluginsDir === undefined)
155
+ delete process.env.CLAWDBOT_BUNDLED_PLUGINS_DIR;
156
+ else
157
+ process.env.CLAWDBOT_BUNDLED_PLUGINS_DIR = previousBundledPluginsDir;
149
158
  }
150
159
  if (options.restoreEnv && tempHome) {
151
160
  await fs.rm(tempHome, {
@@ -224,7 +233,8 @@ timeoutMs = 10_000) {
224
233
  }
225
234
  export async function startGatewayServer(port, opts) {
226
235
  const mod = await serverModulePromise;
227
- return await mod.startGatewayServer(port, opts);
236
+ const resolvedOpts = opts?.controlUiEnabled === undefined ? { ...opts, controlUiEnabled: false } : opts;
237
+ return await mod.startGatewayServer(port, resolvedOpts);
228
238
  }
229
239
  export async function startServerWithClient(token, opts) {
230
240
  let port = await getFreePort();
@@ -259,7 +269,30 @@ export async function startServerWithClient(token, opts) {
259
269
  throw new Error("failed to start gateway server after retries");
260
270
  }
261
271
  const ws = new WebSocket(`ws://127.0.0.1:${port}`);
262
- await new Promise((resolve) => ws.once("open", resolve));
272
+ await new Promise((resolve, reject) => {
273
+ const timer = setTimeout(() => reject(new Error("timeout waiting for ws open")), 10_000);
274
+ const cleanup = () => {
275
+ clearTimeout(timer);
276
+ ws.off("open", onOpen);
277
+ ws.off("error", onError);
278
+ ws.off("close", onClose);
279
+ };
280
+ const onOpen = () => {
281
+ cleanup();
282
+ resolve();
283
+ };
284
+ const onError = (err) => {
285
+ cleanup();
286
+ reject(err instanceof Error ? err : new Error(String(err)));
287
+ };
288
+ const onClose = (code, reason) => {
289
+ cleanup();
290
+ reject(new Error(`closed ${code}: ${reason.toString()}`));
291
+ };
292
+ ws.once("open", onOpen);
293
+ ws.once("error", onError);
294
+ ws.once("close", onClose);
295
+ });
263
296
  return { server, ws, port, prevToken: prev };
264
297
  }
265
298
  export async function connectReq(ws, opts) {
@@ -0,0 +1,2 @@
1
+ /** Default timeout for iMessage probe/RPC operations (10 seconds). */
2
+ export const DEFAULT_IMESSAGE_PROBE_TIMEOUT_MS = 10_000;
@@ -4,7 +4,7 @@ import { resolveMarkdownTableMode } from "../../config/markdown-tables.js";
4
4
  import { convertMarkdownTables } from "../../markdown/tables.js";
5
5
  import { sendMessageIMessage } from "../send.js";
6
6
  export async function deliverReplies(params) {
7
- const { replies, target, client, runtime, maxBytes, textLimit, accountId } = params;
7
+ const { replies, target, client, runtime, maxBytes, textLimit, accountId, sentMessageCache } = params;
8
8
  const cfg = loadConfig();
9
9
  const tableMode = resolveMarkdownTableMode({
10
10
  cfg,
@@ -25,6 +25,7 @@ export async function deliverReplies(params) {
25
25
  client,
26
26
  accountId,
27
27
  });
28
+ sentMessageCache?.remember(target, chunk);
28
29
  }
29
30
  }
30
31
  else {
@@ -38,6 +39,8 @@ export async function deliverReplies(params) {
38
39
  client,
39
40
  accountId,
40
41
  });
42
+ if (caption)
43
+ sentMessageCache?.remember(target, caption);
41
44
  }
42
45
  }
43
46
  runtime.log?.(`imessage: delivered reply to ${target}`);
@@ -25,6 +25,7 @@ import { truncateUtf16Safe } from "../../utils.js";
25
25
  import { resolveControlCommandGate } from "../../channels/command-gating.js";
26
26
  import { resolveIMessageAccount } from "../accounts.js";
27
27
  import { createIMessageRpcClient } from "../client.js";
28
+ import { DEFAULT_IMESSAGE_PROBE_TIMEOUT_MS } from "../constants.js";
28
29
  import { probeIMessage } from "../probe.js";
29
30
  import { sendMessageIMessage } from "../send.js";
30
31
  import { formatIMessageChatTarget, isAllowedIMessageSender, normalizeIMessageHandle, } from "../targets.js";
@@ -72,6 +73,38 @@ function describeReplyContext(message) {
72
73
  const sender = normalizeReplyField(message.reply_to_sender);
73
74
  return { body, id, sender };
74
75
  }
76
+ class SentMessageCache {
77
+ cache = new Map();
78
+ ttlMs = 5000;
79
+ remember(scope, text) {
80
+ if (!text?.trim())
81
+ return;
82
+ const key = `${scope}:${text.trim()}`;
83
+ this.cache.set(key, Date.now());
84
+ this.cleanup();
85
+ }
86
+ has(scope, text) {
87
+ if (!text?.trim())
88
+ return false;
89
+ const key = `${scope}:${text.trim()}`;
90
+ const timestamp = this.cache.get(key);
91
+ if (!timestamp)
92
+ return false;
93
+ const age = Date.now() - timestamp;
94
+ if (age > this.ttlMs) {
95
+ this.cache.delete(key);
96
+ return false;
97
+ }
98
+ return true;
99
+ }
100
+ cleanup() {
101
+ const now = Date.now();
102
+ for (const [text, timestamp] of this.cache.entries()) {
103
+ if (now - timestamp > this.ttlMs)
104
+ this.cache.delete(text);
105
+ }
106
+ }
107
+ }
75
108
  export async function monitorIMessageProvider(opts = {}) {
76
109
  const runtime = resolveRuntime(opts);
77
110
  const cfg = opts.config ?? loadConfig();
@@ -84,6 +117,7 @@ export async function monitorIMessageProvider(opts = {}) {
84
117
  cfg.messages?.groupChat?.historyLimit ??
85
118
  DEFAULT_GROUP_HISTORY_LIMIT);
86
119
  const groupHistories = new Map();
120
+ const sentMessageCache = new SentMessageCache();
87
121
  const textLimit = resolveTextChunkLimit(cfg, "imessage", accountInfo.accountId);
88
122
  const allowFrom = normalizeAllowList(opts.allowFrom ?? imessageCfg.allowFrom);
89
123
  const groupAllowFrom = normalizeAllowList(opts.groupAllowFrom ??
@@ -96,6 +130,7 @@ export async function monitorIMessageProvider(opts = {}) {
96
130
  const mediaMaxBytes = (opts.mediaMaxMb ?? imessageCfg.mediaMaxMb ?? 16) * 1024 * 1024;
97
131
  const cliPath = opts.cliPath ?? imessageCfg.cliPath ?? "imsg";
98
132
  const dbPath = opts.dbPath ?? imessageCfg.dbPath;
133
+ const probeTimeoutMs = imessageCfg.probeTimeoutMs ?? DEFAULT_IMESSAGE_PROBE_TIMEOUT_MS;
99
134
  // Resolve remoteHost: explicit config, or auto-detect from SSH wrapper script
100
135
  let remoteHost = imessageCfg.remoteHost;
101
136
  if (!remoteHost && cliPath && cliPath !== "imsg") {
@@ -290,6 +325,11 @@ export async function monitorIMessageProvider(opts = {}) {
290
325
  });
291
326
  const mentionRegexes = buildMentionRegexes(cfg, route.agentId);
292
327
  const messageText = (message.text ?? "").trim();
328
+ const echoScope = `${accountInfo.accountId}:${isGroup ? formatIMessageChatTarget(chatId) : `imessage:${sender}`}`;
329
+ if (messageText && sentMessageCache.has(echoScope, messageText)) {
330
+ logVerbose(`imessage: skipping echo message (matches recently sent text within 5s): "${truncateUtf16Safe(messageText, 50)}"`);
331
+ return;
332
+ }
293
333
  const attachments = includeAttachments ? (message.attachments ?? []) : [];
294
334
  // Filter to valid attachments with paths
295
335
  const validAttachments = attachments.filter((entry) => entry?.original_path && !entry?.missing);
@@ -426,8 +466,17 @@ export async function monitorIMessageProvider(opts = {}) {
426
466
  });
427
467
  }
428
468
  const imessageTo = (isGroup ? chatTarget : undefined) || `imessage:${sender}`;
469
+ const inboundHistory = isGroup && historyKey && historyLimit > 0
470
+ ? (groupHistories.get(historyKey) ?? []).map((entry) => ({
471
+ sender: entry.sender,
472
+ body: entry.body,
473
+ timestamp: entry.timestamp,
474
+ }))
475
+ : undefined;
429
476
  const ctxPayload = finalizeInboundContext({
430
477
  Body: combinedBody,
478
+ BodyForAgent: bodyText,
479
+ InboundHistory: inboundHistory,
431
480
  RawBody: bodyText,
432
481
  CommandBody: bodyText,
433
482
  From: isGroup ? `imessage:group:${chatId ?? "unknown"}` : `imessage:${sender}`,
@@ -495,6 +544,7 @@ export async function monitorIMessageProvider(opts = {}) {
495
544
  runtime,
496
545
  maxBytes: mediaMaxBytes,
497
546
  textLimit,
547
+ sentMessageCache,
498
548
  });
499
549
  },
500
550
  onError: (err, info) => {
@@ -542,7 +592,7 @@ export async function monitorIMessageProvider(opts = {}) {
542
592
  abortSignal: opts.abortSignal,
543
593
  runtime,
544
594
  check: async () => {
545
- const probe = await probeIMessage(2000, { cliPath, dbPath, runtime });
595
+ const probe = await probeIMessage(probeTimeoutMs, { cliPath, dbPath, runtime });
546
596
  if (probe.ok)
547
597
  return { ok: true };
548
598
  if (probe.fatal) {