@vellumai/assistant 0.12.2 → 0.12.3-staging.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (298) hide show
  1. package/.env.example +2 -0
  2. package/AGENTS.md +1 -1
  3. package/docs/architecture/integrations.md +7 -0
  4. package/docs/architecture/memory.md +17 -2
  5. package/docs/credential-execution-service.md +1 -1
  6. package/docs/desktop-browser-cli.md +7 -3
  7. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/secret-detection.test.ts +1 -0
  8. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/secret-detection.ts +4 -1
  9. package/node_modules/@vellumai/environments/src/shell.test.ts +21 -0
  10. package/node_modules/@vellumai/environments/src/shell.ts +24 -0
  11. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/secret-detection.test.ts +1 -0
  12. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/secret-detection.ts +4 -1
  13. package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +8 -2
  14. package/node_modules/@vellumai/service-contracts/src/__tests__/secret-detection.test.ts +1 -0
  15. package/node_modules/@vellumai/service-contracts/src/secret-detection.ts +4 -1
  16. package/openapi.yaml +54 -5
  17. package/package.json +1 -1
  18. package/scripts/smoke-desktop-browser-cli.ts +1 -0
  19. package/scripts/sync-llm-catalog.ts +3 -0
  20. package/scripts/voice-ttft-spike.ts +2 -2
  21. package/src/__tests__/agent-loop.test.ts +259 -0
  22. package/src/__tests__/agent-wake-delegation-prompt.test.ts +64 -2
  23. package/src/__tests__/anthropic-provider.test.ts +55 -0
  24. package/src/__tests__/approval-interception-trust-gates.test.ts +40 -0
  25. package/src/__tests__/attachments-store.test.ts +22 -3
  26. package/src/__tests__/channel-approval.test.ts +9 -14
  27. package/src/__tests__/channel-reply-delivery.test.ts +56 -0
  28. package/src/__tests__/chat-credential-redaction.test.ts +23 -0
  29. package/src/__tests__/computer-use-screenshot-attachments.test.ts +411 -0
  30. package/src/__tests__/computer-use-screenshot-selection.test.ts +50 -0
  31. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +2 -0
  32. package/src/__tests__/conversation-agent-loop-overflow.test.ts +2 -0
  33. package/src/__tests__/conversation-agent-loop.test.ts +257 -6
  34. package/src/__tests__/conversation-attachments.test.ts +106 -0
  35. package/src/__tests__/conversation-fork-crud.test.ts +191 -0
  36. package/src/__tests__/conversation-rendered-delegation-state.test.ts +276 -0
  37. package/src/__tests__/credential-execution-client.test.ts +70 -53
  38. package/src/__tests__/db-conversation-tool-surface.test.ts +221 -0
  39. package/src/__tests__/events-tail-route.test.ts +33 -0
  40. package/src/__tests__/history-repair.test.ts +53 -0
  41. package/src/__tests__/inference-profile-session-handler.test.ts +26 -0
  42. package/src/__tests__/list-messages-tool-merge.test.ts +85 -1
  43. package/src/__tests__/llm-catalog-parity.test.ts +27 -4
  44. package/src/__tests__/oauth-apps-routes.test.ts +1 -0
  45. package/src/__tests__/oauth-commands-routes.test.ts +114 -101
  46. package/src/__tests__/oauth-connect-orchestrator.test.ts +2 -0
  47. package/src/__tests__/oauth-provider-serializer.test.ts +1 -0
  48. package/src/__tests__/oauth-providers-routes.test.ts +2 -0
  49. package/src/__tests__/persist-media-references.test.ts +50 -0
  50. package/src/__tests__/plugin-import-boundary-guard.test.ts +0 -1
  51. package/src/__tests__/run-conversation-turn-persistence.test.ts +138 -1
  52. package/src/__tests__/schedule-routes.test.ts +20 -0
  53. package/src/__tests__/scheduler-result-notification.test.ts +23 -4
  54. package/src/__tests__/script-proxy-certs.test.ts +1 -1
  55. package/src/__tests__/secret-routes-platform-proxy.test.ts +86 -31
  56. package/src/__tests__/secret-routes-scrub.test.ts +22 -27
  57. package/src/__tests__/secret-scanner.test.ts +20 -0
  58. package/src/__tests__/secure-keys-managed-failover.test.ts +8 -0
  59. package/src/__tests__/secure-keys.test.ts +7 -3
  60. package/src/__tests__/server-tool-pairing.test.ts +107 -0
  61. package/src/__tests__/skills.test.ts +5 -4
  62. package/src/__tests__/subagent-tool-gate-mode.test.ts +226 -0
  63. package/src/__tests__/terminal-tools.test.ts +8 -0
  64. package/src/__tests__/tool-result-follow-up.test.ts +99 -0
  65. package/src/__tests__/tool-result-metadata-plumbing.test.ts +63 -0
  66. package/src/__tests__/unicode.test.ts +36 -0
  67. package/src/agent/loop.ts +37 -12
  68. package/src/agent/tool-result-follow-up.ts +70 -0
  69. package/src/api/attachment-provenance.test.ts +68 -0
  70. package/src/api/computer-use-tool.test.ts +55 -0
  71. package/src/api/computer-use-tool.ts +27 -0
  72. package/src/api/events/assistant-outbound-attachment.ts +3 -0
  73. package/src/api/events/desktop-activity-changed.ts +10 -0
  74. package/src/api/events/question-request.ts +1 -0
  75. package/src/api/index.ts +11 -0
  76. package/src/api/responses/conversation-message.ts +3 -0
  77. package/src/approvals/approval-primitive.ts +5 -2
  78. package/src/approvals/scoped-approval-grants.ts +6 -2
  79. package/src/browser/virtual-desktop-target.ts +1 -2
  80. package/src/cli/commands/__tests__/cli-test-harness.ts +21 -3
  81. package/src/cli/commands/__tests__/plugins.test.ts +20 -1
  82. package/src/cli/commands/__tests__/schedules.test.ts +14 -0
  83. package/src/cli/commands/bash.help.ts +4 -3
  84. package/src/cli/commands/browser.help.ts +5 -1
  85. package/src/cli/commands/credentials.help.ts +3 -3
  86. package/src/cli/commands/oauth/index.help.ts +10 -0
  87. package/src/cli/commands/oauth/providers.ts +12 -0
  88. package/src/cli/commands/oauth/request.test.ts +179 -2
  89. package/src/cli/commands/oauth/request.ts +41 -10
  90. package/src/cli/commands/plugins.ts +12 -5
  91. package/src/cli/commands/schedules.ts +2 -0
  92. package/src/cli/lib/__tests__/inspect-plugin.test.ts +54 -0
  93. package/src/cli/lib/__tests__/install-from-github.test.ts +41 -0
  94. package/src/cli/lib/__tests__/local-plugin-upgrade.test.ts +133 -51
  95. package/src/cli/lib/__tests__/plugin-catalog-cache.test.ts +8 -2
  96. package/src/cli/lib/bundled-marketplace.json +855 -0
  97. package/src/cli/lib/bundled-plugin-packages.json +783 -1
  98. package/src/cli/lib/inspect-plugin.ts +11 -4
  99. package/src/cli/lib/upgrade-plugin.ts +19 -11
  100. package/src/cli/output.ts +18 -4
  101. package/src/config/__tests__/default-provider.test.ts +4 -0
  102. package/src/config/bundled-skills/schedule/SKILL.md +6 -6
  103. package/src/config/loader.ts +9 -17
  104. package/src/config/platform-identity.ts +8 -8
  105. package/src/config/profile-text-generation.test.ts +51 -0
  106. package/src/config/profile-text-generation.ts +51 -0
  107. package/src/config/schemas/__tests__/memory-v3.test.ts +12 -0
  108. package/src/config/schemas/llm.ts +7 -2
  109. package/src/config/schemas/mcp.ts +5 -1
  110. package/src/config/schemas/memory-v3.ts +12 -0
  111. package/src/credential-execution/ces-runtime.ts +2 -2
  112. package/src/credential-execution/executable-discovery.ts +36 -134
  113. package/src/credential-execution/process-manager.test.ts +20 -24
  114. package/src/credential-execution/process-manager.ts +24 -19
  115. package/src/daemon/__tests__/conversation-tool-setup.test.ts +43 -0
  116. package/src/daemon/__tests__/turn-tail-deleted-conversation.test.ts +36 -0
  117. package/src/daemon/assistant-attachments.ts +20 -12
  118. package/src/daemon/chat-credential-redaction.ts +16 -1
  119. package/src/daemon/conversation-agent-loop-handlers.ts +74 -3
  120. package/src/daemon/conversation-agent-loop.ts +12 -0
  121. package/src/daemon/conversation-attachments.ts +93 -12
  122. package/src/daemon/conversation-tool-setup.ts +71 -3
  123. package/src/daemon/conversation-turn-finalize.ts +26 -13
  124. package/src/daemon/conversation.ts +72 -17
  125. package/src/daemon/daemon-control.ts +3 -7
  126. package/src/daemon/lifecycle.ts +4 -4
  127. package/src/daemon/mcp-reload-service.ts +1 -1
  128. package/src/daemon/message-types/shared.ts +1 -0
  129. package/src/daemon/orphan-reaper.ts +4 -3
  130. package/src/daemon/persist-media-references.ts +20 -3
  131. package/src/daemon/tool-setup-types.ts +6 -0
  132. package/src/daemon/wake-conversation-ops.ts +50 -15
  133. package/src/desktop/desktop-automation-lease.test.ts +230 -0
  134. package/src/desktop/desktop-automation-lease.ts +90 -6
  135. package/src/desktop/desktop-help.ts +15 -0
  136. package/src/desktop/desktop-stream-bridge.test.ts +65 -10
  137. package/src/desktop/desktop-stream-bridge.ts +3 -3
  138. package/src/desktop/desktop-wallpaper-renderer.ts +132 -0
  139. package/src/desktop/desktop-wallpaper-worker.ts +17 -0
  140. package/src/desktop/desktop-wallpaper.test.ts +48 -7
  141. package/src/desktop/desktop-wallpaper.ts +34 -102
  142. package/src/desktop/virtual-desktop-feature.ts +1 -1
  143. package/src/mcp/__tests__/manager-tool-caps.test.ts +111 -0
  144. package/src/mcp/__tests__/startup.test.ts +30 -7
  145. package/src/mcp/__tests__/tool-caps.test.ts +107 -0
  146. package/src/mcp/manager.ts +168 -101
  147. package/src/mcp/startup.ts +33 -13
  148. package/src/mcp/tool-caps.ts +176 -0
  149. package/src/messaging/provider-message-metadata.ts +3 -3
  150. package/src/notifications/__tests__/copy-composer.test.ts +70 -0
  151. package/src/notifications/copy-composer.ts +11 -3
  152. package/src/notifications/schedule-result-producer.ts +4 -6
  153. package/src/oauth/AGENTS.md +2 -0
  154. package/src/oauth/__tests__/identity-verifier.test.ts +36 -1
  155. package/src/oauth/identity-verifier.ts +24 -0
  156. package/src/oauth/oauth-store.ts +11 -1
  157. package/src/oauth/provider-serializer.ts +1 -0
  158. package/src/oauth/seed-providers.ts +12 -0
  159. package/src/permissions/confirmation-guardian-request.test.ts +16 -0
  160. package/src/permissions/confirmation-guardian-request.ts +2 -3
  161. package/src/permissions/question-prompter.test.ts +31 -0
  162. package/src/permissions/question-prompter.ts +2 -0
  163. package/src/persistence/attachments-store.ts +84 -44
  164. package/src/persistence/conversation-crud.ts +111 -20
  165. package/src/persistence/conversation-plugin-facade.ts +30 -0
  166. package/src/persistence/conversation-tool-surface.ts +135 -0
  167. package/src/persistence/conversation-types.test.ts +32 -0
  168. package/src/persistence/conversation-types.ts +26 -4
  169. package/src/persistence/migrations/378-create-conversation-tool-surfaces.test.ts +78 -0
  170. package/src/persistence/migrations/378-create-conversation-tool-surfaces.ts +29 -0
  171. package/src/persistence/migrations/379-oauth-providers-response-ok-field.test.ts +92 -0
  172. package/src/persistence/migrations/379-oauth-providers-response-ok-field.ts +32 -0
  173. package/src/persistence/migrations/380-conversation-tool-surfaces-delegate-independent-tasks.test.ts +90 -0
  174. package/src/persistence/migrations/380-conversation-tool-surfaces-delegate-independent-tasks.ts +34 -0
  175. package/src/persistence/schema/conversation-tool-surfaces.ts +32 -0
  176. package/src/persistence/schema/index.ts +1 -0
  177. package/src/persistence/schema/oauth.ts +1 -0
  178. package/src/persistence/steps.ts +24 -3
  179. package/src/plugin-api/conversation-turn.ts +31 -7
  180. package/src/plugin-api/index.ts +11 -1
  181. package/src/plugin-api/plugin-channel-turn-trust.test.ts +133 -0
  182. package/src/plugin-api/plugin-channel-turn-trust.ts +71 -0
  183. package/src/plugins/defaults/memory/AGENTS.md +39 -5
  184. package/src/plugins/defaults/memory/__tests__/buffer-file.test.ts +365 -0
  185. package/src/plugins/defaults/memory/__tests__/buffer-format.test.ts +43 -0
  186. package/src/plugins/defaults/memory/__tests__/conversation-memory-purge.test.ts +1 -0
  187. package/src/plugins/defaults/memory/__tests__/db-memory-attach.test.ts +2 -0
  188. package/src/plugins/defaults/memory/__tests__/fixtures/buffer-appender.ts +17 -0
  189. package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +74 -0
  190. package/src/plugins/defaults/memory/__tests__/memory-run-evidence.test.ts +161 -0
  191. package/src/plugins/defaults/memory/__tests__/relocated-memory-test-rows.ts +10 -0
  192. package/src/plugins/defaults/memory/buffer-file.ts +354 -0
  193. package/src/plugins/defaults/memory/buffer-format.ts +40 -0
  194. package/src/plugins/defaults/memory/context-search/agent-runner.ts +1 -2
  195. package/src/plugins/defaults/memory/context-search/format.ts +2 -1
  196. package/src/plugins/defaults/memory/context-search/sources/memory-v2.ts +2 -1
  197. package/src/plugins/defaults/memory/context-search/sources/workspace.ts +2 -1
  198. package/src/plugins/defaults/memory/conversation-memory-purge.ts +4 -0
  199. package/src/plugins/defaults/memory/graph/capability-seed.ts +1 -2
  200. package/src/plugins/defaults/memory/graph/tool-handlers.ts +1 -42
  201. package/src/plugins/defaults/memory/host-utils.ts +0 -10
  202. package/src/plugins/defaults/memory/injectors.ts +4 -3
  203. package/src/plugins/defaults/memory/memory-retrospective-job.ts +72 -183
  204. package/src/plugins/defaults/memory/memory-run-evidence.ts +213 -0
  205. package/src/plugins/defaults/memory/src/memory-item-routes.test.ts +1 -1
  206. package/src/plugins/defaults/memory/substrate/__tests__/consolidation-job.test.ts +407 -99
  207. package/src/plugins/defaults/memory/substrate/__tests__/consolidation-prompt-flag-gating-guard.test.ts +10 -0
  208. package/src/plugins/defaults/memory/substrate/__tests__/prompts-consolidation.test.ts +107 -7
  209. package/src/plugins/defaults/memory/substrate/consolidation-job.ts +307 -86
  210. package/src/plugins/defaults/memory/substrate/consolidation-tool-surface.ts +34 -0
  211. package/src/plugins/defaults/memory/substrate/page-index.ts +2 -1
  212. package/src/plugins/defaults/memory/substrate/prompts/consolidation.ts +89 -49
  213. package/src/plugins/defaults/memory/substrate/sweep-job.ts +1 -1
  214. package/src/plugins/defaults/memory/tools.ts +1 -1
  215. package/src/plugins/defaults/memory/v1/graph/consolidation.ts +2 -2
  216. package/src/plugins/defaults/memory/v1/graph/extraction.ts +2 -1
  217. package/src/plugins/defaults/memory/v1/graph/retriever.ts +1 -1
  218. package/src/plugins/defaults/memory/v2/__tests__/migration.test.ts +5 -0
  219. package/src/plugins/defaults/memory/v2/__tests__/reranker.test.ts +5 -2
  220. package/src/plugins/defaults/memory/v2/reranker.ts +2 -1
  221. package/src/plugins/defaults/memory/v3/__tests__/injection.test.ts +81 -1
  222. package/src/plugins/defaults/memory/v3/__tests__/orchestrate.test.ts +87 -0
  223. package/src/plugins/defaults/memory/v3/__tests__/plugin-schema.test.ts +12 -0
  224. package/src/plugins/defaults/memory/v3/__tests__/pool-log-store.test.ts +183 -4
  225. package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +103 -2
  226. package/src/plugins/defaults/memory/v3/card.ts +2 -1
  227. package/src/plugins/defaults/memory/v3/injector.ts +212 -178
  228. package/src/plugins/defaults/memory/v3/orchestrate.ts +112 -25
  229. package/src/plugins/defaults/memory/v3/plugin-schema.ts +54 -4
  230. package/src/plugins/defaults/memory/v3/pool-log-store.ts +253 -1
  231. package/src/plugins/defaults/memory/v3/pool-select.test.ts +35 -2
  232. package/src/plugins/defaults/memory/v3/pool-select.ts +31 -19
  233. package/src/plugins/defaults/memory/v3/sections.ts +2 -1
  234. package/src/plugins/defaults/memory/v3/shadow-plugin.ts +41 -14
  235. package/src/plugins/defaults/tool-error/hooks/post-tool-use.ts +4 -1
  236. package/src/plugins/defaults/tool-result-truncate/terminal.ts +1 -46
  237. package/src/prompts/__tests__/parallel-tasks-section.test.ts +25 -0
  238. package/src/prompts/delegation-gate.ts +57 -0
  239. package/src/prompts/system-prompt.ts +14 -32
  240. package/src/providers/inference/adapter-factory.ts +6 -0
  241. package/src/providers/jev/client.test.ts +260 -0
  242. package/src/providers/jev/client.ts +518 -0
  243. package/src/providers/model-catalog.ts +58 -3
  244. package/src/providers/server-tool-pairing.ts +16 -7
  245. package/src/runtime/AGENTS.md +2 -2
  246. package/src/runtime/__tests__/agent-wake.test.ts +94 -1
  247. package/src/runtime/agent-wake.ts +31 -4
  248. package/src/runtime/guardian-action-service.ts +2 -17
  249. package/src/runtime/guardian-reply-router.ts +1 -8
  250. package/src/runtime/http-server.ts +2 -2
  251. package/src/runtime/migrations/__tests__/vbundle-import-policy.test.ts +89 -0
  252. package/src/runtime/migrations/vbundle-import-policy.ts +33 -11
  253. package/src/runtime/routes/__tests__/inference-profiles-routes.test.ts +24 -2
  254. package/src/runtime/routes/channel-route-shared.ts +1 -9
  255. package/src/runtime/routes/conversation-routes.ts +29 -4
  256. package/src/runtime/routes/desktop-setup-routes.test.ts +2 -2
  257. package/src/runtime/routes/desktop-setup-routes.ts +8 -5
  258. package/src/runtime/routes/guardian-approval-interception.ts +24 -0
  259. package/src/runtime/routes/inbound-message-handler.ts +2 -3
  260. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +1 -1
  261. package/src/runtime/routes/inbound-stages/background-dispatch.ts +7 -4
  262. package/src/runtime/routes/inference-profile-session-handler.ts +11 -1
  263. package/src/runtime/routes/inference-profiles-routes.ts +9 -0
  264. package/src/runtime/routes/oauth-commands-routes.ts +57 -54
  265. package/src/runtime/routes/oauth-providers.ts +4 -0
  266. package/src/runtime/routes/oauth-request-hints.test.ts +246 -0
  267. package/src/runtime/routes/oauth-request-hints.ts +149 -0
  268. package/src/runtime/routes/schedule-routes.ts +2 -0
  269. package/src/runtime/routes/secret-routes.ts +99 -119
  270. package/src/schedule/__tests__/worker-mcp-bootstrap.test.ts +107 -0
  271. package/src/schedule/__tests__/worker-mcp-readiness.test.ts +173 -0
  272. package/src/schedule/__tests__/worker-mcp-tools.test.ts +13 -47
  273. package/src/schedule/run-script.ts +2 -2
  274. package/src/schedule/scheduler.ts +47 -11
  275. package/src/schedule/tool-surface-readiness.ts +65 -0
  276. package/src/schedule/worker-mcp.ts +85 -0
  277. package/src/schedule/worker.ts +9 -31
  278. package/src/security/secure-keys.ts +60 -48
  279. package/src/tools/ask-question/ask-question-tool.test.ts +167 -5
  280. package/src/tools/ask-question/ask-question-tool.ts +119 -23
  281. package/src/tools/browser/browser-execution.ts +7 -1
  282. package/src/tools/host-terminal/host-shell.ts +12 -6
  283. package/src/tools/shared/filesystem/file-ops-service.ts +1 -31
  284. package/src/tools/shared/shell-output.test.ts +10 -0
  285. package/src/tools/shared/shell-output.ts +14 -2
  286. package/src/tools/skills/sandbox-runner.ts +13 -2
  287. package/src/tools/skills/scaffold-managed.ts +2 -1
  288. package/src/tools/terminal/__tests__/safe-env.test.ts +33 -4
  289. package/src/tools/terminal/__tests__/sanitized-bash.test.ts +0 -14
  290. package/src/tools/terminal/safe-env.ts +35 -24
  291. package/src/tools/terminal/sanitized-bash.ts +15 -2
  292. package/src/tools/terminal/shell-launch.test.ts +162 -0
  293. package/src/tools/terminal/shell.test.ts +29 -0
  294. package/src/tools/terminal/shell.ts +13 -7
  295. package/src/util/browser-human-verification.ts +14 -0
  296. package/src/util/host-process.test.ts +17 -1
  297. package/src/util/host-process.ts +24 -0
  298. package/src/util/unicode.ts +29 -0
