@vellumai/assistant 0.5.0 → 0.5.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 (347) hide show
  1. package/ARCHITECTURE.md +54 -54
  2. package/docs/architecture/integrations.md +62 -67
  3. package/docs/credential-execution-service.md +3 -3
  4. package/package.json +1 -1
  5. package/src/__tests__/agent-loop.test.ts +111 -0
  6. package/src/__tests__/always-loaded-tools-guard.test.ts +3 -4
  7. package/src/__tests__/app-builder-tool-scripts.test.ts +13 -151
  8. package/src/__tests__/app-dir-path-guard.test.ts +78 -0
  9. package/src/__tests__/app-executors.test.ts +1 -291
  10. package/src/__tests__/app-git-history.test.ts +4 -4
  11. package/src/__tests__/app-routes-csp.test.ts +1 -0
  12. package/src/__tests__/app-store-dir-names.test.ts +426 -0
  13. package/src/__tests__/assistant-feature-flags-integration.test.ts +7 -9
  14. package/src/__tests__/attachments-store.test.ts +169 -21
  15. package/src/__tests__/attachments.test.ts +115 -1
  16. package/src/__tests__/btw-routes.test.ts +1 -0
  17. package/src/__tests__/canonical-guardian-store.test.ts +38 -0
  18. package/src/__tests__/channel-reply-delivery.test.ts +55 -0
  19. package/src/__tests__/checker.test.ts +54 -0
  20. package/src/__tests__/claude-code-skill-regression.test.ts +2 -0
  21. package/src/__tests__/claude-code-tool-profiles.test.ts +2 -0
  22. package/src/__tests__/compaction.benchmark.test.ts +2 -1
  23. package/src/__tests__/config-schema-cmd.test.ts +68 -21
  24. package/src/__tests__/config-schema.test.ts +1 -1
  25. package/src/__tests__/conversation-agent-loop-overflow.test.ts +149 -5
  26. package/src/__tests__/conversation-agent-loop.test.ts +290 -2
  27. package/src/__tests__/conversation-attachments.test.ts +17 -19
  28. package/src/__tests__/conversation-disk-view-integration.test.ts +277 -0
  29. package/src/__tests__/conversation-disk-view.test.ts +810 -0
  30. package/src/__tests__/conversation-error.test.ts +1 -1
  31. package/src/__tests__/conversation-fork-crud.test.ts +551 -0
  32. package/src/__tests__/conversation-fork-route.test.ts +386 -0
  33. package/src/__tests__/conversation-history-web-search.test.ts +1 -1
  34. package/src/__tests__/conversation-key-store-disk-view.test.ts +130 -0
  35. package/src/__tests__/conversation-media-retry.test.ts +8 -2
  36. package/src/__tests__/conversation-queue.test.ts +36 -1
  37. package/src/__tests__/conversation-routes-disk-view.test.ts +439 -0
  38. package/src/__tests__/conversation-routes-guardian-reply.test.ts +2 -2
  39. package/src/__tests__/conversation-routes-slash-commands.test.ts +2 -7
  40. package/src/__tests__/conversation-runtime-assembly.test.ts +17 -2
  41. package/src/__tests__/conversation-skill-tools.test.ts +4 -9
  42. package/src/__tests__/conversation-slash-commands.test.ts +149 -0
  43. package/src/__tests__/conversation-store.test.ts +24 -21
  44. package/src/__tests__/conversation-surfaces-state-update.test.ts +246 -0
  45. package/src/__tests__/conversation-surfaces-task-progress.test.ts +1 -0
  46. package/src/__tests__/conversation-title-service.test.ts +137 -0
  47. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +25 -315
  48. package/src/__tests__/conversation-tool-setup-memory-scope.test.ts +1 -0
  49. package/src/__tests__/conversation-tool-setup-side-effect-flag.test.ts +1 -0
  50. package/src/__tests__/conversation-workspace-cache-state.test.ts +44 -2
  51. package/src/__tests__/conversation-workspace-injection.test.ts +11 -0
  52. package/src/__tests__/credential-execution-feature-gates.test.ts +3 -3
  53. package/src/__tests__/credential-security-invariants.test.ts +3 -0
  54. package/src/__tests__/credential-vault-unit.test.ts +5 -10
  55. package/src/__tests__/cu-unified-flow.test.ts +1 -0
  56. package/src/__tests__/db-conversation-fork-lineage-migration.test.ts +241 -0
  57. package/src/__tests__/db-llm-request-log-provider-migration.test.ts +214 -0
  58. package/src/__tests__/diagnostics-export.test.ts +70 -1
  59. package/src/__tests__/filesystem-tools.test.ts +4 -2
  60. package/src/__tests__/first-greeting.test.ts +80 -0
  61. package/src/__tests__/gateway-only-guard.test.ts +1 -0
  62. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +3 -7
  63. package/src/__tests__/history-repair.test.ts +103 -10
  64. package/src/__tests__/http-conversation-lineage.test.ts +251 -0
  65. package/src/__tests__/image-source-path-reinject.test.ts +136 -0
  66. package/src/__tests__/llm-context-normalization.test.ts +1116 -0
  67. package/src/__tests__/llm-context-route-provider.test.ts +217 -0
  68. package/src/__tests__/llm-request-log-turn-query.test.ts +270 -0
  69. package/src/__tests__/media-generate-image.test.ts +47 -94
  70. package/src/__tests__/memory-lifecycle-e2e.test.ts +3 -1
  71. package/src/__tests__/memory-recall-quality.test.ts +5 -5
  72. package/src/__tests__/migration-cross-version-compatibility.test.ts +4 -1
  73. package/src/__tests__/migration-export-http.test.ts +3 -1
  74. package/src/__tests__/migration-import-commit-http.test.ts +18 -4
  75. package/src/__tests__/migration-import-preflight-http.test.ts +1 -3
  76. package/src/__tests__/mime-builder.test.ts +3 -2
  77. package/src/__tests__/non-member-access-request.test.ts +12 -1
  78. package/src/__tests__/notification-decision-identity.test.ts +52 -0
  79. package/src/__tests__/oauth-apps-routes.test.ts +103 -0
  80. package/src/__tests__/oauth-store.test.ts +115 -0
  81. package/src/__tests__/provider-error-scenarios.test.ts +1 -3
  82. package/src/__tests__/provider-failover-actual-provider.test.ts +66 -0
  83. package/src/__tests__/recording-handler.test.ts +17 -0
  84. package/src/__tests__/registry.test.ts +3 -8
  85. package/src/__tests__/relay-server.test.ts +1 -1
  86. package/src/__tests__/runtime-attachment-metadata.test.ts +7 -3
  87. package/src/__tests__/schema-transforms.test.ts +165 -5
  88. package/src/__tests__/server-history-render.test.ts +2 -2
  89. package/src/__tests__/skill-feature-flags-integration.test.ts +18 -17
  90. package/src/__tests__/skill-feature-flags.test.ts +13 -13
  91. package/src/__tests__/skill-load-feature-flag.test.ts +4 -4
  92. package/src/__tests__/slack-app-setup-skill-regression.test.ts +3 -1
  93. package/src/__tests__/slack-inbound-verification.test.ts +2 -2
  94. package/src/__tests__/starter-task-flow.test.ts +1 -0
  95. package/src/__tests__/suggestion-routes.test.ts +443 -0
  96. package/src/__tests__/swarm-conversation-integration.test.ts +1 -0
  97. package/src/__tests__/swarm-recursion.test.ts +1 -0
  98. package/src/__tests__/swarm-tool.test.ts +1 -0
  99. package/src/__tests__/system-prompt.test.ts +8 -0
  100. package/src/__tests__/tool-execution-abort-cleanup.test.ts +1 -0
  101. package/src/__tests__/tool-preview-lifecycle.test.ts +32 -5
  102. package/src/__tests__/top-level-renderer.test.ts +22 -0
  103. package/src/__tests__/turn-boundary-resolution.test.ts +243 -0
  104. package/src/__tests__/web-fetch.test.ts +6 -2
  105. package/src/__tests__/workspace-migration-006-services-config.test.ts +335 -0
  106. package/src/__tests__/workspace-migration-007-web-search-provider-rename.test.ts +312 -0
  107. package/src/__tests__/workspace-migration-009-backfill-conversation-disk-view.test.ts +278 -0
  108. package/src/__tests__/workspace-migration-010-app-dir-rename.test.ts +275 -0
  109. package/src/__tests__/workspace-migration-012-rename-conversation-disk-view-dirs.test.ts +77 -0
  110. package/src/__tests__/workspace-migration-013-repair-conversation-disk-view.test.ts +401 -0
  111. package/src/__tests__/workspace-migration-backfill-installation-id.test.ts +328 -0
  112. package/src/__tests__/workspace-migration-seed-device-id.test.ts +6 -10
  113. package/src/agent/attachments.ts +27 -1
  114. package/src/agent/loop.ts +29 -1
  115. package/src/avatar/traits-png-sync.ts +80 -25
  116. package/src/bundler/app-bundler.ts +4 -4
  117. package/src/calls/call-domain.ts +1 -0
  118. package/src/calls/voice-session-bridge.ts +1 -0
  119. package/src/cli/commands/auth.ts +92 -0
  120. package/src/cli/commands/avatar.ts +7 -6
  121. package/src/cli/commands/config.ts +2 -0
  122. package/src/cli/commands/oauth/providers.ts +29 -0
  123. package/src/cli/program.ts +12 -0
  124. package/src/cli.ts +15 -48
  125. package/src/config/bundled-skills/app-builder/SKILL.md +103 -28
  126. package/src/config/bundled-skills/app-builder/TOOLS.json +5 -199
  127. package/src/config/bundled-skills/app-builder/tools/{app-query.ts → app-refresh.ts} +2 -2
  128. package/src/config/bundled-skills/contacts/tools/google-contacts.ts +2 -3
  129. package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +6 -9
  130. package/src/config/bundled-skills/gmail/tools/gmail-attachments.ts +4 -6
  131. package/src/config/bundled-skills/gmail/tools/gmail-draft.ts +2 -3
  132. package/src/config/bundled-skills/gmail/tools/gmail-filters.ts +2 -3
  133. package/src/config/bundled-skills/gmail/tools/gmail-follow-up.ts +2 -3
  134. package/src/config/bundled-skills/gmail/tools/gmail-forward.ts +2 -3
  135. package/src/config/bundled-skills/gmail/tools/gmail-label.ts +4 -6
  136. package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +2 -3
  137. package/src/config/bundled-skills/gmail/tools/gmail-send-draft.ts +2 -3
  138. package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +2 -3
  139. package/src/config/bundled-skills/gmail/tools/gmail-trash.ts +2 -3
  140. package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +2 -3
  141. package/src/config/bundled-skills/gmail/tools/gmail-vacation.ts +2 -3
  142. package/src/config/bundled-skills/google-calendar/tools/shared.ts +1 -1
  143. package/src/config/bundled-skills/image-studio/SKILL.md +2 -2
  144. package/src/config/bundled-skills/image-studio/TOOLS.json +2 -2
  145. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +45 -72
  146. package/src/config/bundled-skills/media-processing/tools/extract-keyframes.ts +2 -2
  147. package/src/config/bundled-skills/messaging/tools/shared.ts +1 -1
  148. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +19 -3
  149. package/src/config/bundled-skills/skill-management/TOOLS.json +2 -2
  150. package/src/config/bundled-skills/slack/tools/shared.ts +19 -4
  151. package/src/config/bundled-skills/slack/tools/slack-scan-digest.ts +2 -3
  152. package/src/config/bundled-skills/transcribe/SKILL.md +1 -1
  153. package/src/config/bundled-skills/transcribe/TOOLS.json +2 -6
  154. package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +19 -83
  155. package/src/config/bundled-tool-registry.ts +2 -14
  156. package/src/config/feature-flag-registry.json +16 -0
  157. package/src/config/loader.ts +64 -0
  158. package/src/config/raw-config-utils.ts +30 -0
  159. package/src/config/schema-utils.ts +28 -7
  160. package/src/config/schema.ts +8 -0
  161. package/src/config/schemas/elevenlabs.ts +18 -0
  162. package/src/config/schemas/memory-lifecycle.ts +4 -2
  163. package/src/config/schemas/memory-storage.ts +1 -1
  164. package/src/config/schemas/services.ts +8 -6
  165. package/src/contacts/contact-store.ts +13 -6
  166. package/src/contacts/contacts-write.ts +0 -1
  167. package/src/context/window-manager.ts +13 -2
  168. package/src/daemon/conversation-agent-loop-handlers.ts +46 -42
  169. package/src/daemon/conversation-agent-loop.ts +56 -19
  170. package/src/daemon/conversation-attachments.ts +18 -36
  171. package/src/daemon/conversation-error.ts +2 -1
  172. package/src/daemon/conversation-history.ts +18 -4
  173. package/src/daemon/conversation-lifecycle.ts +39 -15
  174. package/src/daemon/conversation-messaging.ts +70 -26
  175. package/src/daemon/conversation-process.ts +58 -34
  176. package/src/daemon/conversation-runtime-assembly.ts +21 -38
  177. package/src/daemon/conversation-slash.ts +121 -256
  178. package/src/daemon/conversation-surfaces.ts +143 -20
  179. package/src/daemon/conversation-tool-setup.ts +0 -6
  180. package/src/daemon/conversation-workspace.ts +21 -1
  181. package/src/daemon/conversation.ts +51 -29
  182. package/src/daemon/first-greeting.ts +35 -0
  183. package/src/daemon/handlers/config-embeddings.ts +148 -0
  184. package/src/daemon/handlers/config-model.ts +71 -26
  185. package/src/daemon/handlers/conversations.ts +0 -23
  186. package/src/daemon/handlers/recording.ts +26 -21
  187. package/src/daemon/history-repair.ts +28 -8
  188. package/src/daemon/host-cu-proxy.ts +2 -2
  189. package/src/daemon/lifecycle.ts +106 -64
  190. package/src/daemon/message-protocol.ts +3 -0
  191. package/src/daemon/message-types/conversations.ts +19 -0
  192. package/src/daemon/message-types/messages.ts +1 -0
  193. package/src/daemon/message-types/shared.ts +2 -0
  194. package/src/daemon/message-types/surfaces.ts +2 -0
  195. package/src/daemon/message-types/upgrades.ts +23 -0
  196. package/src/daemon/server.ts +83 -12
  197. package/src/daemon/shutdown-handlers.ts +8 -5
  198. package/src/daemon/startup-error.ts +9 -0
  199. package/src/daemon/tool-side-effects.ts +11 -28
  200. package/src/events/tool-permission-telemetry-listener.ts +1 -3
  201. package/src/instrument.ts +0 -4
  202. package/src/media/app-icon-generator.ts +2 -2
  203. package/src/memory/app-git-service.ts +28 -16
  204. package/src/memory/app-store.ts +230 -41
  205. package/src/memory/attachments-store.ts +558 -130
  206. package/src/memory/conversation-attention-store.ts +70 -0
  207. package/src/memory/conversation-crud.ts +442 -3
  208. package/src/memory/conversation-directories.ts +125 -0
  209. package/src/memory/conversation-disk-view.ts +390 -0
  210. package/src/memory/conversation-key-store.ts +17 -5
  211. package/src/memory/conversation-queries.ts +5 -1
  212. package/src/memory/conversation-title-service.ts +21 -49
  213. package/src/memory/db-init.ts +28 -0
  214. package/src/memory/embedding-backend.ts +42 -53
  215. package/src/memory/embedding-gemini.test.ts +4 -4
  216. package/src/memory/embedding-local.ts +1 -3
  217. package/src/memory/embedding-ollama.ts +1 -3
  218. package/src/memory/embedding-openai.ts +1 -3
  219. package/src/memory/indexer.ts +9 -7
  220. package/src/memory/items-extractor.ts +42 -13
  221. package/src/memory/job-handlers/conversation-starters.ts +6 -1
  222. package/src/memory/job-handlers/embedding.test.ts +1 -4
  223. package/src/memory/llm-request-log-store.ts +100 -1
  224. package/src/memory/migrations/102-alter-table-columns.ts +5 -0
  225. package/src/memory/migrations/146-schedule-oneshot-routing.ts +3 -3
  226. package/src/memory/migrations/147-migrate-reminders-to-schedules.ts +66 -70
  227. package/src/memory/migrations/148-drop-reminders-table.ts +5 -9
  228. package/src/memory/migrations/160-drop-loopback-port-column.ts +1 -3
  229. package/src/memory/migrations/174-rename-thread-starters-table.ts +0 -7
  230. package/src/memory/migrations/178-oauth-providers-managed-service-config-key.ts +15 -0
  231. package/src/memory/migrations/179-llm-request-log-message-id.ts +16 -0
  232. package/src/memory/migrations/180-backfill-inline-attachments-to-disk.ts +66 -0
  233. package/src/memory/migrations/181-rename-thread-starters-checkpoints.ts +46 -0
  234. package/src/memory/migrations/182-oauth-providers-display-metadata.ts +20 -0
  235. package/src/memory/migrations/183-add-conversation-fork-lineage.ts +22 -0
  236. package/src/memory/migrations/184-llm-request-log-provider.ts +12 -0
  237. package/src/memory/migrations/index.ts +7 -0
  238. package/src/memory/migrations/registry.ts +13 -0
  239. package/src/memory/retriever.test.ts +601 -2
  240. package/src/memory/retriever.ts +85 -9
  241. package/src/memory/schema/conversations.ts +6 -0
  242. package/src/memory/schema/infrastructure.ts +13 -7
  243. package/src/memory/schema/oauth.ts +6 -0
  244. package/src/messaging/providers/gmail/mime-builder.ts +3 -1
  245. package/src/notifications/copy-composer.ts +26 -0
  246. package/src/notifications/decision-engine.ts +14 -1
  247. package/src/notifications/emit-signal.ts +1 -1
  248. package/src/notifications/signal.ts +36 -0
  249. package/src/oauth/byo-connection.test.ts +1 -45
  250. package/src/oauth/byo-connection.ts +2 -8
  251. package/src/oauth/connect-orchestrator.ts +15 -11
  252. package/src/oauth/connection-resolver.test.ts +191 -0
  253. package/src/oauth/connection-resolver.ts +66 -38
  254. package/src/oauth/connection.ts +0 -1
  255. package/src/oauth/oauth-store.ts +97 -47
  256. package/src/oauth/platform-connection.test.ts +0 -1
  257. package/src/oauth/platform-connection.ts +11 -3
  258. package/src/oauth/seed-providers.ts +78 -3
  259. package/src/oauth/token-persistence.ts +16 -10
  260. package/src/permissions/checker.ts +62 -19
  261. package/src/prompts/system-prompt.ts +2 -0
  262. package/src/prompts/templates/BOOTSTRAP.md +2 -0
  263. package/src/providers/anthropic/client.ts +8 -1
  264. package/src/providers/failover.ts +4 -1
  265. package/src/providers/gemini/client.ts +50 -0
  266. package/src/providers/model-catalog.ts +92 -0
  267. package/src/providers/model-intents.ts +29 -20
  268. package/src/providers/openai/client.ts +49 -0
  269. package/src/providers/types.ts +2 -0
  270. package/src/runtime/access-request-helper.ts +16 -7
  271. package/src/runtime/auth/credential-service.ts +3 -1
  272. package/src/runtime/auth/route-policy.ts +14 -1
  273. package/src/runtime/btw-sidechain.ts +101 -0
  274. package/src/runtime/channel-reply-delivery.ts +17 -1
  275. package/src/runtime/http-router.ts +3 -1
  276. package/src/runtime/http-server.ts +196 -141
  277. package/src/runtime/http-types.ts +1 -0
  278. package/src/runtime/migrations/vbundle-builder.ts +5 -1
  279. package/src/runtime/routes/access-request-decision.ts +41 -0
  280. package/src/runtime/routes/app-management-routes.ts +6 -3
  281. package/src/runtime/routes/app-routes.ts +7 -3
  282. package/src/runtime/routes/approval-routes.ts +1 -0
  283. package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +34 -2
  284. package/src/runtime/routes/attachment-routes.ts +45 -15
  285. package/src/runtime/routes/btw-routes.ts +21 -61
  286. package/src/runtime/routes/conversation-management-routes.ts +68 -0
  287. package/src/runtime/routes/conversation-query-routes.ts +180 -10
  288. package/src/runtime/routes/conversation-routes.ts +222 -28
  289. package/src/runtime/routes/conversation-starter-routes.ts +9 -11
  290. package/src/runtime/routes/diagnostics-routes.ts +1 -0
  291. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +2 -2
  292. package/src/runtime/routes/llm-context-normalization.ts +1199 -0
  293. package/src/runtime/routes/log-export-routes.ts +3 -0
  294. package/src/runtime/routes/memory-item-routes.test.ts +34 -0
  295. package/src/runtime/routes/memory-item-routes.ts +4 -0
  296. package/src/runtime/routes/migration-routes.ts +4 -1
  297. package/src/runtime/routes/oauth-apps.ts +291 -0
  298. package/src/runtime/routes/secret-routes.ts +28 -1
  299. package/src/runtime/routes/settings-routes.ts +14 -0
  300. package/src/runtime/routes/trace-event-routes.ts +4 -1
  301. package/src/schedule/schedule-store.ts +9 -21
  302. package/src/security/secure-keys.ts +21 -0
  303. package/src/signals/bash.ts +1 -1
  304. package/src/swarm/backend-claude-code.ts +3 -6
  305. package/src/telemetry/usage-telemetry-reporter.test.ts +3 -2
  306. package/src/telemetry/usage-telemetry-reporter.ts +3 -1
  307. package/src/tools/AGENTS.md +6 -10
  308. package/src/tools/apps/executors.ts +17 -232
  309. package/src/tools/claude-code/claude-code.ts +2 -3
  310. package/src/tools/credentials/vault.ts +7 -12
  311. package/src/tools/host-filesystem/read.ts +13 -10
  312. package/src/tools/network/__tests__/web-search.test.ts +4 -2
  313. package/src/tools/schedule/list.ts +2 -7
  314. package/src/tools/schema-transforms.ts +5 -0
  315. package/src/tools/shared/filesystem/format-diff.ts +4 -21
  316. package/src/tools/skills/execute.ts +1 -1
  317. package/src/tools/tool-manifest.ts +0 -6
  318. package/src/tools/ui-surface/definitions.ts +2 -2
  319. package/src/util/device-id.ts +28 -5
  320. package/src/util/platform.ts +6 -0
  321. package/src/util/pricing.ts +1 -0
  322. package/src/util/retry.ts +1 -3
  323. package/src/workspace/migrations/002-backfill-installation-id.ts +23 -12
  324. package/src/workspace/migrations/003-seed-device-id.ts +3 -4
  325. package/src/workspace/migrations/006-services-config.ts +5 -0
  326. package/src/workspace/migrations/008-voice-timeout-and-max-steps.ts +12 -0
  327. package/src/workspace/migrations/009-backfill-conversation-disk-view.ts +10 -0
  328. package/src/workspace/migrations/010-app-dir-rename.ts +223 -0
  329. package/src/workspace/migrations/012-rename-conversation-disk-view-dirs.ts +64 -0
  330. package/src/workspace/migrations/013-repair-conversation-disk-view.ts +11 -0
  331. package/src/workspace/migrations/rebuild-conversation-disk-view.ts +186 -0
  332. package/src/workspace/migrations/registry.ts +10 -0
  333. package/src/workspace/top-level-renderer.ts +12 -0
  334. package/src/__tests__/asset-materialize-tool.test.ts +0 -523
  335. package/src/__tests__/asset-search-tool.test.ts +0 -536
  336. package/src/__tests__/fixtures/media-reuse-fixtures.ts +0 -56
  337. package/src/__tests__/media-reuse-story.e2e.test.ts +0 -762
  338. package/src/__tests__/media-visibility-policy.test.ts +0 -190
  339. package/src/config/bundled-skills/app-builder/tools/app-file-edit.ts +0 -14
  340. package/src/config/bundled-skills/app-builder/tools/app-file-list.ts +0 -13
  341. package/src/config/bundled-skills/app-builder/tools/app-file-read.ts +0 -21
  342. package/src/config/bundled-skills/app-builder/tools/app-file-write.ts +0 -14
  343. package/src/config/bundled-skills/app-builder/tools/app-list.ts +0 -13
  344. package/src/config/bundled-skills/app-builder/tools/app-update.ts +0 -23
  345. package/src/daemon/media-visibility-policy.ts +0 -59
  346. package/src/tools/assets/materialize.ts +0 -248
  347. package/src/tools/assets/search.ts +0 -400
