@vellumai/assistant 0.5.1 → 0.5.3

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 (405) hide show
  1. package/ARCHITECTURE.md +163 -54
  2. package/docs/architecture/integrations.md +62 -67
  3. package/docs/credential-execution-service.md +3 -3
  4. package/docs/skills.md +100 -0
  5. package/package.json +1 -1
  6. package/src/__tests__/agent-loop.test.ts +111 -0
  7. package/src/__tests__/always-loaded-tools-guard.test.ts +3 -4
  8. package/src/__tests__/app-builder-tool-scripts.test.ts +13 -151
  9. package/src/__tests__/app-dir-path-guard.test.ts +78 -0
  10. package/src/__tests__/app-executors.test.ts +1 -291
  11. package/src/__tests__/app-git-history.test.ts +4 -4
  12. package/src/__tests__/app-routes-csp.test.ts +1 -0
  13. package/src/__tests__/app-store-dir-names.test.ts +426 -0
  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 +156 -5
  26. package/src/__tests__/conversation-agent-loop.test.ts +297 -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-memory-dirty-tail.test.ts +150 -0
  37. package/src/__tests__/conversation-provider-retry-repair.test.ts +7 -0
  38. package/src/__tests__/conversation-queue.test.ts +36 -1
  39. package/src/__tests__/conversation-routes-disk-view.test.ts +439 -0
  40. package/src/__tests__/conversation-routes-guardian-reply.test.ts +2 -2
  41. package/src/__tests__/conversation-routes-slash-commands.test.ts +2 -7
  42. package/src/__tests__/conversation-runtime-assembly.test.ts +17 -2
  43. package/src/__tests__/conversation-skill-tools.test.ts +4 -9
  44. package/src/__tests__/conversation-slash-commands.test.ts +149 -0
  45. package/src/__tests__/conversation-store.test.ts +24 -21
  46. package/src/__tests__/conversation-surfaces-state-update.test.ts +246 -0
  47. package/src/__tests__/conversation-surfaces-task-progress.test.ts +1 -0
  48. package/src/__tests__/conversation-title-service.test.ts +137 -0
  49. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +25 -315
  50. package/src/__tests__/conversation-tool-setup-memory-scope.test.ts +1 -0
  51. package/src/__tests__/conversation-tool-setup-side-effect-flag.test.ts +1 -0
  52. package/src/__tests__/conversation-wipe.test.ts +226 -0
  53. package/src/__tests__/conversation-workspace-cache-state.test.ts +44 -2
  54. package/src/__tests__/conversation-workspace-injection.test.ts +11 -0
  55. package/src/__tests__/credential-security-invariants.test.ts +3 -0
  56. package/src/__tests__/credential-vault-unit.test.ts +5 -10
  57. package/src/__tests__/cu-unified-flow.test.ts +1 -0
  58. package/src/__tests__/db-conversation-fork-lineage-migration.test.ts +241 -0
  59. package/src/__tests__/db-llm-request-log-provider-migration.test.ts +214 -0
  60. package/src/__tests__/db-memory-archive-migration.test.ts +372 -0
  61. package/src/__tests__/db-memory-brief-state-migration.test.ts +213 -0
  62. package/src/__tests__/db-memory-reducer-checkpoints.test.ts +273 -0
  63. package/src/__tests__/diagnostics-export.test.ts +70 -1
  64. package/src/__tests__/first-greeting.test.ts +80 -0
  65. package/src/__tests__/gateway-only-guard.test.ts +1 -0
  66. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +3 -7
  67. package/src/__tests__/history-repair.test.ts +32 -10
  68. package/src/__tests__/http-conversation-lineage.test.ts +251 -0
  69. package/src/__tests__/image-source-path-reinject.test.ts +136 -0
  70. package/src/__tests__/inline-command-runner.test.ts +311 -0
  71. package/src/__tests__/inline-skill-authoring-guard.test.ts +220 -0
  72. package/src/__tests__/inline-skill-load-permissions.test.ts +435 -0
  73. package/src/__tests__/list-messages-attachments.test.ts +96 -0
  74. package/src/__tests__/llm-context-normalization.test.ts +1116 -0
  75. package/src/__tests__/llm-context-route-provider.test.ts +217 -0
  76. package/src/__tests__/llm-request-log-turn-query.test.ts +270 -0
  77. package/src/__tests__/media-generate-image.test.ts +47 -94
  78. package/src/__tests__/memory-brief-open-loops.test.ts +530 -0
  79. package/src/__tests__/memory-brief-time.test.ts +285 -0
  80. package/src/__tests__/memory-brief-wrapper.test.ts +311 -0
  81. package/src/__tests__/memory-chunk-archive.test.ts +400 -0
  82. package/src/__tests__/memory-chunk-dual-write.test.ts +453 -0
  83. package/src/__tests__/memory-episode-archive.test.ts +370 -0
  84. package/src/__tests__/memory-episode-dual-write.test.ts +626 -0
  85. package/src/__tests__/memory-lifecycle-e2e.test.ts +3 -1
  86. package/src/__tests__/memory-observation-archive.test.ts +375 -0
  87. package/src/__tests__/memory-observation-dual-write.test.ts +318 -0
  88. package/src/__tests__/memory-recall-quality.test.ts +7 -7
  89. package/src/__tests__/memory-reducer-store.test.ts +728 -0
  90. package/src/__tests__/memory-reducer-types.test.ts +699 -0
  91. package/src/__tests__/memory-reducer.test.ts +698 -0
  92. package/src/__tests__/memory-regressions.test.ts +6 -4
  93. package/src/__tests__/memory-simplified-config.test.ts +281 -0
  94. package/src/__tests__/migration-cross-version-compatibility.test.ts +4 -1
  95. package/src/__tests__/migration-export-http.test.ts +3 -1
  96. package/src/__tests__/migration-import-commit-http.test.ts +18 -4
  97. package/src/__tests__/migration-import-preflight-http.test.ts +1 -3
  98. package/src/__tests__/mime-builder.test.ts +3 -2
  99. package/src/__tests__/non-member-access-request.test.ts +12 -1
  100. package/src/__tests__/notification-decision-identity.test.ts +52 -0
  101. package/src/__tests__/oauth-apps-routes.test.ts +103 -0
  102. package/src/__tests__/oauth-store.test.ts +115 -0
  103. package/src/__tests__/parse-identity-fields.test.ts +129 -0
  104. package/src/__tests__/provider-error-scenarios.test.ts +1 -3
  105. package/src/__tests__/provider-failover-actual-provider.test.ts +66 -0
  106. package/src/__tests__/recording-handler.test.ts +17 -0
  107. package/src/__tests__/registry.test.ts +3 -8
  108. package/src/__tests__/relay-server.test.ts +1 -1
  109. package/src/__tests__/runtime-attachment-metadata.test.ts +7 -3
  110. package/src/__tests__/schema-transforms.test.ts +165 -5
  111. package/src/__tests__/server-history-render.test.ts +2 -2
  112. package/src/__tests__/skill-load-inline-command.test.ts +598 -0
  113. package/src/__tests__/skill-load-inline-includes.test.ts +644 -0
  114. package/src/__tests__/skills-inline-command-expansions.test.ts +301 -0
  115. package/src/__tests__/skills-transitive-hash.test.ts +333 -0
  116. package/src/__tests__/slack-app-setup-skill-regression.test.ts +3 -1
  117. package/src/__tests__/slack-inbound-verification.test.ts +2 -2
  118. package/src/__tests__/starter-task-flow.test.ts +1 -0
  119. package/src/__tests__/suggestion-routes.test.ts +443 -0
  120. package/src/__tests__/swarm-conversation-integration.test.ts +1 -0
  121. package/src/__tests__/swarm-recursion.test.ts +1 -0
  122. package/src/__tests__/swarm-tool.test.ts +1 -0
  123. package/src/__tests__/tool-execution-abort-cleanup.test.ts +1 -0
  124. package/src/__tests__/tool-preview-lifecycle.test.ts +32 -5
  125. package/src/__tests__/top-level-renderer.test.ts +22 -0
  126. package/src/__tests__/turn-boundary-resolution.test.ts +243 -0
  127. package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +320 -0
  128. package/src/__tests__/web-fetch.test.ts +6 -2
  129. package/src/__tests__/workspace-migration-006-services-config.test.ts +335 -0
  130. package/src/__tests__/workspace-migration-007-web-search-provider-rename.test.ts +312 -0
  131. package/src/__tests__/workspace-migration-009-backfill-conversation-disk-view.test.ts +278 -0
  132. package/src/__tests__/workspace-migration-010-app-dir-rename.test.ts +275 -0
  133. package/src/__tests__/workspace-migration-012-rename-conversation-disk-view-dirs.test.ts +77 -0
  134. package/src/__tests__/workspace-migration-013-repair-conversation-disk-view.test.ts +401 -0
  135. package/src/__tests__/workspace-migration-backfill-installation-id.test.ts +328 -0
  136. package/src/__tests__/workspace-migration-seed-device-id.test.ts +6 -10
  137. package/src/agent/attachments.ts +27 -1
  138. package/src/agent/loop.ts +29 -1
  139. package/src/avatar/traits-png-sync.ts +80 -25
  140. package/src/bundler/app-bundler.ts +4 -4
  141. package/src/calls/call-domain.ts +1 -0
  142. package/src/calls/voice-session-bridge.ts +1 -0
  143. package/src/cli/commands/auth.ts +92 -0
  144. package/src/cli/commands/avatar.ts +7 -6
  145. package/src/cli/commands/config.ts +2 -0
  146. package/src/cli/commands/oauth/providers.ts +29 -0
  147. package/src/cli/program.ts +12 -0
  148. package/src/cli.ts +15 -48
  149. package/src/config/bundled-skills/app-builder/SKILL.md +103 -28
  150. package/src/config/bundled-skills/app-builder/TOOLS.json +5 -199
  151. package/src/config/bundled-skills/app-builder/tools/{app-query.ts → app-refresh.ts} +2 -2
  152. package/src/config/bundled-skills/contacts/tools/google-contacts.ts +2 -3
  153. package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +6 -9
  154. package/src/config/bundled-skills/gmail/tools/gmail-attachments.ts +4 -6
  155. package/src/config/bundled-skills/gmail/tools/gmail-draft.ts +2 -3
  156. package/src/config/bundled-skills/gmail/tools/gmail-filters.ts +2 -3
  157. package/src/config/bundled-skills/gmail/tools/gmail-follow-up.ts +2 -3
  158. package/src/config/bundled-skills/gmail/tools/gmail-forward.ts +2 -3
  159. package/src/config/bundled-skills/gmail/tools/gmail-label.ts +4 -6
  160. package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +2 -3
  161. package/src/config/bundled-skills/gmail/tools/gmail-send-draft.ts +2 -3
  162. package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +2 -3
  163. package/src/config/bundled-skills/gmail/tools/gmail-trash.ts +2 -3
  164. package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +2 -3
  165. package/src/config/bundled-skills/gmail/tools/gmail-vacation.ts +2 -3
  166. package/src/config/bundled-skills/google-calendar/tools/shared.ts +1 -1
  167. package/src/config/bundled-skills/image-studio/SKILL.md +2 -2
  168. package/src/config/bundled-skills/image-studio/TOOLS.json +2 -2
  169. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +45 -72
  170. package/src/config/bundled-skills/media-processing/tools/extract-keyframes.ts +2 -2
  171. package/src/config/bundled-skills/messaging/tools/shared.ts +1 -1
  172. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +19 -3
  173. package/src/config/bundled-skills/skill-management/SKILL.md +1 -1
  174. package/src/config/bundled-skills/skill-management/TOOLS.json +2 -2
  175. package/src/config/bundled-skills/slack/tools/shared.ts +19 -4
  176. package/src/config/bundled-skills/slack/tools/slack-scan-digest.ts +2 -3
  177. package/src/config/bundled-skills/transcribe/SKILL.md +1 -1
  178. package/src/config/bundled-skills/transcribe/TOOLS.json +2 -6
  179. package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +19 -83
  180. package/src/config/bundled-tool-registry.ts +2 -14
  181. package/src/config/feature-flag-registry.json +24 -0
  182. package/src/config/loader.ts +65 -0
  183. package/src/config/raw-config-utils.ts +58 -0
  184. package/src/config/schema-utils.ts +28 -7
  185. package/src/config/schema.ts +20 -0
  186. package/src/config/schemas/elevenlabs.ts +18 -0
  187. package/src/config/schemas/memory-lifecycle.ts +4 -2
  188. package/src/config/schemas/memory-simplified.ts +101 -0
  189. package/src/config/schemas/memory-storage.ts +1 -1
  190. package/src/config/schemas/memory.ts +4 -0
  191. package/src/config/schemas/services.ts +8 -6
  192. package/src/config/skills.ts +50 -4
  193. package/src/contacts/contact-store.ts +13 -6
  194. package/src/contacts/contacts-write.ts +0 -1
  195. package/src/context/window-manager.ts +13 -2
  196. package/src/daemon/conversation-agent-loop-handlers.ts +54 -8
  197. package/src/daemon/conversation-agent-loop.ts +127 -20
  198. package/src/daemon/conversation-attachments.ts +18 -36
  199. package/src/daemon/conversation-error.ts +2 -1
  200. package/src/daemon/conversation-history.ts +18 -4
  201. package/src/daemon/conversation-lifecycle.ts +50 -16
  202. package/src/daemon/conversation-messaging.ts +70 -26
  203. package/src/daemon/conversation-process.ts +58 -34
  204. package/src/daemon/conversation-runtime-assembly.ts +22 -38
  205. package/src/daemon/conversation-slash.ts +121 -256
  206. package/src/daemon/conversation-surfaces.ts +170 -24
  207. package/src/daemon/conversation-tool-setup.ts +0 -6
  208. package/src/daemon/conversation-workspace.ts +21 -1
  209. package/src/daemon/conversation.ts +69 -30
  210. package/src/daemon/first-greeting.ts +35 -0
  211. package/src/daemon/handlers/config-embeddings.ts +156 -0
  212. package/src/daemon/handlers/config-model.ts +62 -26
  213. package/src/daemon/handlers/conversations.ts +0 -23
  214. package/src/daemon/handlers/identity.ts +12 -1
  215. package/src/daemon/handlers/recording.ts +26 -21
  216. package/src/daemon/host-cu-proxy.ts +2 -2
  217. package/src/daemon/lifecycle.ts +115 -65
  218. package/src/daemon/message-protocol.ts +3 -0
  219. package/src/daemon/message-types/conversations.ts +18 -0
  220. package/src/daemon/message-types/messages.ts +1 -0
  221. package/src/daemon/message-types/shared.ts +2 -0
  222. package/src/daemon/message-types/surfaces.ts +2 -0
  223. package/src/daemon/message-types/upgrades.ts +23 -0
  224. package/src/daemon/server.ts +83 -12
  225. package/src/daemon/shutdown-handlers.ts +8 -5
  226. package/src/daemon/startup-error.ts +9 -0
  227. package/src/daemon/tool-side-effects.ts +11 -28
  228. package/src/events/tool-permission-telemetry-listener.ts +1 -3
  229. package/src/followups/followup-store.ts +47 -1
  230. package/src/instrument.ts +0 -4
  231. package/src/media/app-icon-generator.ts +2 -2
  232. package/src/memory/app-git-service.ts +28 -16
  233. package/src/memory/app-store.ts +230 -41
  234. package/src/memory/archive-store.ts +400 -0
  235. package/src/memory/attachments-store.ts +558 -130
  236. package/src/memory/brief-formatting.ts +33 -0
  237. package/src/memory/brief-open-loops.ts +266 -0
  238. package/src/memory/brief-time.ts +161 -0
  239. package/src/memory/brief.ts +75 -0
  240. package/src/memory/conversation-attention-store.ts +70 -0
  241. package/src/memory/conversation-crud.ts +591 -8
  242. package/src/memory/conversation-directories.ts +125 -0
  243. package/src/memory/conversation-disk-view.ts +390 -0
  244. package/src/memory/conversation-key-store.ts +17 -5
  245. package/src/memory/conversation-queries.ts +5 -1
  246. package/src/memory/conversation-title-service.ts +21 -49
  247. package/src/memory/db-init.ts +40 -0
  248. package/src/memory/embedding-backend.ts +42 -53
  249. package/src/memory/embedding-gemini.test.ts +4 -4
  250. package/src/memory/embedding-local.ts +1 -3
  251. package/src/memory/embedding-ollama.ts +1 -3
  252. package/src/memory/embedding-openai.ts +1 -3
  253. package/src/memory/indexer.ts +114 -21
  254. package/src/memory/items-extractor.ts +42 -13
  255. package/src/memory/job-handlers/conversation-starters.ts +6 -1
  256. package/src/memory/job-handlers/embedding.test.ts +2 -4
  257. package/src/memory/job-handlers/embedding.ts +83 -0
  258. package/src/memory/job-utils.ts +1 -1
  259. package/src/memory/jobs-store.ts +6 -0
  260. package/src/memory/jobs-worker.ts +12 -0
  261. package/src/memory/llm-request-log-store.ts +100 -1
  262. package/src/memory/migrations/102-alter-table-columns.ts +5 -0
  263. package/src/memory/migrations/146-schedule-oneshot-routing.ts +3 -3
  264. package/src/memory/migrations/147-migrate-reminders-to-schedules.ts +66 -70
  265. package/src/memory/migrations/148-drop-reminders-table.ts +5 -9
  266. package/src/memory/migrations/160-drop-loopback-port-column.ts +1 -3
  267. package/src/memory/migrations/174-rename-thread-starters-table.ts +0 -7
  268. package/src/memory/migrations/178-oauth-providers-managed-service-config-key.ts +15 -0
  269. package/src/memory/migrations/179-llm-request-log-message-id.ts +16 -0
  270. package/src/memory/migrations/180-backfill-inline-attachments-to-disk.ts +66 -0
  271. package/src/memory/migrations/181-rename-thread-starters-checkpoints.ts +46 -0
  272. package/src/memory/migrations/182-oauth-providers-display-metadata.ts +20 -0
  273. package/src/memory/migrations/183-add-conversation-fork-lineage.ts +22 -0
  274. package/src/memory/migrations/184-llm-request-log-provider.ts +12 -0
  275. package/src/memory/migrations/185-memory-brief-state.ts +52 -0
  276. package/src/memory/migrations/186-memory-archive.ts +109 -0
  277. package/src/memory/migrations/187-memory-reducer-checkpoints.ts +19 -0
  278. package/src/memory/migrations/index.ts +10 -0
  279. package/src/memory/migrations/registry.ts +13 -0
  280. package/src/memory/qdrant-client.ts +23 -4
  281. package/src/memory/reducer-store.ts +271 -0
  282. package/src/memory/reducer-types.ts +99 -0
  283. package/src/memory/reducer.ts +453 -0
  284. package/src/memory/retriever.test.ts +601 -2
  285. package/src/memory/retriever.ts +85 -9
  286. package/src/memory/schema/conversations.ts +9 -0
  287. package/src/memory/schema/index.ts +2 -0
  288. package/src/memory/schema/infrastructure.ts +13 -7
  289. package/src/memory/schema/memory-archive.ts +121 -0
  290. package/src/memory/schema/memory-brief.ts +55 -0
  291. package/src/memory/schema/oauth.ts +6 -0
  292. package/src/memory/search/semantic.ts +17 -4
  293. package/src/messaging/providers/gmail/mime-builder.ts +3 -1
  294. package/src/notifications/copy-composer.ts +26 -0
  295. package/src/notifications/decision-engine.ts +14 -1
  296. package/src/notifications/emit-signal.ts +1 -1
  297. package/src/notifications/signal.ts +36 -0
  298. package/src/oauth/byo-connection.test.ts +1 -45
  299. package/src/oauth/byo-connection.ts +2 -8
  300. package/src/oauth/connect-orchestrator.ts +15 -11
  301. package/src/oauth/connection-resolver.test.ts +191 -0
  302. package/src/oauth/connection-resolver.ts +66 -38
  303. package/src/oauth/connection.ts +0 -1
  304. package/src/oauth/oauth-store.ts +99 -47
  305. package/src/oauth/platform-connection.test.ts +0 -1
  306. package/src/oauth/platform-connection.ts +11 -3
  307. package/src/oauth/seed-providers.ts +78 -3
  308. package/src/oauth/token-persistence.ts +16 -10
  309. package/src/permissions/checker.ts +160 -14
  310. package/src/permissions/defaults.ts +14 -0
  311. package/src/prompts/templates/BOOTSTRAP.md +2 -0
  312. package/src/providers/anthropic/client.ts +8 -1
  313. package/src/providers/failover.ts +4 -1
  314. package/src/providers/gemini/client.ts +50 -0
  315. package/src/providers/model-catalog.ts +92 -0
  316. package/src/providers/model-intents.ts +29 -20
  317. package/src/providers/openai/client.ts +49 -0
  318. package/src/providers/types.ts +2 -0
  319. package/src/runtime/access-request-helper.ts +16 -7
  320. package/src/runtime/auth/credential-service.ts +3 -1
  321. package/src/runtime/auth/route-policy.ts +14 -1
  322. package/src/runtime/btw-sidechain.ts +101 -0
  323. package/src/runtime/channel-reply-delivery.ts +17 -1
  324. package/src/runtime/http-router.ts +3 -1
  325. package/src/runtime/http-server.ts +196 -141
  326. package/src/runtime/http-types.ts +1 -0
  327. package/src/runtime/migrations/vbundle-builder.ts +5 -1
  328. package/src/runtime/routes/access-request-decision.ts +41 -0
  329. package/src/runtime/routes/app-management-routes.ts +6 -3
  330. package/src/runtime/routes/app-routes.ts +7 -3
  331. package/src/runtime/routes/approval-routes.ts +1 -0
  332. package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +34 -2
  333. package/src/runtime/routes/attachment-routes.ts +45 -15
  334. package/src/runtime/routes/btw-routes.ts +21 -61
  335. package/src/runtime/routes/conversation-management-routes.ts +74 -0
  336. package/src/runtime/routes/conversation-query-routes.ts +187 -10
  337. package/src/runtime/routes/conversation-routes.ts +269 -28
  338. package/src/runtime/routes/conversation-starter-routes.ts +9 -11
  339. package/src/runtime/routes/diagnostics-routes.ts +1 -0
  340. package/src/runtime/routes/identity-routes.ts +2 -35
  341. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +2 -2
  342. package/src/runtime/routes/llm-context-normalization.ts +1212 -0
  343. package/src/runtime/routes/log-export-routes.ts +3 -0
  344. package/src/runtime/routes/memory-item-routes.test.ts +34 -0
  345. package/src/runtime/routes/memory-item-routes.ts +94 -5
  346. package/src/runtime/routes/migration-routes.ts +4 -1
  347. package/src/runtime/routes/oauth-apps.ts +291 -0
  348. package/src/runtime/routes/secret-routes.ts +30 -1
  349. package/src/runtime/routes/settings-routes.ts +14 -0
  350. package/src/runtime/routes/surface-action-routes.ts +68 -1
  351. package/src/runtime/routes/trace-event-routes.ts +4 -1
  352. package/src/schedule/schedule-store.ts +30 -21
  353. package/src/security/secure-keys.ts +21 -0
  354. package/src/signals/bash.ts +1 -1
  355. package/src/skills/inline-command-expansions.ts +204 -0
  356. package/src/skills/inline-command-render.ts +127 -0
  357. package/src/skills/inline-command-runner.ts +242 -0
  358. package/src/skills/transitive-version-hash.ts +88 -0
  359. package/src/swarm/backend-claude-code.ts +3 -6
  360. package/src/tasks/task-store.ts +43 -1
  361. package/src/telemetry/usage-telemetry-reporter.test.ts +3 -2
  362. package/src/telemetry/usage-telemetry-reporter.ts +3 -1
  363. package/src/tools/AGENTS.md +6 -10
  364. package/src/tools/apps/executors.ts +17 -232
  365. package/src/tools/claude-code/claude-code.ts +2 -3
  366. package/src/tools/credentials/vault.ts +7 -12
  367. package/src/tools/host-filesystem/read.ts +13 -10
  368. package/src/tools/network/__tests__/web-search.test.ts +4 -2
  369. package/src/tools/permission-checker.ts +8 -1
  370. package/src/tools/schedule/list.ts +2 -7
  371. package/src/tools/schema-transforms.ts +5 -0
  372. package/src/tools/shared/filesystem/format-diff.ts +2 -7
  373. package/src/tools/skills/execute.ts +1 -1
  374. package/src/tools/skills/load.ts +140 -6
  375. package/src/tools/tool-manifest.ts +0 -6
  376. package/src/tools/ui-surface/definitions.ts +2 -2
  377. package/src/util/device-id.ts +28 -5
  378. package/src/util/platform.ts +24 -0
  379. package/src/util/pricing.ts +1 -0
  380. package/src/util/retry.ts +1 -3
  381. package/src/workspace/migrations/003-seed-device-id.ts +3 -4
  382. package/src/workspace/migrations/006-services-config.ts +5 -0
  383. package/src/workspace/migrations/008-voice-timeout-and-max-steps.ts +12 -0
  384. package/src/workspace/migrations/009-backfill-conversation-disk-view.ts +10 -0
  385. package/src/workspace/migrations/010-app-dir-rename.ts +223 -0
  386. package/src/workspace/migrations/{002-backfill-installation-id.ts → 011-backfill-installation-id.ts} +24 -13
  387. package/src/workspace/migrations/012-rename-conversation-disk-view-dirs.ts +64 -0
  388. package/src/workspace/migrations/013-repair-conversation-disk-view.ts +11 -0
  389. package/src/workspace/migrations/rebuild-conversation-disk-view.ts +186 -0
  390. package/src/workspace/migrations/registry.ts +11 -1
  391. package/src/workspace/top-level-renderer.ts +12 -0
  392. package/src/__tests__/asset-materialize-tool.test.ts +0 -523
  393. package/src/__tests__/asset-search-tool.test.ts +0 -536
  394. package/src/__tests__/fixtures/media-reuse-fixtures.ts +0 -56
  395. package/src/__tests__/media-reuse-story.e2e.test.ts +0 -762
  396. package/src/__tests__/media-visibility-policy.test.ts +0 -190
  397. package/src/config/bundled-skills/app-builder/tools/app-file-edit.ts +0 -14
  398. package/src/config/bundled-skills/app-builder/tools/app-file-list.ts +0 -13
  399. package/src/config/bundled-skills/app-builder/tools/app-file-read.ts +0 -21
  400. package/src/config/bundled-skills/app-builder/tools/app-file-write.ts +0 -14
  401. package/src/config/bundled-skills/app-builder/tools/app-list.ts +0 -13
  402. package/src/config/bundled-skills/app-builder/tools/app-update.ts +0 -23
  403. package/src/daemon/media-visibility-policy.ts +0 -59
  404. package/src/tools/assets/materialize.ts +0 -248
  405. package/src/tools/assets/search.ts +0 -400