@@ -28,13 +28,17 @@ import { Database } from "bun:sqlite";
28
28
  import { afterAll, beforeEach, describe, expect, mock, test } from "bun:test";
29
29
 
30
30
  import type { OrchestrateResult } from "../orchestrate.js";
31
- import { ensureMemoryV3PoolsSchema } from "../plugin-schema.js";
31
+ import {
32
+ ensureMemoryV3PoolInputsSchema,
33
+ ensureMemoryV3PoolsSchema,
34
+ } from "../plugin-schema.js";
32
35
  import type {
33
36
  PoolCandidateRecord,
34
37
  PoolLane,
35
38
  PoolRecord,
36
39
  } from "../pool-log-store.js";
37
- import type { Section, Slug } from "../types.js";
40
+ import { renderFinderLine } from "../pool-select.js";
41
+ import type { MemoryRoutingTurn, Section, Slug } from "../types.js";
38
42
 
39
43
  const realDb = {
40
44
  ...(await import("../../../../../persistence/db-connection.js")),
@@ -50,6 +54,7 @@ makeDb();
50
54
  function makeDb() {
51
55
  memorySqlite = new Database(":memory:");
52
56
  ensureMemoryV3PoolsSchema(memorySqlite);
57
+ ensureMemoryV3PoolInputsSchema(memorySqlite);
53
58
  }
54
59
 
55
60
  mock.module("../../../../../persistence/db-connection.js", () => ({
@@ -62,8 +67,17 @@ mock.module("../../../../../persistence/db-connection.js", () => ({
62
67
  : realDb.getMemorySqlite(),
63
68
  }));
64
69
 
65
- const { buildPoolRecord, readPoolForMessageIds, readPoolForTurn, writePool } =
66
- await import("../pool-log-store.js");
70
+ const {
71
+ buildPoolInput,
72
+ buildPoolRecord,
73
+ hashPoolText,
74
+ readPoolForMessageIds,
75
+ readPoolForTurn,
76
+ readPoolInputForTurn,
77
+ readPoolText,
78
+ writePool,
79
+ writePoolInput,
80
+ } = await import("../pool-log-store.js");
67
81
 
68
82
  beforeEach(() => {
69
83
  storeMockActive = true;
@@ -115,6 +129,32 @@ function orchestrated(): OrchestrateResult {
115
129
  };
116
130
  }
117
131
 
132
+ /** {@link orchestrated} with the pool the selector was given: a card per
133
+ * stable-prefix slug and one finder candidate per finder line, in the
134
+ * record's order, plus the gate and keep-all facts. */
135
+ function orchestratedWithPool(): OrchestrateResult {
136
+ const result = orchestrated();
137
+ return {
138
+ ...result,
139
+ pool: {
140
+ stable: [
141
+ { slug: "core/page", card: "core card" },
142
+ { slug: "hot/page", card: "hot card" },
143
+ { slug: "fresh/page", card: "fresh card" },
144
+ { slug: "skills/example", card: "skill card" },
145
+ ],
146
+ finder: result.lanes.finder.map((line) => ({
147
+ slug: line.slug,
148
+ lane: line.lane,
149
+ section: line.section,
150
+ descriptor: line.descriptor || `${line.slug} lead`,
151
+ })),
152
+ },
153
+ keptAll: false,
154
+ gateReason: "dense_pass",
155
+ };
156
+ }
157
+
118
158
  /**
119
159
  * A turn the injection gate hard-skipped: the selector was never consulted,
120
160
  * so there are no selections, but the result's lanes still carry the stable
@@ -447,3 +487,142 @@ describe("ensureMemoryV3PoolsSchema", () => {
447
487
  expect(column?.dflt_value).toBe("1");
448
488
  });
449
489
  });
490
+
491
+ describe("pool input capture", () => {
492
+ const turn: MemoryRoutingTurn = {
493
+ conversationId: "conv-1",
494
+ turnNumber: 4,
495
+ currentMessage: "what did we decide about the garden?",
496
+ recentContext: "user: hi\nassistant: hello",
497
+ situationalContext: "Today is 2026-01-01.",
498
+ previousAssistantMessage: "hello",
499
+ };
500
+
501
+ test("buildPoolInput captures each candidate's rendered text in pool order, aligned with the pool record", () => {
502
+ const result = orchestratedWithPool();
503
+ const { input, texts } = buildPoolInput(result, turn, "select wisely");
504
+ const record = buildPoolRecord(result);
505
+
506
+ expect(input.candidate_text_hashes).toHaveLength(record.candidates.length);
507
+ const rendered = [
508
+ ...result.pool!.stable.map((candidate) => candidate.card),
509
+ ...result.pool!.finder.map((candidate) => renderFinderLine(candidate)),
510
+ ];
511
+ expect(input.candidate_text_hashes.map((hash) => texts.get(hash))).toEqual(
512
+ rendered,
513
+ );
514
+ expect(input.candidate_text_hashes[0]).toBe(hashPoolText("core card"));
515
+ expect(input).toMatchObject({
516
+ situational_context: "Today is 2026-01-01.",
517
+ recent_context: "user: hi\nassistant: hello",
518
+ current_message: "what did we decide about the garden?",
519
+ previous_assistant_message: "hello",
520
+ selector_prompt_hash: hashPoolText("select wisely"),
521
+ kept_all: false,
522
+ gate_reason: "dense_pass",
523
+ });
524
+ });
525
+
526
+ test("a turn whose selector never judged a pool captures the context and no candidate texts", () => {
527
+ const { input, texts } = buildPoolInput(
528
+ hardSkipped(),
529
+ {
530
+ ...turn,
531
+ situationalContext: undefined,
532
+ previousAssistantMessage: undefined,
533
+ },
534
+ "select wisely",
535
+ );
536
+
537
+ expect(input.candidate_text_hashes).toEqual([]);
538
+ expect(texts.size).toBe(0);
539
+ expect(input).toMatchObject({
540
+ situational_context: null,
541
+ previous_assistant_message: null,
542
+ selector_prompt_hash: null,
543
+ gate_reason: null,
544
+ });
545
+ });
546
+
547
+ test("a result without the selector's pool captures no candidate texts rather than misaligned ones", () => {
548
+ const { input, texts } = buildPoolInput(
549
+ orchestrated(),
550
+ turn,
551
+ "select wisely",
552
+ );
553
+
554
+ expect(input.candidate_text_hashes).toEqual([]);
555
+ expect(texts.size).toBe(0);
556
+ });
557
+
558
+ test("writePoolInput and readPoolInputForTurn round-trip; a re-observed turn replaces its row; texts are stored once", () => {
559
+ const capture = buildPoolInput(
560
+ orchestratedWithPool(),
561
+ turn,
562
+ "select wisely",
563
+ );
564
+ writePoolInput(memorySqlite, "conv-1", 4, capture);
565
+
566
+ expect(readPoolInputForTurn("conv-1", 4)).toEqual(capture.input);
567
+ expect(readPoolText(capture.input.candidate_text_hashes[0]!)).toBe(
568
+ "core card",
569
+ );
570
+
571
+ // The next turn pools the same candidates: its input row is its own, the
572
+ // texts are shared by hash.
573
+ writePoolInput(
574
+ memorySqlite,
575
+ "conv-1",
576
+ 5,
577
+ buildPoolInput(
578
+ orchestratedWithPool(),
579
+ { ...turn, turnNumber: 5, currentMessage: "and the fence?" },
580
+ "select wisely",
581
+ ),
582
+ );
583
+ const textCount = () =>
584
+ (
585
+ memorySqlite
586
+ .query(`SELECT COUNT(*) AS n FROM memory_v3_pool_texts`)
587
+ .get() as { n: number }
588
+ ).n;
589
+ expect(textCount()).toBe(new Set(capture.input.candidate_text_hashes).size);
590
+ expect(readPoolInputForTurn("conv-1", 5)?.current_message).toBe(
591
+ "and the fence?",
592
+ );
593
+
594
+ writePoolInput(
595
+ memorySqlite,
596
+ "conv-1",
597
+ 4,
598
+ buildPoolInput(
599
+ orchestratedWithPool(),
600
+ { ...turn, currentMessage: "again" },
601
+ "select wisely",
602
+ ),
603
+ );
604
+ expect(readPoolInputForTurn("conv-1", 4)?.current_message).toBe("again");
605
+ expect(
606
+ (
607
+ memorySqlite
608
+ .query(`SELECT COUNT(*) AS n FROM memory_v3_pool_inputs`)
609
+ .get() as { n: number }
610
+ ).n,
611
+ ).toBe(2);
612
+ });
613
+
614
+ test("an absent turn or text reads null, and reads degrade to null when the memory connection is unavailable", () => {
615
+ expect(readPoolInputForTurn("conv-1", 9)).toBeNull();
616
+ expect(readPoolText("missing")).toBeNull();
617
+
618
+ writePoolInput(
619
+ memorySqlite,
620
+ "conv-1",
621
+ 4,
622
+ buildPoolInput(orchestratedWithPool(), turn, "select wisely"),
623
+ );
624
+ memoryDbAvailable = false;
625
+ expect(readPoolInputForTurn("conv-1", 4)).toBeNull();
626
+ expect(readPoolText(hashPoolText("core card"))).toBeNull();
627
+ });
628
+ });
@@ -43,6 +43,7 @@ import type { HotSetEntry, HotSetOptions } from "../hot-set.js";
43
43
  import type { OrchestrateResult } from "../orchestrate.js";
44
44
  import {
45
45
  ensureMemoryV3InjectedSectionsSchema,
46
+ ensureMemoryV3PoolInputsSchema,
46
47
  ensureMemoryV3PoolsSchema,
47
48
  } from "../plugin-schema.js";
48
49
  import { MEMORY_V3_FULL_PROFILE_MIN_PAGES } from "../tuning-profile.js";
@@ -123,6 +124,9 @@ let gateEnabledCfg = true;
123
124
  // Mutable `memory.v3.rareTerm.enabled` switch carried by the mocked config
124
125
  // (default on, mirroring the schema default).
125
126
  let rareTermEnabledCfg = true;
127
+ // Mutable `memory.v3.poolLog.captureInput` opt-in carried by the mocked
128
+ // config (default off, mirroring the schema default).
129
+ let captureInputCfg = false;
126
130
  let messages: Array<{
127
131
  role: string;
128
132
  content: string;
@@ -240,6 +244,7 @@ function makeDb() {
240
244
  ensureMemoryV3InjectedSectionsSchema(memorySqlite);
241
245
  // `observeTurn` records each turn's candidate pool next to its selections.
242
246
  ensureMemoryV3PoolsSchema(memorySqlite);
247
+ ensureMemoryV3PoolInputsSchema(memorySqlite);
243
248
  return db;
244
249
  }
245
250
 
@@ -298,6 +303,7 @@ function seedMemoryConfig(): void {
298
303
  // Gate tuning (schema defaults) with the mutable `enabled` kill-switch,
299
304
  // threaded through to orchestrate as-is.
300
305
  gate: { ...GATE_DEFAULTS, enabled: gateEnabledCfg },
306
+ poolLog: { captureInput: captureInputCfg },
301
307
  },
302
308
  qdrant: { vectorSize: 8, onDisk: false },
303
309
  });
@@ -586,8 +592,9 @@ const {
586
592
  } = await import("../shadow-plugin.js");
587
593
  const { memoryV3Injector, resetMemoryV3InjectorStateForTests } =
588
594
  await import("../injector.js");
589
- const { MemoryV3RetrievalUnavailableError } = await import("../pool-select.js");
590
- const { buildPoolRecord, readPoolForMessageIds } =
595
+ const { MemoryV3RetrievalUnavailableError, renderFinderLine } =
596
+ await import("../pool-select.js");
597
+ const { buildPoolRecord, readPoolForMessageIds, readPoolText } =
591
598
  await import("../pool-log-store.js");
592
599
 
593
600
  /** Seed the real config from the current mutable knobs, then run the real
@@ -626,6 +633,23 @@ function readPools() {
626
633
  }>;
627
634
  }
628
635
 
636
+ function readPoolInputs() {
637
+ return memorySqlite
638
+ .query(
639
+ `SELECT conversation_id, turn, current_message, kept_all, gate_reason,
640
+ candidate_text_hashes_json
641
+ FROM memory_v3_pool_inputs ORDER BY turn`,
642
+ )
643
+ .all() as Array<{
644
+ conversation_id: string;
645
+ turn: number;
646
+ current_message: string;
647
+ kept_all: number;
648
+ gate_reason: string | null;
649
+ candidate_text_hashes_json: string;
650
+ }>;
651
+ }
652
+
629
653
  /** The `chosen` verdict per pooled slug, read from the single pool row. */
630
654
  function chosenBySlug(): Record<string, boolean> {
631
655
  const pools = readPools();
@@ -668,6 +692,7 @@ beforeEach(() => {
668
692
  selectorEnabledCfg = false;
669
693
  gateEnabledCfg = true;
670
694
  rareTermEnabledCfg = true;
695
+ captureInputCfg = false;
671
696
  messages = [
672
697
  {
673
698
  role: "user",
@@ -975,6 +1000,61 @@ describe("memory-v3 engine", () => {
975
1000
  expect(readPools().map((pool) => pool.message_id)).toEqual(["m-skipped"]);
976
1001
  });
977
1002
 
1003
+ test("the selector's input is not captured unless memory.v3.poolLog.captureInput is on", async () => {
1004
+ await observeTurn("conv-1", 2);
1005
+
1006
+ expect(readPools()).toHaveLength(1);
1007
+ expect(readPoolInputs()).toHaveLength(0);
1008
+ });
1009
+
1010
+ test("with memory.v3.poolLog.captureInput on, the turn log writes the selector's input and every pooled candidate's text", async () => {
1011
+ captureInputCfg = true;
1012
+ const finder = [
1013
+ { slug: "page-1", descriptor: "the first page", lane: "needle" as const },
1014
+ { slug: "page-2", descriptor: "", lane: "edge" as const },
1015
+ ];
1016
+ orchestrateSpy.mockImplementationOnce(async () => ({
1017
+ selections: [
1018
+ { slug: "page-core", sections: [] },
1019
+ { slug: "page-1", sections: [] },
1020
+ ],
1021
+ lanes: { core: ["page-core"], hot: [], fresh: [], always: [], finder },
1022
+ selectorRan: true,
1023
+ keptAll: false,
1024
+ gateReason: "dense_pass",
1025
+ pool: {
1026
+ stable: [{ slug: "page-core", card: "core card" }],
1027
+ finder,
1028
+ },
1029
+ }));
1030
+
1031
+ await observeTurn("conv-1", 2);
1032
+
1033
+ const pools = readPools();
1034
+ expect(pools).toHaveLength(1);
1035
+ expect(pools[0]!.pool_size).toBe(3);
1036
+ const inputs = readPoolInputs();
1037
+ expect(inputs).toHaveLength(1);
1038
+ expect(inputs[0]).toMatchObject({
1039
+ conversation_id: "conv-1",
1040
+ turn: 2,
1041
+ current_message: "hello world",
1042
+ kept_all: 0,
1043
+ gate_reason: "dense_pass",
1044
+ });
1045
+
1046
+ // One hash per pooled candidate in pool order: the card as pre-rendered,
1047
+ // then each finder line exactly as the selector renders it.
1048
+ const hashes = JSON.parse(
1049
+ inputs[0]!.candidate_text_hashes_json,
1050
+ ) as string[];
1051
+ expect(hashes.map((hash) => readPoolText(hash))).toEqual([
1052
+ "core card",
1053
+ renderFinderLine(finder[0]!),
1054
+ renderFinderLine(finder[1]!),
1055
+ ]);
1056
+ });
1057
+
978
1058
  test("a turn observed again replaces its selection rows in step with its pool", async () => {
979
1059
  orchestrateSpy.mockImplementationOnce(async () =>
980
1060
  poolOf(["page-1", "page-2"]),
@@ -1163,6 +1243,27 @@ describe("memory-v3 engine", () => {
1163
1243
  expect(sourceOf([])).toBe("needle");
1164
1244
  });
1165
1245
 
1246
+ test("a turn whose selector could not run attributes no rows, so unjudged pages never feed frecency or learned edges", () => {
1247
+ const rows = attributeSelections({
1248
+ selections: [
1249
+ { slug: "page-core", sections: [] },
1250
+ { slug: "page-hot", sections: [] },
1251
+ ],
1252
+ lanes: {
1253
+ core: ["page-core"],
1254
+ hot: ["page-hot"],
1255
+ fresh: [],
1256
+ always: [],
1257
+ finder: [{ slug: "page-two", descriptor: "", lane: "needle" }],
1258
+ },
1259
+ selectorRan: false,
1260
+ selectorFailure: new MemoryV3RetrievalUnavailableError(
1261
+ "selector unavailable",
1262
+ ),
1263
+ });
1264
+ expect(rows).toEqual([]);
1265
+ });
1266
+
1166
1267
  test("a selection of a core page a finder also hit attributes to core (pool position wins)", () => {
1167
1268
  const rows = attributeSelections({
1168
1269
  selections: [{ slug: "page-core", sections: [] }],
@@ -1,5 +1,6 @@
1
+ import { safeStringSlice } from "@vellumai/plugin-api";
2
+
1
3
  import { FRONTMATTER_REGEX, parseFrontmatterFields } from "../frontmatter.js";
2
- import { safeStringSlice } from "../host-utils.js";
3
4
  import { injectedConceptHeader } from "../substrate/injected-block-slugs.js";
4
5
  import { LINK_SEPARATOR, parseLinkEntry } from "../substrate/page-links.js";
5
6
  import type { Slug } from "./types.js";