@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,5 +1,6 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import fs from "node:fs/promises";
3
+ import fsSync from "node:fs";
3
4
  import path from "node:path";
4
5
  import chokidar from "chokidar";
5
6
  import { resolveAgentDir, resolveAgentWorkspaceDir } from "../agents/agent-scope.js";
@@ -11,9 +12,11 @@ import { resolveUserPath } from "../utils.js";
11
12
  import { createEmbeddingProvider, } from "./embeddings.js";
12
13
  import { DEFAULT_GEMINI_EMBEDDING_MODEL } from "./embeddings-gemini.js";
13
14
  import { DEFAULT_OPENAI_EMBEDDING_MODEL } from "./embeddings-openai.js";
15
+ import { DEFAULT_VOYAGE_EMBEDDING_MODEL } from "./embeddings-voyage.js";
14
16
  import { OPENAI_BATCH_ENDPOINT, runOpenAiEmbeddingBatches, } from "./batch-openai.js";
15
17
  import { runGeminiEmbeddingBatches } from "./batch-gemini.js";
16
- import { buildFileEntry, chunkMarkdown, ensureDir, hashText, isMemoryPath, listMemoryFiles, normalizeRelPath, parseEmbedding, } from "./internal.js";
18
+ import { runVoyageEmbeddingBatches } from "./batch-voyage.js";
19
+ import { buildFileEntry, chunkMarkdown, ensureDir, hashText, isMemoryPath, listMemoryFiles, normalizeExtraMemoryPaths, parseEmbedding, remapChunkLines, runWithConcurrency, } from "./internal.js";
17
20
  import { bm25RankToScore, buildFtsQuery, mergeHybridResults } from "./hybrid.js";
18
21
  import { searchKeyword, searchVector } from "./manager-search.js";
19
22
  import { ensureMemoryIndexSchema } from "./memory-schema.js";