@@ -27,6 +27,9 @@ mock.module("../providers/registry.js", () => ({
27
27
  mock.module("../config/loader.js", () => ({
28
28
  getConfig: () => ({
29
29
  ui: {},
30
+ daemon: {
31
+ titleGenerationMaxTokens: 30,
32
+ },
30
33
 
31
34
  provider: "mock-provider",
32
35
  maxTokens: 4096,
@@ -174,6 +177,10 @@ mock.module("../memory/conversation-queries.js", () => ({
174
177
  listConversations: () => [],
175
178
  }));
176
179
 
180
+ mock.module("../memory/archive-store.js", () => ({
181
+ insertCompactionEpisode: () => {},
182
+ }));
183
+
177
184
  mock.module("../memory/retriever.js", () => ({
178
185
  buildMemoryRecall: async () => ({
179
186
  enabled: false,
@@ -36,6 +36,8 @@ mock.module("../util/logger.js", () => ({
36
36
 
37
37
  mock.module("../util/platform.js", () => ({
38
38
  getDataDir: () => "/tmp",
39
+ getWorkspaceDir: () => "/tmp/workspace",
40
+ getConversationsDir: () => "/tmp/workspace/conversations",
39
41
  }));
40
42
 
41
43
  mock.module("../memory/guardian-action-store.js", () => ({
@@ -156,14 +158,43 @@ mock.module("../memory/conversation-queries.js", () => ({
156
158
  }));
157
159
 
158
160
  let linkAttachmentShouldThrow = false;
161
+ let mockAttachmentIdCounter = 0;
159
162
 
160
163
  mock.module("../memory/attachments-store.js", () => ({
164
+ AttachmentUploadError: class AttachmentUploadError extends Error {},
161
165
  uploadAttachment: () => ({ id: `att-${Date.now()}` }),
162
166
  linkAttachmentToMessage: () => {
163
167
  if (linkAttachmentShouldThrow) {
164
168
  throw new Error("Simulated linkAttachmentToMessage failure");
165
169
  }
166
170
  },
171
+ attachInlineAttachmentToMessage: (
172
+ _messageId: string,
173
+ _position: number,
174
+ filename: string,
175
+ mimeType: string,
176
+ dataBase64: string,
177
+ ) => {
178
+ if (linkAttachmentShouldThrow) {
179
+ throw new Error("Simulated linkAttachmentToMessage failure");
180
+ }
181
+
182
+ return {
183
+ id: `att-inline-${++mockAttachmentIdCounter}`,
184
+ originalFilename: filename,
185
+ mimeType,
186
+ sizeBytes: Buffer.from(dataBase64, "base64").byteLength,
187
+ kind: mimeType.startsWith("image/")
188
+ ? "image"
189
+ : mimeType.startsWith("video/")
190
+ ? "video"
191
+ : "file",
192
+ thumbnailBase64: null,
193
+ createdAt: Date.now(),
194
+ };
195
+ },
196
+ getFilePathForAttachment: () => null,
197
+ setAttachmentThumbnail: () => {},
167
198
  }));
168
199
 
169
200
  mock.module("../memory/retriever.js", () => ({
@@ -1419,14 +1450,18 @@ describe("Conversation host attachment directives", () => {
1419
1450
  ),
1420
1451
  ).toBe(true);
1421
1452
 
1453
+ // Attachment warnings are surfaced on the completion payload instead of
1454
+ // being emitted as late assistant_text_delta events.
1422
1455
  const warningDelta = events.find(
1423
1456
  (e) =>
1424
1457
  e.type === "assistant_text_delta" &&
1425
1458
  e.text.includes("Attachment warning:"),
1426
1459
  );
1427
- expect(warningDelta).toBeDefined();
1460
+ expect(warningDelta).toBeUndefined();
1428
1461
  const completion = events.find((e) => e.type === "message_complete");
1429
1462
  expect(completion).toBeDefined();
1463
+ expect((completion as { attachmentWarnings?: string[] }).attachmentWarnings)
1464
+ .toEqual(expect.arrayContaining([expect.stringContaining("access denied by user")]));
1430
1465
  } finally {
1431
1466
  rmSync(hostPath, { force: true });
1432
1467
  }
@@ -0,0 +1,439 @@
1
+ import {
2
+ existsSync,
3
+ mkdirSync,
4
+ mkdtempSync,
5
+ readFileSync,
6
+ realpathSync,
7
+ rmSync,
8
+ } from "node:fs";
9
+ import { tmpdir } from "node:os";
10
+ import { join } from "node:path";
11
+ import { afterAll, beforeEach, describe, expect, mock, test } from "bun:test";
12
+
13
+ import { createAssistantMessage } from "../agent/message-types.js";
14
+ import type { Conversation } from "../daemon/conversation.js";
15
+ import { persistUserMessage } from "../daemon/conversation-messaging.js";
16
+ import {
17
+ addMessage,
18
+ getConversation,
19
+ provenanceFromTrustContext,
20
+ } from "../memory/conversation-crud.js";
21
+ import {
22
+ getConversationDirPath,
23
+ syncMessageToDisk,
24
+ } from "../memory/conversation-disk-view.js";
25
+ import {
26
+ getConversationByKey,
27
+ getOrCreateConversation as getOrCreateConversationMapping,
28
+ } from "../memory/conversation-key-store.js";
29
+ import { getDb, initializeDb, resetDb } from "../memory/db.js";
30
+ import { AssistantEventHub } from "../runtime/assistant-event-hub.js";
31
+ import type { AuthContext } from "../runtime/auth/types.js";
32
+ import { handleSendMessage } from "../runtime/routes/conversation-routes.js";
33
+
34
+ const testDir = realpathSync(
35
+ mkdtempSync(join(tmpdir(), "conversation-routes-disk-view-test-")),
36
+ );
37
+ const workspaceDir = join(testDir, "workspace");
38
+ const conversationsDir = join(workspaceDir, "conversations");
39
+ mkdirSync(conversationsDir, { recursive: true });
40
+
41
+ mock.module("../util/platform.js", () => ({
42
+ getRootDir: () => testDir,
43
+ getDataDir: () => join(testDir, "data"),
44
+ getWorkspaceDir: () => workspaceDir,
45
+ getConversationsDir: () => conversationsDir,
46
+ isMacOS: () => process.platform === "darwin",
47
+ isLinux: () => process.platform === "linux",
48
+ isWindows: () => process.platform === "win32",
49
+ getPidPath: () => join(testDir, "test.pid"),
50
+ getDbPath: () => join(testDir, "test.db"),
51
+ getLogPath: () => join(testDir, "test.log"),
52
+ ensureDataDir: () => {},
53
+ }));
54
+
55
+ mock.module("../util/logger.js", () => ({
56
+ getLogger: () =>
57
+ new Proxy({} as Record<string, unknown>, {
58
+ get: () => () => {},
59
+ }),
60
+ }));
61
+
62
+ mock.module("../config/loader.js", () => ({
63
+ getConfig: () => ({
64
+ ui: {},
65
+ model: "test",
66
+ provider: "test",
67
+ memory: { enabled: false },
68
+ rateLimit: { maxRequestsPerMinute: 0 },
69
+ secretDetection: { enabled: false },
70
+ contextWindow: { maxInputTokens: 200000 },
71
+ services: {
72
+ inference: {
73
+ mode: "your-own",
74
+ provider: "anthropic",
75
+ model: "claude-opus-4-6",
76
+ },
77
+ "image-generation": {
78
+ mode: "your-own",
79
+ provider: "gemini",
80
+ model: "gemini-3.1-flash-image-preview",
81
+ },
82
+ "web-search": { mode: "your-own", provider: "inference-provider-native" },
83
+ },
84
+ }),
85
+ }));
86
+
87
+ initializeDb();
88
+
89
+ const conversationInstances = new Map<string, Conversation>();
90
+
91
+ const authContext: AuthContext = {
92
+ subject: "svc_gateway:self",
93
+ principalType: "svc_gateway",
94
+ assistantId: "self",
95
+ scopeProfile: "gateway_service_v1",
96
+ scopes: new Set([
97
+ "chat.read",
98
+ "chat.write",
99
+ "approval.read",
100
+ "approval.write",
101
+ "settings.read",
102
+ "settings.write",
103
+ "attachments.read",
104
+ "attachments.write",
105
+ "calls.read",
106
+ "calls.write",
107
+ "feature_flags.read",
108
+ "feature_flags.write",
109
+ ]),
110
+ policyEpoch: 1,
111
+ };
112
+
113
+ function resetTables(): void {
114
+ const db = getDb();
115
+ db.run("DELETE FROM messages");
116
+ db.run("DELETE FROM conversations");
117
+ db.run("DELETE FROM conversation_keys");
118
+ }
119
+
120
+ function resetConversationsDir(): void {
121
+ rmSync(conversationsDir, { recursive: true, force: true });
122
+ mkdirSync(conversationsDir, { recursive: true });
123
+ }
124
+
125
+ function createFakeConversation(conversationId: string): Conversation {
126
+ const conversation = {
127
+ conversationId,
128
+ processing: false,
129
+ currentRequestId: undefined as string | undefined,
130
+ abortController: null as AbortController | null,
131
+ trustContext: undefined as unknown,
132
+ turnChannelContext: null as
133
+ | {
134
+ userMessageChannel: string;
135
+ assistantMessageChannel: string;
136
+ }
137
+ | null,
138
+ turnInterfaceContext: null as
139
+ | {
140
+ userMessageInterface: string;
141
+ assistantMessageInterface: string;
142
+ }
143
+ | null,
144
+ messages: [] as Array<unknown>,
145
+ hostBashProxy: undefined as unknown,
146
+ hostFileProxy: undefined as unknown,
147
+ hostCuProxy: undefined as unknown,
148
+ usageStats: { inputTokens: 0, outputTokens: 0, estimatedCost: 0 },
149
+ memoryPolicy: {
150
+ scopeId: "default",
151
+ includeDefaultFallback: false,
152
+ strictSideEffects: false,
153
+ },
154
+ isProcessing(this: { processing: boolean }) {
155
+ return this.processing;
156
+ },
157
+ setChannelCapabilities: () => {},
158
+ setAssistantId: () => {},
159
+ setTrustContext(this: { trustContext: unknown }, ctx: unknown) {
160
+ this.trustContext = ctx;
161
+ },
162
+ setAuthContext: () => {},
163
+ setCommandIntent: () => {},
164
+ setTurnChannelContext(
165
+ this: {
166
+ turnChannelContext: {
167
+ userMessageChannel: string;
168
+ assistantMessageChannel: string;
169
+ } | null;
170
+ },
171
+ ctx: { userMessageChannel: string; assistantMessageChannel: string },
172
+ ) {
173
+ this.turnChannelContext = ctx;
174
+ },
175
+ getTurnChannelContext(this: {
176
+ turnChannelContext: {
177
+ userMessageChannel: string;
178
+ assistantMessageChannel: string;
179
+ } | null;
180
+ }) {
181
+ return this.turnChannelContext;
182
+ },
183
+ setTurnInterfaceContext(
184
+ this: {
185
+ turnInterfaceContext: {
186
+ userMessageInterface: string;
187
+ assistantMessageInterface: string;
188
+ } | null;
189
+ },
190
+ ctx: {
191
+ userMessageInterface: string;
192
+ assistantMessageInterface: string;
193
+ },
194
+ ) {
195
+ this.turnInterfaceContext = ctx;
196
+ },
197
+ getTurnInterfaceContext(this: {
198
+ turnInterfaceContext: {
199
+ userMessageInterface: string;
200
+ assistantMessageInterface: string;
201
+ } | null;
202
+ }) {
203
+ return this.turnInterfaceContext;
204
+ },
205
+ ensureActorScopedHistory: async () => {},
206
+ updateClient: () => {},
207
+ setHostBashProxy(this: { hostBashProxy: unknown }, proxy: unknown) {
208
+ this.hostBashProxy = proxy;
209
+ },
210
+ setHostFileProxy(this: { hostFileProxy: unknown }, proxy: unknown) {
211
+ this.hostFileProxy = proxy;
212
+ },
213
+ setHostCuProxy(this: { hostCuProxy: unknown }, proxy: unknown) {
214
+ this.hostCuProxy = proxy;
215
+ },
216
+ addPreactivatedSkillId: () => {},
217
+ hasAnyPendingConfirmation: () => false,
218
+ hasPendingConfirmation: () => false,
219
+ denyAllPendingConfirmations: () => {},
220
+ emitConfirmationStateChanged: () => {},
221
+ emitActivityState: () => {},
222
+ enqueueMessage: () => ({ queued: true, requestId: crypto.randomUUID() }),
223
+ getQueueDepth: () => 0,
224
+ handleConfirmationResponse: () => {},
225
+ handleSecretResponse: () => {},
226
+ getMessages(this: { messages: Array<unknown> }) {
227
+ return this.messages as never[];
228
+ },
229
+ persistUserMessage(
230
+ this: Conversation,
231
+ content: string,
232
+ attachments: Array<{
233
+ id: string;
234
+ filename: string;
235
+ mimeType: string;
236
+ data: string;
237
+ extractedText?: string;
238
+ filePath?: string;
239
+ }>,
240
+ requestId?: string,
241
+ metadata?: Record<string, unknown>,
242
+ displayContent?: string,
243
+ ): Promise<string> {
244
+ return persistUserMessage(
245
+ this as Parameters<typeof persistUserMessage>[0],
246
+ content,
247
+ attachments,
248
+ requestId,
249
+ metadata,
250
+ displayContent,
251
+ );
252
+ },
253
+ async runAgentLoop(
254
+ this: {
255
+ conversationId: string;
256
+ turnChannelContext: {
257
+ userMessageChannel: string;
258
+ assistantMessageChannel: string;
259
+ } | null;
260
+ turnInterfaceContext: {
261
+ userMessageInterface: string;
262
+ assistantMessageInterface: string;
263
+ } | null;
264
+ trustContext: unknown;
265
+ messages: Array<unknown>;
266
+ processing: boolean;
267
+ abortController: AbortController | null;
268
+ currentRequestId?: string;
269
+ },
270
+ _content: string,
271
+ _userMessageId: string,
272
+ onEvent: (msg: Record<string, unknown>) => void,
273
+ ): Promise<void> {
274
+ const assistantText = "Synthetic assistant reply";
275
+ const assistantMessage = createAssistantMessage(assistantText);
276
+ const assistantMetadata = {
277
+ ...provenanceFromTrustContext(this.trustContext as never),
278
+ ...(this.turnChannelContext
279
+ ? {
280
+ userMessageChannel: this.turnChannelContext.userMessageChannel,
281
+ assistantMessageChannel:
282
+ this.turnChannelContext.assistantMessageChannel,
283
+ }
284
+ : {}),
285
+ ...(this.turnInterfaceContext
286
+ ? {
287
+ userMessageInterface:
288
+ this.turnInterfaceContext.userMessageInterface,
289
+ assistantMessageInterface:
290
+ this.turnInterfaceContext.assistantMessageInterface,
291
+ }
292
+ : {}),
293
+ };
294
+
295
+ const persistedAssistant = await addMessage(
296
+ this.conversationId,
297
+ "assistant",
298
+ JSON.stringify(assistantMessage.content),
299
+ assistantMetadata,
300
+ );
301
+ this.messages.push(assistantMessage);
302
+
303
+ const conversationRow = getConversation(this.conversationId);
304
+ if (conversationRow) {
305
+ syncMessageToDisk(
306
+ this.conversationId,
307
+ persistedAssistant.id,
308
+ conversationRow.createdAt,
309
+ );
310
+ }
311
+
312
+ onEvent({
313
+ type: "assistant_text_delta",
314
+ text: assistantText,
315
+ conversationId: this.conversationId,
316
+ });
317
+ onEvent({
318
+ type: "message_complete",
319
+ conversationId: this.conversationId,
320
+ });
321
+
322
+ this.processing = false;
323
+ this.abortController = null;
324
+ this.currentRequestId = undefined;
325
+ },
326
+ };
327
+
328
+ return conversation as unknown as Conversation;
329
+ }
330
+
331
+ function getOrCreateFakeConversation(conversationId: string): Conversation {
332
+ const existing = conversationInstances.get(conversationId);
333
+ if (existing) return existing;
334
+ const created = createFakeConversation(conversationId);
335
+ conversationInstances.set(conversationId, created);
336
+ return created;
337
+ }
338
+
339
+ async function waitFor<T>(
340
+ getter: () => T | undefined,
341
+ timeoutMs = 3000,
342
+ ): Promise<T> {
343
+ const deadline = Date.now() + timeoutMs;
344
+ while (Date.now() < deadline) {
345
+ const value = getter();
346
+ if (value !== undefined) return value;
347
+ await Bun.sleep(20);
348
+ }
349
+ throw new Error("Timed out waiting for expected disk-view output");
350
+ }
351
+
352
+ beforeEach(() => {
353
+ resetTables();
354
+ resetConversationsDir();
355
+ conversationInstances.clear();
356
+ });
357
+
358
+ afterAll(() => {
359
+ resetDb();
360
+ try {
361
+ rmSync(testDir, { recursive: true, force: true });
362
+ } catch {
363
+ /* best effort */
364
+ }
365
+ });
366
+
367
+ describe("conversationKey send path disk-view regression", () => {
368
+ test("first send on a fresh conversationKey creates disk-view dir and writes user+assistant records", async () => {
369
+ const conversationKey = `fresh-conv-key-${crypto.randomUUID()}`;
370
+ const content = "Please persist this first turn.";
371
+
372
+ const response = await handleSendMessage(
373
+ new Request("http://localhost/v1/messages", {
374
+ method: "POST",
375
+ headers: { "Content-Type": "application/json" },
376
+ body: JSON.stringify({
377
+ conversationKey,
378
+ content,
379
+ sourceChannel: "vellum",
380
+ interface: "macos",
381
+ }),
382
+ }),
383
+ {
384
+ sendMessageDeps: {
385
+ getOrCreateConversation: async (conversationId: string) =>
386
+ getOrCreateFakeConversation(conversationId),
387
+ assistantEventHub: new AssistantEventHub(),
388
+ resolveAttachments: () => [],
389
+ },
390
+ },
391
+ authContext,
392
+ );
393
+
394
+ expect(response.status).toBe(202);
395
+ const body = (await response.json()) as {
396
+ accepted: boolean;
397
+ conversationId: string;
398
+ messageId: string;
399
+ };
400
+ expect(body.accepted).toBe(true);
401
+ expect(body.conversationId).toBeDefined();
402
+ expect(body.messageId).toBeDefined();
403
+
404
+ // Verify the real key store mapping is reused after the first send.
405
+ const mapping = getOrCreateConversationMapping(conversationKey);
406
+ expect(mapping.created).toBe(false);
407
+ expect(mapping.conversationId).toBe(body.conversationId);
408
+ expect(getConversationByKey(conversationKey)?.conversationId).toBe(
409
+ body.conversationId,
410
+ );
411
+
412
+ const conversationRow = getConversation(body.conversationId);
413
+ expect(conversationRow).not.toBeNull();
414
+ const conversationDir = getConversationDirPath(
415
+ body.conversationId,
416
+ conversationRow!.createdAt,
417
+ );
418
+ const metaPath = join(conversationDir, "meta.json");
419
+ const messagesPath = join(conversationDir, "messages.jsonl");
420
+
421
+ expect(existsSync(conversationDir)).toBe(true);
422
+ expect(existsSync(metaPath)).toBe(true);
423
+
424
+ const lines = await waitFor(() => {
425
+ if (!existsSync(messagesPath)) return undefined;
426
+ const raw = readFileSync(messagesPath, "utf-8").trim();
427
+ if (!raw) return undefined;
428
+ const parsed = raw
429
+ .split("\n")
430
+ .map((line) => JSON.parse(line) as { role: string; content?: string });
431
+ return parsed.length >= 2 ? parsed : undefined;
432
+ });
433
+
434
+ expect(lines[0]?.role).toBe("user");
435
+ expect(lines[0]?.content).toBe(content);
436
+ expect(lines[1]?.role).toBe("assistant");
437
+ expect(lines[1]?.content).toBe("Synthetic assistant reply");
438
+ });
439
+ });
@@ -216,7 +216,7 @@ describe("handleSendMessage canonical guardian reply interception", () => {
216
216
  expect(runAgentLoop).toHaveBeenCalledTimes(0);
217
217
  });
218
218
 
219
- test("passes undefined pendingRequestIds when no canonical hints are found", async () => {
219
+ test("passes empty pendingRequestIds array when no canonical hints are found", async () => {
220
220
  listPendingByDestinationMock.mockReturnValue([]);
221
221
  listCanonicalMock.mockReturnValue([]);
222
222
  routeGuardianReplyMock.mockResolvedValue({
@@ -279,7 +279,7 @@ describe("handleSendMessage canonical guardian reply interception", () => {
279
279
  expect(routeGuardianReplyMock).toHaveBeenCalledTimes(1);
280
280
  const routerCall = (routeGuardianReplyMock as any).mock
281
281
  .calls[0][0] as Record<string, unknown>;
282
- expect(routerCall.pendingRequestIds).toBeUndefined();
282
+ expect(routerCall.pendingRequestIds).toEqual([]);
283
283
  expect(persistUserMessage).toHaveBeenCalledTimes(1);
284
284
  expect(runAgentLoop).toHaveBeenCalledTimes(1);
285
285
  });
@@ -2,7 +2,7 @@
2
2
  * Tests for slash command interception in the POST /v1/messages handler.
3
3
  *
4
4
  * Validates that:
5
- * - Built-in slash commands (/status, /model, /commands) are intercepted and
5
+ * - Built-in slash commands (/status, /models, /commands) are intercepted and
6
6
  * do NOT trigger the agent loop.
7
7
  * - Regular messages pass through to the agent loop unchanged.
8
8
  */
@@ -122,12 +122,7 @@ mock.module("../daemon/conversation-process.js", () => ({
122
122
  configuredProviders: ["anthropic", "ollama"],
123
123
  }),
124
124
  isModelSlashCommand: (content: string) => {
125
- const trimmed = content.trim();
126
- return (
127
- trimmed === "/model" ||
128
- trimmed === "/models" ||
129
- trimmed.startsWith("/model ")
130
- );
125
+ return content.trim() === "/models";
131
126
  },
132
127
  }));
133
128
 
@@ -1065,9 +1065,24 @@ describe("buildTurnContextBlock (channel-only)", () => {
1065
1065
  },
1066
1066
  undefined,
1067
1067
  );
1068
- expect(block).toBe(
1069
- "<turn_context>\n" + "channel: telegram\n" + "</turn_context>",
1068
+ expect(block).toContain("<turn_context>");
1069
+ expect(block).toContain("channel: telegram");
1070
+ expect(block).toContain("response_discretion:");
1071
+ expect(block).toContain("</turn_context>");
1072
+ });
1073
+
1074
+ test("omits response_discretion for vellum channel", () => {
1075
+ const block = buildTurnContextBlock(
1076
+ {
1077
+ turnContext: {
1078
+ userMessageChannel: "vellum",
1079
+ assistantMessageChannel: "vellum",
1080
+ },
1081
+ conversationOriginChannel: "vellum",
1082
+ },
1083
+ undefined,
1070
1084
  );
1085
+ expect(block).not.toContain("response_discretion:");
1071
1086
  });
1072
1087
 
1073
1088
  test('uses "unknown" when conversationOriginChannel is null', () => {
@@ -1779,14 +1779,9 @@ describe("bundled skill: claude-code", () => {
1779
1779
 
1780
1780
  const APP_BUILDER_TOOL_NAMES = [
1781
1781
  "app_create",
1782
- "app_list",
1783
- "app_query",
1784
- "app_update",
1785
1782
  "app_delete",
1786
- "app_file_list",
1787
- "app_file_read",
1788
- "app_file_edit",
1789
- "app_file_write",
1783
+ "app_generate_icon",
1784
+ "app_refresh",
1790
1785
  ] as const;
1791
1786
 
1792
1787
  describe("bundled skill: app-builder", () => {
@@ -1803,7 +1798,7 @@ describe("bundled skill: app-builder", () => {
1803
1798
  sessionState = new Map<string, string>();
1804
1799
  });
1805
1800
 
1806
- test("app-builder skill activation registers all 9 canonical non-proxy tools in allowedToolNames", () => {
1801
+ test("app-builder skill activation registers all 4 canonical non-proxy tools in allowedToolNames", () => {
1807
1802
  mockCatalog = [
1808
1803
  makeSkill("app-builder", "/path/to/bundled-skills/app-builder"),
1809
1804
  ];
@@ -1862,7 +1857,7 @@ describe("bundled skill: app-builder", () => {
1862
1857
 
1863
1858
  const tools = mockRegisteredTools.get("app-builder");
1864
1859
  expect(tools).toBeDefined();
1865
- expect(tools!.length).toBe(9);
1860
+ expect(tools!.length).toBe(4);
1866
1861
 
1867
1862
  // All tools should have skill origin metadata
1868
1863
  for (const tool of tools!) {