@@ -53,10 +53,18 @@ mock.module("../memory/embedding-local.js", () => ({
53
53
  }));
54
54
 
55
55
  // Mock Qdrant client so semantic search returns empty results by default.
56
+ // Tests can push entries into `mockQdrantResults` to simulate Qdrant returning
57
+ // specific hits (e.g. item candidates).
58
+ const mockQdrantResults: Array<{
59
+ id: string;
60
+ score: number;
61
+ payload: Record<string, unknown>;
62
+ }> = [];
63
+
56
64
  mock.module("../memory/qdrant-client.js", () => ({
57
65
  getQdrantClient: () => ({
58
- searchWithFilter: async () => [],
59
- hybridSearch: async () => [],
66
+ searchWithFilter: async () => [...mockQdrantResults],
67
+ hybridSearch: async () => [...mockQdrantResults],
60
68
  upsertPoints: async () => {},
61
69
  deletePoints: async () => {},
62
70
  }),
@@ -145,6 +153,7 @@ function insertMessage(
145
153
  role: string,
146
154
  text: string,
147
155
  createdAt: number,
156
+ opts?: { metadata?: string | null },
148
157
  ) {
149
158
  db.insert(messages)
150
159
  .values({
@@ -153,6 +162,7 @@ function insertMessage(
153
162
  role,
154
163
  content: JSON.stringify([{ type: "text", text }]),
155
164
  createdAt,
165
+ metadata: opts?.metadata ?? null,
156
166
  })
157
167
  .run();
158
168
  }
@@ -297,6 +307,7 @@ describe("Memory Retriever Pipeline", () => {
297
307
  db.run("DELETE FROM conversations");
298
308
  _resetQdrantBreaker();
299
309
  clearEmbeddingBackendCache();
310
+ mockQdrantResults.length = 0;
300
311
  });
301
312
 
302
313
  afterAll(() => {
@@ -858,4 +869,592 @@ describe("Memory Retriever Pipeline", () => {
858
869
  // Current-conversation segments are filtered out of merged results
859
870
  expect(result.mergedCount).toBe(0);
860
871
  });
872
+
873
+ // -----------------------------------------------------------------------
874
+ // Step 5b: in-context item filtering
875
+ // -----------------------------------------------------------------------
876
+
877
+ describe("step 5b: in-context item filtering", () => {
878
+ test("filters items whose all sources are in-context messages", async () => {
879
+ const db = getDb();
880
+ const now = Date.now();
881
+ const convId = "conv-item-filter";
882
+
883
+ insertConversation(db, convId, now - 60_000);
884
+ insertMessage(db, "msg-if-1", convId, "user", "hello", now - 50_000);
885
+ insertMessage(db, "msg-if-2", convId, "assistant", "world", now - 40_000);
886
+ insertMessage(
887
+ db,
888
+ "msg-if-3",
889
+ convId,
890
+ "user",
891
+ "memory items test",
892
+ now - 30_000,
893
+ );
894
+
895
+ // Insert a memory item sourced from msg-if-2 (in-context)
896
+ insertItem(db, {
897
+ id: "item-in-ctx",
898
+ kind: "fact",
899
+ subject: "test",
900
+ statement: "A fact from in-context message",
901
+ firstSeenAt: now - 35_000,
902
+ });
903
+ insertItemSource(db, "item-in-ctx", "msg-if-2", now - 35_000);
904
+
905
+ // Simulate Qdrant returning this item as a semantic hit
906
+ mockQdrantResults.push({
907
+ id: "qdrant-pt-1",
908
+ score: 0.9,
909
+ payload: {
910
+ target_type: "item",
911
+ target_id: "item-in-ctx",
912
+ text: "test: A fact from in-context message",
913
+ created_at: now - 35_000,
914
+ },
915
+ });
916
+
917
+ const result = await buildMemoryRecall(
918
+ "memory items test",
919
+ convId,
920
+ TEST_CONFIG,
921
+ );
922
+
923
+ expect(result.enabled).toBe(true);
924
+ // The item should be filtered because its only source is in-context
925
+ expect(result.mergedCount).toBe(0);
926
+ });
927
+
928
+ test("keeps items from compacted messages", async () => {
929
+ const db = getDb();
930
+ const now = Date.now();
931
+ const convId = "conv-item-compacted";
932
+
933
+ // 2 messages compacted away
934
+ insertConversation(db, convId, now - 120_000, {
935
+ contextCompactedMessageCount: 2,
936
+ });
937
+
938
+ // Compacted messages (first 2 by createdAt order)
939
+ insertMessage(
940
+ db,
941
+ "msg-ic-1",
942
+ convId,
943
+ "user",
944
+ "compacted old topic",
945
+ now - 100_000,
946
+ );
947
+ insertMessage(
948
+ db,
949
+ "msg-ic-2",
950
+ convId,
951
+ "assistant",
952
+ "compacted old reply",
953
+ now - 90_000,
954
+ );
955
+
956
+ // Still in context
957
+ insertMessage(
958
+ db,
959
+ "msg-ic-3",
960
+ convId,
961
+ "user",
962
+ "item compaction test",
963
+ now - 50_000,
964
+ );
965
+
966
+ // Item sourced from a compacted message — should be kept
967
+ insertItem(db, {
968
+ id: "item-compacted",
969
+ kind: "fact",
970
+ subject: "compaction",
971
+ statement: "A fact from a compacted message",
972
+ firstSeenAt: now - 95_000,
973
+ });
974
+ insertItemSource(db, "item-compacted", "msg-ic-1", now - 95_000);
975
+
976
+ // Simulate Qdrant returning this item as a semantic hit
977
+ mockQdrantResults.push({
978
+ id: "qdrant-pt-2",
979
+ score: 0.9,
980
+ payload: {
981
+ target_type: "item",
982
+ target_id: "item-compacted",
983
+ text: "compaction: A fact from a compacted message",
984
+ created_at: now - 95_000,
985
+ },
986
+ });
987
+
988
+ const result = await buildMemoryRecall(
989
+ "item compaction test",
990
+ convId,
991
+ TEST_CONFIG,
992
+ );
993
+
994
+ expect(result.enabled).toBe(true);
995
+ // The item sourced from a compacted message should survive filtering
996
+ // because its source is no longer in the context window
997
+ expect(result.mergedCount).toBeGreaterThan(0);
998
+ });
999
+
1000
+ test("keeps items with cross-conversation sources", async () => {
1001
+ const db = getDb();
1002
+ const now = Date.now();
1003
+ const convId = "conv-item-cross";
1004
+ const otherConvId = "conv-item-other";
1005
+
1006
+ insertConversation(db, convId, now - 60_000);
1007
+ insertConversation(db, otherConvId, now - 120_000);
1008
+
1009
+ // Messages in current conversation
1010
+ insertMessage(
1011
+ db,
1012
+ "msg-cr-1",
1013
+ convId,
1014
+ "user",
1015
+ "cross conv test",
1016
+ now - 50_000,
1017
+ );
1018
+ insertMessage(
1019
+ db,
1020
+ "msg-cr-2",
1021
+ convId,
1022
+ "assistant",
1023
+ "cross conv reply",
1024
+ now - 40_000,
1025
+ );
1026
+
1027
+ // Message in the other conversation
1028
+ insertMessage(
1029
+ db,
1030
+ "msg-cr-other",
1031
+ otherConvId,
1032
+ "user",
1033
+ "other conv msg",
1034
+ now - 100_000,
1035
+ );
1036
+
1037
+ // Item sourced from BOTH the current conversation AND a different one
1038
+ insertItem(db, {
1039
+ id: "item-cross",
1040
+ kind: "fact",
1041
+ subject: "cross",
1042
+ statement: "A cross-conversation fact",
1043
+ firstSeenAt: now - 95_000,
1044
+ });
1045
+ insertItemSource(db, "item-cross", "msg-cr-1", now - 45_000);
1046
+ insertItemSource(db, "item-cross", "msg-cr-other", now - 95_000);
1047
+
1048
+ // Simulate Qdrant returning this item as a semantic hit
1049
+ mockQdrantResults.push({
1050
+ id: "qdrant-pt-3",
1051
+ score: 0.9,
1052
+ payload: {
1053
+ target_type: "item",
1054
+ target_id: "item-cross",
1055
+ text: "cross: A cross-conversation fact",
1056
+ created_at: now - 95_000,
1057
+ },
1058
+ });
1059
+
1060
+ const result = await buildMemoryRecall(
1061
+ "cross conv test",
1062
+ convId,
1063
+ TEST_CONFIG,
1064
+ );
1065
+
1066
+ expect(result.enabled).toBe(true);
1067
+ // The item has a source outside the in-context set (from other conv),
1068
+ // so it should NOT be filtered — it carries cross-conversation info
1069
+ expect(result.mergedCount).toBeGreaterThan(0);
1070
+ });
1071
+ });
1072
+
1073
+ // -----------------------------------------------------------------------
1074
+ // Step 5b: fork-aware filtering
1075
+ // -----------------------------------------------------------------------
1076
+
1077
+ describe("step 5b: fork-aware filtering", () => {
1078
+ test("filters segments sourced from fork-parent messages", async () => {
1079
+ const db = getDb();
1080
+ const now = Date.now();
1081
+
1082
+ // Parent conversation with messages
1083
+ const parentConv = "conv-parent";
1084
+ insertConversation(db, parentConv, now - 120_000);
1085
+ insertMessage(
1086
+ db,
1087
+ "parent-msg-1",
1088
+ parentConv,
1089
+ "user",
1090
+ "discuss fork patterns",
1091
+ now - 110_000,
1092
+ );
1093
+ insertMessage(
1094
+ db,
1095
+ "parent-msg-2",
1096
+ parentConv,
1097
+ "assistant",
1098
+ "fork patterns are useful",
1099
+ now - 100_000,
1100
+ );
1101
+
1102
+ // Fork conversation — messages are copies with forkSourceMessageId metadata
1103
+ const forkConv = "conv-fork";
1104
+ insertConversation(db, forkConv, now - 50_000);
1105
+ insertMessage(
1106
+ db,
1107
+ "fork-msg-1",
1108
+ forkConv,
1109
+ "user",
1110
+ "discuss fork patterns",
1111
+ now - 50_000,
1112
+ {
1113
+ metadata: JSON.stringify({
1114
+ forkSourceMessageId: "parent-msg-1",
1115
+ }),
1116
+ },
1117
+ );
1118
+ insertMessage(
1119
+ db,
1120
+ "fork-msg-2",
1121
+ forkConv,
1122
+ "assistant",
1123
+ "fork patterns are useful",
1124
+ now - 49_000,
1125
+ {
1126
+ metadata: JSON.stringify({
1127
+ forkSourceMessageId: "parent-msg-2",
1128
+ }),
1129
+ },
1130
+ );
1131
+
1132
+ // Segment sourced from a parent message — should be filtered when
1133
+ // recalling for the fork conversation since the fork copy is in context.
1134
+ insertSegment(
1135
+ db,
1136
+ "seg-parent-1",
1137
+ "parent-msg-1",
1138
+ parentConv,
1139
+ "user",
1140
+ "discuss fork patterns detail",
1141
+ now - 110_000,
1142
+ );
1143
+
1144
+ // Simulate Qdrant returning the parent-conversation segment as a
1145
+ // semantic hit so it enters the candidate map (recency search is scoped
1146
+ // to forkConv and would never find it).
1147
+ mockQdrantResults.push({
1148
+ id: "qdrant-fork-1",
1149
+ score: 0.9,
1150
+ payload: {
1151
+ target_type: "segment",
1152
+ target_id: "seg-parent-1",
1153
+ text: "discuss fork patterns detail",
1154
+ created_at: now - 110_000,
1155
+ message_id: "parent-msg-1",
1156
+ conversation_id: parentConv,
1157
+ },
1158
+ });
1159
+
1160
+ const result = await buildMemoryRecall(
1161
+ "fork patterns",
1162
+ forkConv,
1163
+ TEST_CONFIG,
1164
+ );
1165
+
1166
+ expect(result.enabled).toBe(true);
1167
+ // The segment entered the candidate map via semantic search…
1168
+ expect(result.semanticHits).toBeGreaterThanOrEqual(1);
1169
+ // …but the fork-source filtering removed it because parent-msg-1 is
1170
+ // in the in-context set (via forkSourceMessageId on fork-msg-1).
1171
+ expect(result.mergedCount).toBe(0);
1172
+ });
1173
+
1174
+ test("keeps segments from compacted fork messages' parents", async () => {
1175
+ const db = getDb();
1176
+ const now = Date.now();
1177
+
1178
+ // Parent conversation
1179
+ const parentConv = "conv-parent-compact";
1180
+ insertConversation(db, parentConv, now - 200_000);
1181
+ insertMessage(
1182
+ db,
1183
+ "parent-compact-msg-1",
1184
+ parentConv,
1185
+ "user",
1186
+ "compacted parent topic",
1187
+ now - 190_000,
1188
+ );
1189
+ insertMessage(
1190
+ db,
1191
+ "parent-compact-msg-2",
1192
+ parentConv,
1193
+ "assistant",
1194
+ "compacted parent response",
1195
+ now - 180_000,
1196
+ );
1197
+
1198
+ // Fork conversation with compaction — first 2 messages are compacted
1199
+ const forkConv = "conv-fork-compact";
1200
+ insertConversation(db, forkConv, now - 100_000, {
1201
+ contextCompactedMessageCount: 2,
1202
+ });
1203
+
1204
+ // These two messages are compacted (offset=2 means first 2 are compacted)
1205
+ insertMessage(
1206
+ db,
1207
+ "fork-compact-msg-1",
1208
+ forkConv,
1209
+ "user",
1210
+ "compacted parent topic",
1211
+ now - 100_000,
1212
+ {
1213
+ metadata: JSON.stringify({
1214
+ forkSourceMessageId: "parent-compact-msg-1",
1215
+ }),
1216
+ },
1217
+ );
1218
+ insertMessage(
1219
+ db,
1220
+ "fork-compact-msg-2",
1221
+ forkConv,
1222
+ "assistant",
1223
+ "compacted parent response",
1224
+ now - 99_000,
1225
+ {
1226
+ metadata: JSON.stringify({
1227
+ forkSourceMessageId: "parent-compact-msg-2",
1228
+ }),
1229
+ },
1230
+ );
1231
+
1232
+ // A newer message still in context
1233
+ insertMessage(
1234
+ db,
1235
+ "fork-compact-msg-3",
1236
+ forkConv,
1237
+ "user",
1238
+ "recent fork topic",
1239
+ now - 50_000,
1240
+ );
1241
+
1242
+ // Segment in the fork conversation sourced from a compacted fork
1243
+ // message. Since the fork message is compacted, its forkSourceMessageId
1244
+ // is NOT added to the in-context set, so the segment should survive.
1245
+ insertSegment(
1246
+ db,
1247
+ "seg-compact-fork",
1248
+ "fork-compact-msg-1",
1249
+ forkConv,
1250
+ "user",
1251
+ "compacted parent topic detail",
1252
+ now - 100_000,
1253
+ );
1254
+
1255
+ // Also insert a segment from an in-context message for contrast —
1256
+ // this one SHOULD be filtered.
1257
+ insertSegment(
1258
+ db,
1259
+ "seg-in-context-fork",
1260
+ "fork-compact-msg-3",
1261
+ forkConv,
1262
+ "user",
1263
+ "recent fork topic detail",
1264
+ now - 50_000,
1265
+ );
1266
+
1267
+ const result = await buildMemoryRecall(
1268
+ "compacted parent topic",
1269
+ forkConv,
1270
+ TEST_CONFIG,
1271
+ );
1272
+
1273
+ expect(result.enabled).toBe(true);
1274
+ // The segment from the compacted fork message survives filtering
1275
+ // (its source message is no longer in context). The in-context segment
1276
+ // is filtered out. Recency search returns both, but only the compacted
1277
+ // one survives step 5b.
1278
+ expect(result.mergedCount).toBeGreaterThan(0);
1279
+ });
1280
+
1281
+ test("handles multi-level forks", async () => {
1282
+ const db = getDb();
1283
+ const now = Date.now();
1284
+
1285
+ // Grandparent conversation
1286
+ const grandparentConv = "conv-grandparent";
1287
+ insertConversation(db, grandparentConv, now - 300_000);
1288
+ insertMessage(
1289
+ db,
1290
+ "gp-msg-1",
1291
+ grandparentConv,
1292
+ "user",
1293
+ "grandparent topic",
1294
+ now - 290_000,
1295
+ );
1296
+
1297
+ // Parent conversation (fork of grandparent)
1298
+ // The fork metadata preserves the original grandparent message ID
1299
+ const parentConv = "conv-parent-multi";
1300
+ insertConversation(db, parentConv, now - 200_000);
1301
+ insertMessage(
1302
+ db,
1303
+ "parent-multi-msg-1",
1304
+ parentConv,
1305
+ "user",
1306
+ "grandparent topic",
1307
+ now - 200_000,
1308
+ {
1309
+ metadata: JSON.stringify({
1310
+ forkSourceMessageId: "gp-msg-1",
1311
+ }),
1312
+ },
1313
+ );
1314
+
1315
+ // Child conversation (fork of parent)
1316
+ // forkSourceMessageId still points to the original grandparent message
1317
+ const childConv = "conv-child-multi";
1318
+ insertConversation(db, childConv, now - 100_000);
1319
+ insertMessage(
1320
+ db,
1321
+ "child-multi-msg-1",
1322
+ childConv,
1323
+ "user",
1324
+ "grandparent topic",
1325
+ now - 100_000,
1326
+ {
1327
+ metadata: JSON.stringify({
1328
+ forkSourceMessageId: "gp-msg-1",
1329
+ }),
1330
+ },
1331
+ );
1332
+
1333
+ // Segment sourced from the grandparent message
1334
+ insertSegment(
1335
+ db,
1336
+ "seg-gp",
1337
+ "gp-msg-1",
1338
+ grandparentConv,
1339
+ "user",
1340
+ "grandparent topic detail",
1341
+ now - 290_000,
1342
+ );
1343
+
1344
+ // Simulate Qdrant returning the grandparent segment as a semantic hit
1345
+ // so it enters the candidate map (recency search is scoped to childConv
1346
+ // and would never find it).
1347
+ mockQdrantResults.push({
1348
+ id: "qdrant-gp-1",
1349
+ score: 0.9,
1350
+ payload: {
1351
+ target_type: "segment",
1352
+ target_id: "seg-gp",
1353
+ text: "grandparent topic detail",
1354
+ created_at: now - 290_000,
1355
+ message_id: "gp-msg-1",
1356
+ conversation_id: grandparentConv,
1357
+ },
1358
+ });
1359
+
1360
+ const result = await buildMemoryRecall(
1361
+ "grandparent topic",
1362
+ childConv,
1363
+ TEST_CONFIG,
1364
+ );
1365
+
1366
+ expect(result.enabled).toBe(true);
1367
+ // The segment entered the candidate map via semantic search…
1368
+ expect(result.semanticHits).toBeGreaterThanOrEqual(1);
1369
+ // …but the fork-source filtering removed it because gp-msg-1 is in the
1370
+ // in-context set (via forkSourceMessageId on child-multi-msg-1).
1371
+ expect(result.mergedCount).toBe(0);
1372
+ });
1373
+
1374
+ test("handles missing or invalid metadata gracefully", async () => {
1375
+ const db = getDb();
1376
+ const now = Date.now();
1377
+
1378
+ const forkConv = "conv-fork-bad-meta";
1379
+ insertConversation(db, forkConv, now - 50_000);
1380
+
1381
+ // Message with null metadata (no forkSourceMessageId)
1382
+ insertMessage(
1383
+ db,
1384
+ "fork-null-meta",
1385
+ forkConv,
1386
+ "user",
1387
+ "null metadata topic",
1388
+ now - 50_000,
1389
+ );
1390
+
1391
+ // Message with malformed JSON metadata
1392
+ insertMessage(
1393
+ db,
1394
+ "fork-bad-json",
1395
+ forkConv,
1396
+ "assistant",
1397
+ "bad json topic",
1398
+ now - 49_000,
1399
+ { metadata: "not valid json {{{" },
1400
+ );
1401
+
1402
+ // Message with metadata that is a JSON array (not an object)
1403
+ insertMessage(
1404
+ db,
1405
+ "fork-array-meta",
1406
+ forkConv,
1407
+ "user",
1408
+ "array metadata topic",
1409
+ now - 48_000,
1410
+ { metadata: JSON.stringify([1, 2, 3]) },
1411
+ );
1412
+
1413
+ // Message with metadata object but no forkSourceMessageId field
1414
+ insertMessage(
1415
+ db,
1416
+ "fork-no-field",
1417
+ forkConv,
1418
+ "assistant",
1419
+ "no field topic",
1420
+ now - 47_000,
1421
+ { metadata: JSON.stringify({ someOtherField: "value" }) },
1422
+ );
1423
+
1424
+ // Message with forkSourceMessageId that is not a string
1425
+ insertMessage(
1426
+ db,
1427
+ "fork-non-string",
1428
+ forkConv,
1429
+ "user",
1430
+ "non-string fork id",
1431
+ now - 46_000,
1432
+ { metadata: JSON.stringify({ forkSourceMessageId: 12345 }) },
1433
+ );
1434
+
1435
+ // Insert a segment from this conversation — should be filtered normally
1436
+ // (it's an in-context segment from the active conversation)
1437
+ insertSegment(
1438
+ db,
1439
+ "seg-bad-meta",
1440
+ "fork-null-meta",
1441
+ forkConv,
1442
+ "user",
1443
+ "null metadata topic detail",
1444
+ now - 50_000,
1445
+ );
1446
+
1447
+ // This should not crash despite various malformed metadata
1448
+ const result = await buildMemoryRecall(
1449
+ "metadata topic",
1450
+ forkConv,
1451
+ TEST_CONFIG,
1452
+ );
1453
+
1454
+ expect(result.enabled).toBe(true);
1455
+ // No crash — the pipeline completes successfully
1456
+ // The in-context segment is still filtered normally
1457
+ expect(result.mergedCount).toBe(0);
1458
+ });
1459
+ });
861
1460
  });