@@ -53,6 +56,7 @@ export class MemoryIndexManager {
53
56
  fallbackReason;
54
57
  openAi;
55
58
  gemini;
59
+ voyage;
56
60
  batch;
57
61
  batchFailureCount = 0;
58
62
  batchFailureLastError;
@@ -120,6 +124,7 @@ export class MemoryIndexManager {
120
124
  this.fallbackReason = params.providerResult.fallbackReason;
121
125
  this.openAi = params.providerResult.openAi;
122
126
  this.gemini = params.providerResult.gemini;
127
+ this.voyage = params.providerResult.voyage;
123
128
  this.sources = new Set(params.settings.sources);
124
129
  this.db = this.openDatabase();
125
130
  this.providerKey = this.computeProviderKey();
@@ -257,13 +262,51 @@ export class MemoryIndexManager {
257
262
  return this.syncing;
258
263
  }
259
264
  async readFile(params) {
260
- const relPath = normalizeRelPath(params.relPath);
261
- if (!relPath || !isMemoryPath(relPath)) {
265
+ const rawPath = params.relPath.trim();
266
+ if (!rawPath) {
262
267
  throw new Error("path required");
263
268
  }
264
- const absPath = path.resolve(this.workspaceDir, relPath);
265
- if (!absPath.startsWith(this.workspaceDir)) {
266
- throw new Error("path escapes workspace");
269
+ const absPath = path.isAbsolute(rawPath)
270
+ ? path.resolve(rawPath)
271
+ : path.resolve(this.workspaceDir, rawPath);
272
+ const relPath = path.relative(this.workspaceDir, absPath).replace(/\\/g, "/");
273
+ const inWorkspace = relPath.length > 0 && !relPath.startsWith("..") && !path.isAbsolute(relPath);
274
+ const allowedWorkspace = inWorkspace && isMemoryPath(relPath);
275
+ let allowedAdditional = false;
276
+ if (!allowedWorkspace && this.settings.extraPaths.length > 0) {
277
+ const additionalPaths = normalizeExtraMemoryPaths(this.workspaceDir, this.settings.extraPaths);
278
+ for (const additionalPath of additionalPaths) {
279
+ try {
280
+ const stat = await fs.lstat(additionalPath);
281
+ if (stat.isSymbolicLink()) {
282
+ continue;
283
+ }
284
+ if (stat.isDirectory()) {
285
+ if (absPath === additionalPath || absPath.startsWith(`${additionalPath}${path.sep}`)) {
286
+ allowedAdditional = true;
287
+ break;
288
+ }
289
+ continue;
290
+ }
291
+ if (stat.isFile()) {
292
+ if (absPath === additionalPath && absPath.endsWith(".md")) {
293
+ allowedAdditional = true;
294
+ break;
295
+ }
296
+ }
297
+ }
298
+ catch { }
299
+ }
300
+ }
301
+ if (!allowedWorkspace && !allowedAdditional) {
302
+ throw new Error("path required");
303
+ }
304
+ if (!absPath.endsWith(".md")) {
305
+ throw new Error("path required");
306
+ }
307
+ const stat = await fs.lstat(absPath);
308
+ if (stat.isSymbolicLink() || !stat.isFile()) {
309
+ throw new Error("path required");
267
310
  }
268
311
  const content = await fs.readFile(absPath, "utf-8");
269
312
  if (!params.from && !params.lines) {
@@ -307,19 +350,20 @@ export class MemoryIndexManager {
307
350
  entry.chunks = row.c ?? 0;
308
351
  bySource.set(row.source, entry);
309
352
  }
310
- return sources.map((source) => ({ source, ...bySource.get(source) }));
353
+ return sources.map((source) => Object.assign({ source }, bySource.get(source)));
311
354
  })();
312
355
  return {
313
356
  backend: "builtin",
314
357
  files: files?.c ?? 0,
315
358
  chunks: chunks?.c ?? 0,
316
- dirty: this.dirty,
359
+ dirty: this.dirty || this.sessionsDirty,
317
360
  workspaceDir: this.workspaceDir,
318
361
  dbPath: this.settings.store.path,
319
362
  provider: this.provider.id,
320
363
  model: this.provider.model,
321
364
  requestedProvider: this.requestedProvider,
322
365
  sources: Array.from(this.sources),
366
+ extraPaths: this.settings.extraPaths,
323
367
  sourceCounts,
324
368
  cache: this.cache.enabled
325
369
  ? {
@@ -560,13 +604,27 @@ export class MemoryIndexManager {
560
604
  }
561
605
  }
562
606
  ensureWatcher() {
563
- if (!this.sources.has("memory") || !this.settings.sync.watch || this.watcher)
607
+ if (!this.sources.has("memory") || !this.settings.sync.watch || this.watcher) {
564
608
  return;
565
- const watchPaths = [
609
+ }
610
+ const additionalPaths = normalizeExtraMemoryPaths(this.workspaceDir, this.settings.extraPaths)
611
+ .map((entry) => {
612
+ try {
613
+ const stat = fsSync.lstatSync(entry);
614
+ return stat.isSymbolicLink() ? null : entry;
615
+ }
616
+ catch {
617
+ return null;
618
+ }
619
+ })
620
+ .filter((entry) => Boolean(entry));
621
+ const watchPaths = new Set([
566
622
  path.join(this.workspaceDir, "MEMORY.md"),
623
+ path.join(this.workspaceDir, "memory.md"),
567
624
  path.join(this.workspaceDir, "memory"),
568
- ];
569
- this.watcher = chokidar.watch(watchPaths, {
625
+ ...additionalPaths,
626
+ ]);
627
+ this.watcher = chokidar.watch(Array.from(watchPaths), {
570
628
  ignoreInitial: true,
571
629
  awaitWriteFinish: {
572
630
  stabilityThreshold: this.settings.sync.watchDebounceMs,
@@ -765,7 +823,7 @@ export class MemoryIndexManager {
765
823
  return this.sessionsDirty && this.sessionsDirtyFiles.size > 0;
766
824
  }
767
825
  async syncMemoryFiles(params) {
768
- const files = await listMemoryFiles(this.workspaceDir);
826
+ const files = await listMemoryFiles(this.workspaceDir, this.settings.extraPaths);
769
827
  const fileEntries = await Promise.all(files.map(async (file) => buildFileEntry(file, this.workspaceDir)));
770
828
  log.debug("memory sync: indexing memory files", {
771
829
  files: fileEntries.length,
@@ -805,7 +863,7 @@ export class MemoryIndexManager {
805
863
  });
806
864
  }
807
865
  });
808
- await this.runWithConcurrency(tasks, this.getIndexConcurrency());
866
+ await runWithConcurrency(tasks, this.getIndexConcurrency());
809
867
  const staleRows = this.db
810
868
  .prepare(`SELECT path FROM files WHERE source = ?`)
811
869
  .all("memory");
@@ -895,7 +953,7 @@ export class MemoryIndexManager {
895
953
  });
896
954
  }
897
955
  });
898
- await this.runWithConcurrency(tasks, this.getIndexConcurrency());
956
+ await runWithConcurrency(tasks, this.getIndexConcurrency());
899
957
  const staleRows = this.db
900
958
  .prepare(`SELECT path FROM files WHERE source = ?`)
901
959
  .all("sessions");
@@ -1011,7 +1069,8 @@ export class MemoryIndexManager {
1011
1069
  const batch = this.settings.remote?.batch;
1012
1070
  const enabled = Boolean(batch?.enabled &&
1013
1071
  ((this.openAi && this.provider.id === "openai") ||
1014
- (this.gemini && this.provider.id === "gemini")));
1072
+ (this.gemini && this.provider.id === "gemini") ||
1073
+ (this.voyage && this.provider.id === "voyage")));
1015
1074
  return {
1016
1075
  enabled,
1017
1076
  wait: batch?.wait ?? true,
@@ -1031,7 +1090,9 @@ export class MemoryIndexManager {
1031
1090
  ? DEFAULT_GEMINI_EMBEDDING_MODEL
1032
1091
  : fallback === "openai"
1033
1092
  ? DEFAULT_OPENAI_EMBEDDING_MODEL
1034
- : this.settings.model;
1093
+ : fallback === "voyage"
1094
+ ? DEFAULT_VOYAGE_EMBEDDING_MODEL
1095
+ : this.settings.model;
1035
1096
  const fallbackResult = await createEmbeddingProvider({
1036
1097
  config: this.cfg,
1037
1098
  agentDir: resolveAgentDir(this.cfg, this.agentId),
@@ -1046,6 +1107,7 @@ export class MemoryIndexManager {
1046
1107
  this.provider = fallbackResult.provider;
1047
1108
  this.openAi = fallbackResult.openAi;
1048
1109
  this.gemini = fallbackResult.gemini;
1110
+ this.voyage = fallbackResult.voyage;
1049
1111
  this.providerKey = this.computeProviderKey();
1050
1112
  this.batch = this.resolveBatchConfig();
1051
1113
  log.warn(`memory embeddings: switched to fallback provider (${fallback})`, { reason });
@@ -1402,7 +1464,7 @@ export class MemoryIndexManager {
1402
1464
  if (this.provider.id === "openai" && this.openAi) {
1403
1465
  const entries = Object.entries(this.openAi.headers)
1404
1466
  .filter(([key]) => key.toLowerCase() !== "authorization")
1405
- .sort(([a], [b]) => a.localeCompare(b))
1467
+ .toSorted(([a], [b]) => a.localeCompare(b))
1406
1468
  .map(([key, value]) => [key, value]);
1407
1469
  return hashText(JSON.stringify({
1408
1470
  provider: "openai",
@@ -1417,7 +1479,7 @@ export class MemoryIndexManager {
1417
1479
  const lower = key.toLowerCase();
1418
1480
  return lower !== "authorization" && lower !== "x-goog-api-key";
1419
1481
  })
1420
- .sort(([a], [b]) => a.localeCompare(b))
1482
+ .toSorted(([a], [b]) => a.localeCompare(b))
1421
1483
  .map(([key, value]) => [key, value]);
1422
1484
  return hashText(JSON.stringify({
1423
1485
  provider: "gemini",
@@ -1435,8 +1497,78 @@ export class MemoryIndexManager {
1435
1497
  if (this.provider.id === "gemini" && this.gemini) {
1436
1498
  return this.embedChunksWithGeminiBatch(chunks, entry, source);
1437
1499
  }
1500
+ if (this.provider.id === "voyage" && this.voyage) {
1501
+ return this.embedChunksWithVoyageBatch(chunks, entry, source);
1502
+ }
1438
1503
  return this.embedChunksInBatches(chunks);
1439
1504
  }
1505
+ async embedChunksWithVoyageBatch(chunks, entry, source) {
1506
+ const voyage = this.voyage;
1507
+ if (!voyage) {
1508
+ return this.embedChunksInBatches(chunks);
1509
+ }
1510
+ if (chunks.length === 0) {
1511
+ return [];
1512
+ }
1513
+ const cached = this.loadEmbeddingCache(chunks.map((chunk) => chunk.hash));
1514
+ const embeddings = Array.from({ length: chunks.length }, () => []);
1515
+ const missing = [];
1516
+ for (let i = 0; i < chunks.length; i += 1) {
1517
+ const chunk = chunks[i];
1518
+ const hit = chunk?.hash ? cached.get(chunk.hash) : undefined;
1519
+ if (hit && hit.length > 0) {
1520
+ embeddings[i] = hit;
1521
+ }
1522
+ else if (chunk) {
1523
+ missing.push({ index: i, chunk });
1524
+ }
1525
+ }
1526
+ if (missing.length === 0) {
1527
+ return embeddings;
1528
+ }
1529
+ const requests = [];
1530
+ const mapping = new Map();
1531
+ for (const item of missing) {
1532
+ const chunk = item.chunk;
1533
+ const customId = hashText(`${source}:${entry.path}:${chunk.startLine}:${chunk.endLine}:${chunk.hash}:${item.index}`);
1534
+ mapping.set(customId, { index: item.index, hash: chunk.hash });
1535
+ requests.push({
1536
+ custom_id: customId,
1537
+ body: {
1538
+ input: chunk.text,
1539
+ },
1540
+ });
1541
+ }
1542
+ const batchResult = await this.runBatchWithFallback({
1543
+ provider: "voyage",
1544
+ run: async () => await runVoyageEmbeddingBatches({
1545
+ client: voyage,
1546
+ agentId: this.agentId,
1547
+ requests,
1548
+ wait: this.batch.wait,
1549
+ concurrency: this.batch.concurrency,
1550
+ pollIntervalMs: this.batch.pollIntervalMs,
1551
+ timeoutMs: this.batch.timeoutMs,
1552
+ debug: (message, data) => log.debug(message, { ...data, source, chunks: chunks.length }),
1553
+ }),
1554
+ fallback: async () => await this.embedChunksInBatches(chunks),
1555
+ });
1556
+ if (Array.isArray(batchResult)) {
1557
+ return batchResult;
1558
+ }
1559
+ const byCustomId = batchResult;
1560
+ const toCache = [];
1561
+ for (const [customId, embedding] of byCustomId.entries()) {
1562
+ const mapped = mapping.get(customId);
1563
+ if (!mapped) {
1564
+ continue;
1565
+ }
1566
+ embeddings[mapped.index] = embedding;
1567
+ toCache.push({ hash: mapped.hash, embedding });
1568
+ }
1569
+ this.upsertEmbeddingCache(toCache);
1570
+ return embeddings;
1571
+ }
1440
1572
  async embedChunksWithOpenAiBatch(chunks, entry, source) {
1441
1573
  const openAi = this.openAi;
1442
1574
  if (!openAi) {
@@ -1623,35 +1755,6 @@ export class MemoryIndexManager {
1623
1755
  clearTimeout(timer);
1624
1756
  }
1625
1757
  }
1626
- async runWithConcurrency(tasks, limit) {
1627
- if (tasks.length === 0)
1628
- return [];
1629
- const resolvedLimit = Math.max(1, Math.min(limit, tasks.length));
1630
- const results = Array.from({ length: tasks.length });
1631
- let next = 0;
1632
- let firstError = null;
1633
- const workers = Array.from({ length: resolvedLimit }, async () => {
1634
- while (true) {
1635
- if (firstError)
1636
- return;
1637
- const index = next;
1638
- next += 1;
1639
- if (index >= tasks.length)
1640
- return;
1641
- try {
1642
- results[index] = await tasks[index]();
1643
- }
1644
- catch (err) {
1645
- firstError = err;
1646
- return;
1647
- }
1648
- }
1649
- });
1650
- await Promise.allSettled(workers);
1651
- if (firstError)
1652
- throw firstError;
1653
- return results;
1654
- }
1655
1758
  async withBatchFailureLock(fn) {
1656
1759
  let release;
1657
1760
  const wait = this.batchFailureLock;
@@ -1749,6 +1852,9 @@ export class MemoryIndexManager {
1749
1852
  async indexFile(entry, options) {
1750
1853
  const content = options.content ?? (await fs.readFile(entry.absPath, "utf-8"));
1751
1854
  const chunks = chunkMarkdown(content, this.settings.chunking).filter((chunk) => chunk.text.trim().length > 0);
1855
+ if (options.source === "sessions" && "lineMap" in entry) {
1856
+ remapChunkLines(chunks, entry.lineMap);
1857
+ }
1752
1858
  const embeddings = this.batch.enabled
1753
1859
  ? await this.embedChunksWithBatch(chunks, entry, options.source)
1754
1860
  : await this.embedChunksInBatches(chunks);
@@ -1,4 +1,40 @@
1
+ import { createSubsystemLogger } from "../logging/subsystem.js";
2
+ import { resolveMemoryBackendConfig } from "./backend-config.js";
3
+ const log = createSubsystemLogger("memory");
4
+ const QMD_MANAGER_CACHE = new Map();
1
5
  export async function getMemorySearchManager(params) {
6
+ const resolved = resolveMemoryBackendConfig(params);
7
+ if (resolved.backend === "qmd" && resolved.qmd) {
8
+ const cacheKey = buildQmdCacheKey(params.agentId, resolved.qmd);
9
+ const cached = QMD_MANAGER_CACHE.get(cacheKey);
10
+ if (cached) {
11
+ return { manager: cached };
12
+ }
13
+ try {
14
+ // @ts-expect-error qmd-manager is an optional module that may not exist at build time
15
+ const { QmdMemoryManager } = await import("./qmd-manager.js");
16
+ const primary = await QmdMemoryManager.create({
17
+ cfg: params.cfg,
18
+ agentId: params.agentId,
19
+ resolved,
20
+ });
21
+ if (primary) {
22
+ const wrapper = new FallbackMemoryManager({
23
+ primary,
24
+ fallbackFactory: async () => {
25
+ const { MemoryIndexManager } = await import("./manager.js");
26
+ return await MemoryIndexManager.get(params);
27
+ },
28
+ }, () => QMD_MANAGER_CACHE.delete(cacheKey));
29
+ QMD_MANAGER_CACHE.set(cacheKey, wrapper);
30
+ return { manager: wrapper };
31
+ }
32
+ }
33
+ catch (err) {
34
+ const message = err instanceof Error ? err.message : String(err);
35
+ log.warn(`qmd memory unavailable; falling back to builtin: ${message}`);
36
+ }
37
+ }
2
38
  try {
3
39
  const { MemoryIndexManager } = await import("./manager.js");
4
40
  const manager = await MemoryIndexManager.get(params);
@@ -9,3 +45,140 @@ export async function getMemorySearchManager(params) {
9
45
  return { manager: null, error: message };
10
46
  }
11
47
  }
48
+ class FallbackMemoryManager {
49
+ deps;
50
+ onClose;
51
+ fallback = null;
52
+ primaryFailed = false;
53
+ lastError;
54
+ cacheEvicted = false;
55
+ constructor(deps, onClose) {
56
+ this.deps = deps;
57
+ this.onClose = onClose;
58
+ }
59
+ async search(query, opts) {
60
+ if (!this.primaryFailed) {
61
+ try {
62
+ return await this.deps.primary.search(query, opts);
63
+ }
64
+ catch (err) {
65
+ this.primaryFailed = true;
66
+ this.lastError = err instanceof Error ? err.message : String(err);
67
+ log.warn(`qmd memory failed; switching to builtin index: ${this.lastError}`);
68
+ await this.deps.primary.close?.().catch(() => { });
69
+ // Evict the failed wrapper so the next request can retry QMD with a fresh manager.
70
+ this.evictCacheEntry();
71
+ }
72
+ }
73
+ const fallback = await this.ensureFallback();
74
+ if (fallback) {
75
+ return await fallback.search(query, opts);
76
+ }
77
+ throw new Error(this.lastError ?? "memory search unavailable");
78
+ }
79
+ async readFile(params) {
80
+ if (!this.primaryFailed) {
81
+ return await this.deps.primary.readFile(params);
82
+ }
83
+ const fallback = await this.ensureFallback();
84
+ if (fallback) {
85
+ return await fallback.readFile(params);
86
+ }
87
+ throw new Error(this.lastError ?? "memory read unavailable");
88
+ }
89
+ status() {
90
+ if (!this.primaryFailed) {
91
+ return this.deps.primary.status();
92
+ }
93
+ const fallbackStatus = this.fallback?.status();
94
+ const fallbackInfo = { from: "qmd", reason: this.lastError ?? "unknown" };
95
+ if (fallbackStatus) {
96
+ const custom = fallbackStatus.custom ?? {};
97
+ return {
98
+ ...fallbackStatus,
99
+ fallback: fallbackInfo,
100
+ custom: {
101
+ ...custom,
102
+ fallback: { disabled: true, reason: this.lastError ?? "unknown" },
103
+ },
104
+ };
105
+ }
106
+ const primaryStatus = this.deps.primary.status();
107
+ const custom = primaryStatus.custom ?? {};
108
+ return {
109
+ ...primaryStatus,
110
+ fallback: fallbackInfo,
111
+ custom: {
112
+ ...custom,
113
+ fallback: { disabled: true, reason: this.lastError ?? "unknown" },
114
+ },
115
+ };
116
+ }
117
+ async sync(params) {
118
+ if (!this.primaryFailed) {
119
+ await this.deps.primary.sync?.(params);
120
+ return;
121
+ }
122
+ const fallback = await this.ensureFallback();
123
+ await fallback?.sync?.(params);
124
+ }
125
+ async probeEmbeddingAvailability() {
126
+ if (!this.primaryFailed) {
127
+ return await this.deps.primary.probeEmbeddingAvailability();
128
+ }
129
+ const fallback = await this.ensureFallback();
130
+ if (fallback) {
131
+ return await fallback.probeEmbeddingAvailability();
132
+ }
133
+ return { ok: false, error: this.lastError ?? "memory embeddings unavailable" };
134
+ }
135
+ async probeVectorAvailability() {
136
+ if (!this.primaryFailed) {
137
+ return await this.deps.primary.probeVectorAvailability();
138
+ }
139
+ const fallback = await this.ensureFallback();
140
+ return (await fallback?.probeVectorAvailability()) ?? false;
141
+ }
142
+ async close() {
143
+ await this.deps.primary.close?.();
144
+ await this.fallback?.close?.();
145
+ this.evictCacheEntry();
146
+ }
147
+ async ensureFallback() {
148
+ if (this.fallback) {
149
+ return this.fallback;
150
+ }
151
+ const fallback = await this.deps.fallbackFactory();
152
+ if (!fallback) {
153
+ log.warn("memory fallback requested but builtin index is unavailable");
154
+ return null;
155
+ }
156
+ this.fallback = fallback;
157
+ return this.fallback;
158
+ }
159
+ evictCacheEntry() {
160
+ if (this.cacheEvicted) {
161
+ return;
162
+ }
163
+ this.cacheEvicted = true;
164
+ this.onClose?.();
165
+ }
166
+ }
167
+ function buildQmdCacheKey(agentId, config) {
168
+ return `${agentId}:${stableSerialize(config)}`;
169
+ }
170
+ function stableSerialize(value) {
171
+ return JSON.stringify(sortValue(value));
172
+ }
173
+ function sortValue(value) {
174
+ if (Array.isArray(value)) {
175
+ return value.map((entry) => sortValue(entry));
176
+ }
177
+ if (value && typeof value === "object") {
178
+ const sortedEntries = Object.keys(value)
179
+ .toSorted((a, b) => a.localeCompare(b))
180
+ .map((key) => [key, sortValue(value[key])]);
181
+ return Object.fromEntries(sortedEntries);
182
+ }
183
+ return value;
184
+ }
@@ -1,6 +1,7 @@
1
1
  import fs from "node:fs/promises";
2
2
  import path from "node:path";
3
3
  import { resolveSessionTranscriptsDirForAgent } from "../config/sessions/paths.js";
4
+ import { redactSensitiveText } from "../logging/redact.js";
4
5
  import { createSubsystemLogger } from "../logging/subsystem.js";
5
6
  import { hashText } from "./internal.js";
6
7
  const log = createSubsystemLogger("memory");
@@ -55,7 +56,9 @@ export async function buildSessionEntry(absPath) {
55
56
  const raw = await fs.readFile(absPath, "utf-8");
56
57
  const lines = raw.split("\n");
57
58
  const collected = [];
58
- for (const line of lines) {
59
+ const lineMap = [];
60
+ for (let jsonlIdx = 0; jsonlIdx < lines.length; jsonlIdx++) {
61
+ const line = lines[jsonlIdx];
59
62
  if (!line.trim())
60
63
  continue;
61
64
  let record;
@@ -78,8 +81,10 @@ export async function buildSessionEntry(absPath) {
78
81
  const text = extractSessionText(message.content);
79
82
  if (!text)
80
83
  continue;
84
+ const safe = redactSensitiveText(text, { mode: "tools" });
81
85
  const label = message.role === "user" ? "User" : "Assistant";
82
- collected.push(`${label}: ${text}`);
86
+ collected.push(`${label}: ${safe}`);
87
+ lineMap.push(jsonlIdx + 1);
83
88
  }
84
89
  const content = collected.join("\n");
85
90
  return {
@@ -87,8 +92,9 @@ export async function buildSessionEntry(absPath) {
87
92
  absPath,
88
93
  mtimeMs: stat.mtimeMs,
89
94
  size: stat.size,
90
- hash: hashText(content),
95
+ hash: hashText(content + "\n" + lineMap.join(",")),
91
96
  content,
97
+ lineMap,
92
98
  };
93
99
  }
94
100
  catch (err) {
@@ -17,10 +17,16 @@ import { ensurePoolbotCliOnPath } from "../infra/path-env.js";
17
17
  import { VERSION } from "../version.js";
18
18
  import { GATEWAY_CLIENT_MODES, GATEWAY_CLIENT_NAMES } from "../utils/message-channel.js";
19
19
  import { ensureNodeHostConfig, saveNodeHostConfig } from "./config.js";
20
+ import { withTimeout } from "./with-timeout.js";
20
21
  import { GatewayClient } from "../gateway/client.js";
21
22
  function resolveExecSecurity(value) {
22
23
  return value === "deny" || value === "allowlist" || value === "full" ? value : "allowlist";
23
24
  }
25
+ function isCmdExeInvocation(argv) {
26
+ const bin = argv[0]?.trim().toLowerCase().replace(/\\/g, "/") ?? "";
27
+ const base = bin.split("/").pop() ?? "";
28
+ return base === "cmd" || base === "cmd.exe";
29
+ }
24
30
  function resolveExecAsk(value) {
25
31
  return value === "off" || value === "on-miss" || value === "always" ? value : "on-miss";
26
32
  }
@@ -124,26 +130,6 @@ async function ensureBrowserControlService() {
124
130
  })();
125
131
  return browserControlReady;
126
132
  }
127
- async function withTimeout(promise, timeoutMs, label) {
128
- const resolved = typeof timeoutMs === "number" && Number.isFinite(timeoutMs)
129
- ? Math.max(1, Math.floor(timeoutMs))
130
- : undefined;
131
- if (!resolved)
132
- return await promise;
133
- let timer;
134
- const timeoutPromise = new Promise((_, reject) => {
135
- timer = setTimeout(() => {
136
- reject(new Error(`${label ?? "request"} timed out`));
137
- }, resolved);
138
- });
139
- try {
140
- return await Promise.race([promise, timeoutPromise]);
141
- }
142
- finally {
143
- if (timer)
144
- clearTimeout(timer);
145
- }
146
- }
147
133
  function isProfileAllowed(params) {
148
134
  const { allowProfiles, profile } = params;
149
135
  if (!allowProfiles.length)
@@ -422,7 +408,7 @@ export async function runNodeHost(opts) {
422
408
  },
423
409
  });
424
410
  const skillBins = new SkillBinsCache(async () => {
425
- const res = (await client.request("skills.bins", {}));
411
+ const res = await client.request("skills.bins", {});
426
412
  const bins = Array.isArray(res?.bins) ? res.bins.map((bin) => String(bin)) : [];
427
413
  return bins;
428
414
  });
@@ -562,7 +548,7 @@ async function handleInvoke(frame, client, skillBins) {
562
548
  query[key] = typeof value === "string" ? value : String(value);
563
549
  }
564
550
  const dispatcher = createBrowserRouteDispatcher(createBrowserControlContext());
565
- const response = await withTimeout(dispatcher.dispatch({
551
+ const response = await withTimeout((_signal) => dispatcher.dispatch({
566
552
  method: method === "DELETE" ? "DELETE" : method === "POST" ? "POST" : "GET",
567
553
  path,
568
554
  query,
@@ -597,7 +583,9 @@ async function handleInvoke(frame, client, skillBins) {
597
583
  return file;
598
584
  }
599
585
  catch (err) {
600
- throw new Error(`browser proxy file read failed for ${p}: ${String(err)}`);
586
+ throw new Error(`browser proxy file read failed for ${p}: ${String(err)}`, {
587
+ cause: err,
588
+ });
601
589
  }
602
590
  }));
603
591
  if (loaded.length > 0)
@@ -675,6 +663,7 @@ async function handleInvoke(frame, client, skillBins) {
675
663
  env,
676
664
  skillBins: bins,
677
665
  autoAllowSkills,
666
+ platform: process.platform,
678
667
  });
679
668
  analysisOk = allowlistEval.analysisOk;
680
669
  allowlistMatches = allowlistEval.allowlistMatches;
@@ -698,6 +687,14 @@ async function handleInvoke(frame, client, skillBins) {
698
687
  security === "allowlist" && analysisOk ? allowlistEval.allowlistSatisfied : false;
699
688
  segments = analysis.segments;
700
689
  }
690
+ const isWindows = process.platform === "win32";
691
+ const cmdInvocation = rawCommand
692
+ ? isCmdExeInvocation(segments[0]?.argv ?? [])
693
+ : isCmdExeInvocation(argv);
694
+ if (security === "allowlist" && isWindows && cmdInvocation) {
695
+ analysisOk = false;
696
+ allowlistSatisfied = false;
697
+ }
701
698
  const useMacAppExec = process.platform === "darwin";
702
699
  if (useMacAppExec) {
703
700
  const approvalDecision = params.approvalDecision === "allow-once" || params.approvalDecision === "allow-always"
@@ -853,7 +850,20 @@ async function handleInvoke(frame, client, skillBins) {
853
850
  });
854
851
  return;
855
852
  }
856
- const result = await runCommand(argv, params.cwd?.trim() || undefined, env, params.timeoutMs ?? undefined);
853
+ /* On Windows, avoid spawning via cmd.exe when the allowlist already approved
854
+ * a single-segment command — run the parsed argv directly instead. */
855
+ let execArgv = argv;
856
+ if (security === "allowlist" &&
857
+ isWindows &&
858
+ !approvedByAsk &&
859
+ rawCommand &&
860
+ analysisOk &&
861
+ allowlistSatisfied &&
862
+ segments.length === 1 &&
863
+ segments[0]?.argv.length > 0) {
864
+ execArgv = segments[0].argv;
865
+ }
866
+ const result = await runCommand(execArgv, params.cwd?.trim() || undefined, env, params.timeoutMs ?? undefined);
857
867
  if (result.truncated) {
858
868
  const suffix = "... (truncated)";
859
869
  if (result.stderr.trim().length > 0) {