@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
@@ -10,10 +10,13 @@
10
10
  import { describe, expect, test } from "bun:test";
11
11
 
12
12
  import {
13
+ bufferEntryText,
13
14
  formatBufferTimestamp,
14
15
  formatRememberEntry,
15
16
  isBufferEntryStart,
17
+ joinBufferEntries,
16
18
  matchBufferEntryStart,
19
+ splitBufferContent,
17
20
  splitBufferEntries,
18
21
  } from "../buffer-format.js";
19
22
 
@@ -202,3 +205,43 @@ describe("splitBufferEntries", () => {
202
205
  expect(splitBufferEntries([])).toEqual([]);
203
206
  });
204
207
  });
208
+
209
+ describe("splitBufferContent / joinBufferEntries", () => {
210
+ const content =
211
+ formatRememberEntry("Alice prefers dark mode", new Date(2026, 0, 1, 9, 0)) +
212
+ formatRememberEntry(
213
+ "Bob's plan:\n- [ ] step one\n\nend",
214
+ new Date(2026, 0, 1, 9, 1),
215
+ );
216
+
217
+ test("round-trips newline-terminated content byte for byte", () => {
218
+ expect(joinBufferEntries(splitBufferContent(content))).toBe(content);
219
+ });
220
+
221
+ test("an entry's text is the same whether or not it is last in the file", () => {
222
+ const [first] = splitBufferContent(content);
223
+ const [alone] = splitBufferContent(
224
+ formatRememberEntry(
225
+ "Alice prefers dark mode",
226
+ new Date(2026, 0, 1, 9, 0),
227
+ ),
228
+ );
229
+ expect(bufferEntryText(alone!)).toBe(bufferEntryText(first!));
230
+ expect(bufferEntryText(alone!)).toBe(
231
+ "- [Jan 1, 9:00 AM] Alice prefers dark mode",
232
+ );
233
+ });
234
+
235
+ test("empty content has no entries and joins back to empty", () => {
236
+ expect(splitBufferContent("")).toEqual([]);
237
+ expect(joinBufferEntries([])).toBe("");
238
+ });
239
+
240
+ test("content without a terminating newline keeps its last line intact", () => {
241
+ const entries = splitBufferContent("- [Jan 1, 9:00 AM] partial appe");
242
+ expect(entries).toHaveLength(1);
243
+ expect(bufferEntryText(entries[0]!)).toBe(
244
+ "- [Jan 1, 9:00 AM] partial appe",
245
+ );
246
+ });
247
+ });
@@ -60,6 +60,7 @@ describe("conversation memory purge", () => {
60
60
  "memory_v2_activation_logs",
61
61
  "memory_v3_ever_injected",
62
62
  "memory_v3_injected_sections",
63
+ "memory_v3_pool_inputs",
63
64
  "memory_v3_pools",
64
65
  "memory_v3_selections",
65
66
  ].sort(),
@@ -68,6 +68,8 @@ describe("memory database connection", () => {
68
68
  "memory_recall_logs",
69
69
  "memory_v3_selections",
70
70
  "memory_v3_pools",
71
+ "memory_v3_pool_inputs",
72
+ "memory_v3_pool_texts",
71
73
  "activation_sessions",
72
74
  "activation_state",
73
75
  "conversation_graph_memory_state",
@@ -0,0 +1,17 @@
1
+ // Child process for the buffer-file cross-process test: appends `count`
2
+ // uniquely tagged entries to `bufferPath` as fast as it can, through the same
3
+ // `appendFileSync` call the production appender uses. Hand-formats the entry
4
+ // line so the fixture imports nothing from `src/`.
5
+ //
6
+ // usage: bun buffer-appender.ts <bufferPath> <count> <tag>
7
+
8
+ import { appendFileSync } from "node:fs";
9
+
10
+ const [bufferPath, countArg, tag] = process.argv.slice(2);
11
+ if (!bufferPath || !countArg || !tag) {
12
+ throw new Error("usage: buffer-appender <bufferPath> <count> <tag>");
13
+ }
14
+ const count = Number.parseInt(countArg, 10);
15
+ for (let i = 0; i < count; i++) {
16
+ appendFileSync(bufferPath, `- [Apr 27, 9:00 AM] ${tag}-${i}\n`, "utf-8");
17
+ }
@@ -117,6 +117,21 @@ let loadedConversations: Record<string, { processing: boolean }> = {};
117
117
  // the stale-flag override's age check. Absent ids read as null (stampless).
118
118
  let processingStartedAtById: Record<string, number | null> = {};
119
119
 
120
+ // The source conversation's recorded wire surface (what its last live turn
121
+ // sent to the provider, and whether that turn's prompt rendered the
122
+ // delegation section). `null` = no live turn has recorded one yet.
123
+ let mockSourceToolSurface: {
124
+ tools: Array<Record<string, unknown>>;
125
+ delegateIndependentTasks: boolean | null;
126
+ } | null = null;
127
+ let toolSurfaceReads: string[] = [];
128
+ mock.module("../../../../persistence/conversation-tool-surface.js", () => ({
129
+ getConversationToolSurface: (conversationId: string) => {
130
+ toolSurfaceReads.push(conversationId);
131
+ return mockSourceToolSurface;
132
+ },
133
+ }));
134
+
120
135
  const watchdogEvents: Array<{
121
136
  checkName: string;
122
137
  value?: number | null;
@@ -509,6 +524,8 @@ describe("memoryRetrospectiveJob", () => {
509
524
  resolveUserSlugCalls = [];
510
525
  mockV3TierActive = true;
511
526
  mockSkillImprovementActive = false;
527
+ mockSourceToolSurface = null;
528
+ toolSurfaceReads = [];
512
529
  });
513
530
 
514
531
  test("first-run happy path: no state row, no prior retrospective, both pointer fields set on success", async () => {
@@ -1842,6 +1859,63 @@ describe("memoryRetrospectiveJob", () => {
1842
1859
  });
1843
1860
  });
1844
1861
 
1862
+ test("replays the source's recorded wire tool surface verbatim on the fork wake", async () => {
1863
+ mockSourceToolSurface = {
1864
+ tools: [
1865
+ { name: "bash", description: "Run", input_schema: {} },
1866
+ { name: "remember", description: "Save", input_schema: {} },
1867
+ { name: "bell_jingle", description: "Ring", input_schema: {} },
1868
+ { type: "web_search_20250305", name: "web_search", max_uses: 5 },
1869
+ ],
1870
+ delegateIndependentTasks: true,
1871
+ };
1872
+
1873
+ await memoryRetrospectiveJob(makeJob(), stubConfig);
1874
+
1875
+ expect(toolSurfaceReads).toEqual(["src-conv-1"]);
1876
+ expect(wakeCalls).toHaveLength(1);
1877
+ // The exact recorded array rides to the wake; the fork sends it in place
1878
+ // of the surface it would resolve for itself.
1879
+ expect(wakeCalls[0]!.opts.wireToolDefinitions).toEqual(
1880
+ mockSourceToolSurface.tools,
1881
+ );
1882
+ // The delegation-section state the source's prompt rendered rides with
1883
+ // it, so the fork's system prompt matches too.
1884
+ expect(wakeCalls[0]!.opts.delegateIndependentTasks).toBe(true);
1885
+ // The execution-side pin still rides alongside it.
1886
+ expect(wakeCalls[0]!.opts.toolGateMode).toBe("execution");
1887
+ expect(wakeCalls[0]!.opts.toolContextPin).toBeDefined();
1888
+ });
1889
+
1890
+ test("a surface recorded without the delegation state leaves the fork to derive the section", async () => {
1891
+ // A row written before the state was recorded: the array still replays,
1892
+ // and the prompt gate falls back to the wake's own answer.
1893
+ mockSourceToolSurface = {
1894
+ tools: [{ name: "remember", description: "Save", input_schema: {} }],
1895
+ delegateIndependentTasks: null,
1896
+ };
1897
+
1898
+ await memoryRetrospectiveJob(makeJob(), stubConfig);
1899
+
1900
+ expect(wakeCalls).toHaveLength(1);
1901
+ expect(wakeCalls[0]!.opts.wireToolDefinitions).toEqual(
1902
+ mockSourceToolSurface.tools,
1903
+ );
1904
+ expect("delegateIndependentTasks" in wakeCalls[0]!.opts).toBe(false);
1905
+ });
1906
+
1907
+ test("no recorded source surface → no wireToolDefinitions; the pin alone shapes the wire", async () => {
1908
+ mockSourceToolSurface = null;
1909
+
1910
+ await memoryRetrospectiveJob(makeJob(), stubConfig);
1911
+
1912
+ expect(toolSurfaceReads).toEqual(["src-conv-1"]);
1913
+ expect(wakeCalls).toHaveLength(1);
1914
+ expect("wireToolDefinitions" in wakeCalls[0]!.opts).toBe(false);
1915
+ expect("delegateIndependentTasks" in wakeCalls[0]!.opts).toBe(false);
1916
+ expect(wakeCalls[0]!.opts.toolContextPin).toBeDefined();
1917
+ });
1918
+
1845
1919
  test("execution mode is unconditional → toolContextPin rides even without a resolved profile", async () => {
1846
1920
  await memoryRetrospectiveJob(
1847
1921
  makeJob(),
@@ -0,0 +1,161 @@
1
+ /**
2
+ * Tests for `memory-run-evidence.ts`: the readers of what a background
3
+ * memory run durably produced, shared by the retrospective's cursor advance
4
+ * and consolidation's buffer consumption.
5
+ */
6
+
7
+ import { describe, expect, test } from "bun:test";
8
+
9
+ import {
10
+ collectSuccessfulToolResultIds,
11
+ countDurableToolUses,
12
+ endsWithTextReply,
13
+ extractRememberContents,
14
+ hasCommittedTextReply,
15
+ type MessageLike,
16
+ } from "../memory-run-evidence.js";
17
+
18
+ const DURABLE = new Set(["file_write", "remember"]);
19
+
20
+ /**
21
+ * Persisted message rows carry their blocks as a JSON string; the readers
22
+ * parse it. Rows are built that way here so the test exercises the same
23
+ * shape the jobs read.
24
+ */
25
+ function row(role: string, blocks: unknown[]): MessageLike {
26
+ return { role, content: JSON.stringify(blocks) };
27
+ }
28
+ const use = (id: string, name: string, input: unknown = {}) => ({
29
+ type: "tool_use",
30
+ id,
31
+ name,
32
+ input,
33
+ });
34
+ const result = (id: string, isError = false) => ({
35
+ type: "tool_result",
36
+ tool_use_id: id,
37
+ is_error: isError,
38
+ content: "…",
39
+ });
40
+ const text = (value: string) => ({ type: "text", text: value });
41
+
42
+ describe("collectSuccessfulToolResultIds", () => {
43
+ test("collects non-error tool_result ids from user rows only, tolerating malformed rows", () => {
44
+ const ids = collectSuccessfulToolResultIds([
45
+ row("user", [result("ok"), result("bad", true)]),
46
+ row("assistant", [result("not-a-user-row")]),
47
+ { role: "user", content: "{not json" },
48
+ row("user", [result("second")]),
49
+ ]);
50
+ expect([...ids].sort()).toEqual(["ok", "second"]);
51
+ });
52
+ });
53
+
54
+ describe("countDurableToolUses", () => {
55
+ const messages = [
56
+ row("assistant", [
57
+ use("a", "file_write"),
58
+ use("b", "file_write"),
59
+ use("c", "file_read"),
60
+ ]),
61
+ row("user", [result("a"), result("b", true), result("c")]),
62
+ ];
63
+
64
+ test("counts only the named tools, and with a success set only verified executions", () => {
65
+ expect(countDurableToolUses(messages, DURABLE, null)).toBe(2);
66
+ expect(
67
+ countDurableToolUses(
68
+ messages,
69
+ DURABLE,
70
+ collectSuccessfulToolResultIds(messages),
71
+ ),
72
+ ).toBe(1);
73
+ expect(countDurableToolUses(messages, new Set(["file_read"]), null)).toBe(
74
+ 1,
75
+ );
76
+ });
77
+ });
78
+
79
+ describe("hasCommittedTextReply", () => {
80
+ test("true only when the LAST assistant row carries non-blank text", () => {
81
+ expect(
82
+ hasCommittedTextReply([
83
+ row("assistant", [text("narrating")]),
84
+ row("assistant", [text(" ")]),
85
+ ]),
86
+ ).toBe(false);
87
+ expect(
88
+ hasCommittedTextReply([
89
+ row("assistant", [use("a", "file_write")]),
90
+ row("user", [result("a")]),
91
+ row("assistant", [text("Done.")]),
92
+ ]),
93
+ ).toBe(true);
94
+ expect(hasCommittedTextReply([{ role: "assistant", content: "{" }])).toBe(
95
+ false,
96
+ );
97
+ });
98
+ });
99
+
100
+ describe("endsWithTextReply", () => {
101
+ test("true only when the final row is an assistant reply with text and no tool call", () => {
102
+ expect(
103
+ endsWithTextReply([
104
+ row("assistant", [use("a", "file_write")]),
105
+ row("user", [result("a")]),
106
+ row("assistant", [text("Filed it.")]),
107
+ ]),
108
+ ).toBe(true);
109
+ // Narration on the row that also calls a tool, then a tool result: the
110
+ // run stopped mid-loop, whatever the narration said.
111
+ expect(
112
+ endsWithTextReply([
113
+ row("assistant", [
114
+ text("Fixing that page now."),
115
+ use("a", "file_write"),
116
+ ]),
117
+ row("user", [result("a")]),
118
+ ]),
119
+ ).toBe(false);
120
+ expect(
121
+ endsWithTextReply([
122
+ row("assistant", [text("Done."), use("b", "file_read")]),
123
+ ]),
124
+ ).toBe(false);
125
+ expect(endsWithTextReply([row("assistant", [text(" ")])])).toBe(false);
126
+ expect(endsWithTextReply([{ role: "assistant", content: "{" }])).toBe(
127
+ false,
128
+ );
129
+ expect(endsWithTextReply([])).toBe(false);
130
+ });
131
+ });
132
+
133
+ describe("extractRememberContents", () => {
134
+ const messages = [
135
+ row("assistant", [
136
+ use("a", "remember", { content: " one " }),
137
+ use("b", "remember", { content: ["two", "", "three"] }),
138
+ use("c", "remember", { content: "failed" }),
139
+ use("d", "file_write", { content: "not a remember" }),
140
+ ]),
141
+ row("user", [result("a"), result("b"), result("c", true)]),
142
+ ];
143
+
144
+ test("flattens batched facts, trims, and drops blanks", () => {
145
+ expect(extractRememberContents(messages)).toEqual([
146
+ "one",
147
+ "two",
148
+ "three",
149
+ "failed",
150
+ ]);
151
+ });
152
+
153
+ test("with a success set, a failed remember's facts are excluded", () => {
154
+ expect(
155
+ extractRememberContents(
156
+ messages,
157
+ collectSuccessfulToolResultIds(messages),
158
+ ),
159
+ ).toEqual(["one", "two", "three"]);
160
+ });
161
+ });
@@ -52,6 +52,16 @@ export function seedRelocatedMemoryRow(
52
52
  )
53
53
  .run(conversationId, now);
54
54
  return;
55
+ case "memory_v3_pool_inputs":
56
+ raw
57
+ .query(
58
+ `INSERT INTO memory_v3_pool_inputs
59
+ (conversation_id, turn, created_at, recent_context, current_message,
60
+ candidate_text_hashes_json)
61
+ VALUES (?, 1, ?, '', '', '[]')`,
62
+ )
63
+ .run(conversationId, now);
64
+ return;
55
65
  case "activation_sessions":
56
66
  raw
57
67
  .query(