@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
@@ -3207,8 +3207,9 @@ describe("Memory regressions", () => {
3207
3207
  .filter((j) => JSON.parse(j.payload).messageId === "msg-untrusted-gate");
3208
3208
  expect(extractJobs.length).toBe(0);
3209
3209
 
3210
- // enqueuedJobs should reflect: embed jobs + summary (1), no extract (0)
3211
- const expectedJobs = result.indexedSegments + 1; // embed per segment + summary
3210
+ // enqueuedJobs reflects legacy embed_segment + archive embed_chunk per
3211
+ // segment, plus the summary job, with extract_items gated off.
3212
+ const expectedJobs = result.indexedSegments * 2 + 1;
3212
3213
  expect(result.enqueuedJobs).toBe(expectedJobs);
3213
3214
  });
3214
3215
 
@@ -3389,8 +3390,9 @@ describe("Memory regressions", () => {
3389
3390
  .filter((j) => JSON.parse(j.payload).messageId === "msg-unverified-gate");
3390
3391
  expect(extractJobs.length).toBe(0);
3391
3392
 
3392
- // enqueuedJobs should reflect: embed jobs + summary (1), no extract (0)
3393
- const expectedJobs = result.indexedSegments + 1; // embed per segment + summary
3393
+ // enqueuedJobs reflects legacy embed_segment + archive embed_chunk per
3394
+ // segment, plus the summary job, with extract_items gated off.
3395
+ const expectedJobs = result.indexedSegments * 2 + 1;
3394
3396
  expect(result.enqueuedJobs).toBe(expectedJobs);
3395
3397
  });
3396
3398
 
@@ -0,0 +1,281 @@
1
+ import { randomBytes } from "node:crypto";
2
+ import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
6
+
7
+ // ---------------------------------------------------------------------------
8
+ // Mocks — declared before imports that depend on platform/logger
9
+ // ---------------------------------------------------------------------------
10
+
11
+ const TEST_DIR = join(
12
+ tmpdir(),
13
+ `vellum-simplified-mem-test-${randomBytes(4).toString("hex")}`,
14
+ );
15
+ const WORKSPACE_DIR = join(TEST_DIR, "workspace");
16
+ const CONFIG_PATH = join(WORKSPACE_DIR, "config.json");
17
+
18
+ function ensureTestDir(): void {
19
+ const dirs = [
20
+ TEST_DIR,
21
+ WORKSPACE_DIR,
22
+ join(TEST_DIR, "data"),
23
+ join(TEST_DIR, "memory"),
24
+ join(TEST_DIR, "memory", "knowledge"),
25
+ join(TEST_DIR, "logs"),
26
+ ];
27
+ for (const dir of dirs) {
28
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
29
+ }
30
+ }
31
+
32
+ function makeLoggerStub(): Record<string, unknown> {
33
+ const stub: Record<string, unknown> = {};
34
+ for (const m of [
35
+ "info",
36
+ "warn",
37
+ "error",
38
+ "debug",
39
+ "trace",
40
+ "fatal",
41
+ "silent",
42
+ "child",
43
+ ]) {
44
+ stub[m] = m === "child" ? () => makeLoggerStub() : () => {};
45
+ }
46
+ return stub;
47
+ }
48
+
49
+ mock.module("../util/logger.js", () => ({
50
+ getLogger: () => makeLoggerStub(),
51
+ }));
52
+
53
+ mock.module("../util/platform.js", () => ({
54
+ getRootDir: () => TEST_DIR,
55
+ getWorkspaceDir: () => WORKSPACE_DIR,
56
+ getWorkspaceConfigPath: () => CONFIG_PATH,
57
+ getDataDir: () => join(TEST_DIR, "data"),
58
+ getLogPath: () => join(TEST_DIR, "logs", "vellum.log"),
59
+ ensureDataDir: () => ensureTestDir(),
60
+ isMacOS: () => false,
61
+ isLinux: () => false,
62
+ isWindows: () => false,
63
+ }));
64
+
65
+ import { invalidateConfigCache, loadConfig } from "../config/loader.js";
66
+ import { AssistantConfigSchema } from "../config/schema.js";
67
+ import { MemorySimplifiedConfigSchema } from "../config/schemas/memory-simplified.js";
68
+ import { _setStorePath } from "../security/encrypted-store.js";
69
+
70
+ // ---------------------------------------------------------------------------
71
+ // Helpers
72
+ // ---------------------------------------------------------------------------
73
+
74
+ function writeConfig(obj: unknown): void {
75
+ writeFileSync(CONFIG_PATH, JSON.stringify(obj));
76
+ }
77
+
78
+ // ---------------------------------------------------------------------------
79
+ // Tests: MemorySimplifiedConfigSchema (unit)
80
+ // ---------------------------------------------------------------------------
81
+
82
+ describe("MemorySimplifiedConfigSchema", () => {
83
+ test("parses empty object with all defaults", () => {
84
+ const result = MemorySimplifiedConfigSchema.parse({});
85
+ expect(result).toEqual({
86
+ enabled: false,
87
+ brief: { maxTokens: 4000 },
88
+ reducer: { idleDelayMs: 30_000, switchWaitMs: 5_000 },
89
+ archiveRecall: { maxSnippets: 10 },
90
+ });
91
+ });
92
+
93
+ test("accepts explicit enabled=true", () => {
94
+ const result = MemorySimplifiedConfigSchema.parse({ enabled: true });
95
+ expect(result.enabled).toBe(true);
96
+ });
97
+
98
+ test("accepts custom brief.maxTokens", () => {
99
+ const result = MemorySimplifiedConfigSchema.parse({
100
+ brief: { maxTokens: 8000 },
101
+ });
102
+ expect(result.brief.maxTokens).toBe(8000);
103
+ });
104
+
105
+ test("accepts custom reducer values", () => {
106
+ const result = MemorySimplifiedConfigSchema.parse({
107
+ reducer: { idleDelayMs: 60_000, switchWaitMs: 10_000 },
108
+ });
109
+ expect(result.reducer.idleDelayMs).toBe(60_000);
110
+ expect(result.reducer.switchWaitMs).toBe(10_000);
111
+ });
112
+
113
+ test("accepts custom archiveRecall.maxSnippets", () => {
114
+ const result = MemorySimplifiedConfigSchema.parse({
115
+ archiveRecall: { maxSnippets: 20 },
116
+ });
117
+ expect(result.archiveRecall.maxSnippets).toBe(20);
118
+ });
119
+
120
+ test("rejects non-boolean enabled", () => {
121
+ const result = MemorySimplifiedConfigSchema.safeParse({
122
+ enabled: "yes",
123
+ });
124
+ expect(result.success).toBe(false);
125
+ });
126
+
127
+ test("rejects non-positive brief.maxTokens", () => {
128
+ const result = MemorySimplifiedConfigSchema.safeParse({
129
+ brief: { maxTokens: 0 },
130
+ });
131
+ expect(result.success).toBe(false);
132
+ });
133
+
134
+ test("rejects non-integer brief.maxTokens", () => {
135
+ const result = MemorySimplifiedConfigSchema.safeParse({
136
+ brief: { maxTokens: 3.5 },
137
+ });
138
+ expect(result.success).toBe(false);
139
+ });
140
+
141
+ test("rejects non-positive reducer.idleDelayMs", () => {
142
+ const result = MemorySimplifiedConfigSchema.safeParse({
143
+ reducer: { idleDelayMs: 0 },
144
+ });
145
+ expect(result.success).toBe(false);
146
+ });
147
+
148
+ test("rejects non-positive reducer.switchWaitMs", () => {
149
+ const result = MemorySimplifiedConfigSchema.safeParse({
150
+ reducer: { switchWaitMs: -1 },
151
+ });
152
+ expect(result.success).toBe(false);
153
+ });
154
+
155
+ test("rejects non-positive archiveRecall.maxSnippets", () => {
156
+ const result = MemorySimplifiedConfigSchema.safeParse({
157
+ archiveRecall: { maxSnippets: 0 },
158
+ });
159
+ expect(result.success).toBe(false);
160
+ });
161
+
162
+ test("rejects non-integer archiveRecall.maxSnippets", () => {
163
+ const result = MemorySimplifiedConfigSchema.safeParse({
164
+ archiveRecall: { maxSnippets: 2.5 },
165
+ });
166
+ expect(result.success).toBe(false);
167
+ });
168
+ });
169
+
170
+ // ---------------------------------------------------------------------------
171
+ // Tests: Wired into AssistantConfigSchema
172
+ // ---------------------------------------------------------------------------
173
+
174
+ describe("AssistantConfigSchema memory.simplified", () => {
175
+ test("empty config exposes memory.simplified with defaults", () => {
176
+ const result = AssistantConfigSchema.parse({});
177
+ expect(result.memory.simplified).toEqual({
178
+ enabled: false,
179
+ brief: { maxTokens: 4000 },
180
+ reducer: { idleDelayMs: 30_000, switchWaitMs: 5_000 },
181
+ archiveRecall: { maxSnippets: 10 },
182
+ });
183
+ });
184
+
185
+ test("memory.simplified does not disturb legacy memory config", () => {
186
+ const result = AssistantConfigSchema.parse({});
187
+ // Legacy fields still present with their defaults
188
+ expect(result.memory.enabled).toBe(true);
189
+ expect(result.memory.retrieval).toBeDefined();
190
+ expect(result.memory.jobs).toBeDefined();
191
+ expect(result.memory.cleanup).toBeDefined();
192
+ expect(result.memory.extraction).toBeDefined();
193
+ expect(result.memory.summarization).toBeDefined();
194
+ expect(result.memory.segmentation).toBeDefined();
195
+ expect(result.memory.embeddings).toBeDefined();
196
+ expect(result.memory.qdrant).toBeDefined();
197
+ expect(result.memory.retention).toBeDefined();
198
+ });
199
+
200
+ test("accepts memory.simplified overrides alongside legacy config", () => {
201
+ const result = AssistantConfigSchema.parse({
202
+ memory: {
203
+ enabled: true,
204
+ simplified: {
205
+ enabled: true,
206
+ brief: { maxTokens: 6000 },
207
+ },
208
+ },
209
+ });
210
+ expect(result.memory.enabled).toBe(true);
211
+ expect(result.memory.simplified.enabled).toBe(true);
212
+ expect(result.memory.simplified.brief.maxTokens).toBe(6000);
213
+ // Defaults preserved for unset simplified fields
214
+ expect(result.memory.simplified.reducer.idleDelayMs).toBe(30_000);
215
+ expect(result.memory.simplified.archiveRecall.maxSnippets).toBe(10);
216
+ });
217
+ });
218
+
219
+ // ---------------------------------------------------------------------------
220
+ // Tests: loadConfig integration (empty config file loads cleanly)
221
+ // ---------------------------------------------------------------------------
222
+
223
+ describe("loadConfig with memory.simplified", () => {
224
+ beforeEach(() => {
225
+ ensureTestDir();
226
+ const resetPaths = [
227
+ CONFIG_PATH,
228
+ join(TEST_DIR, "keys.enc"),
229
+ join(TEST_DIR, "data"),
230
+ join(TEST_DIR, "memory"),
231
+ ];
232
+ for (const path of resetPaths) {
233
+ if (existsSync(path)) {
234
+ rmSync(path, { recursive: true, force: true });
235
+ }
236
+ }
237
+ ensureTestDir();
238
+ _setStorePath(join(TEST_DIR, "keys.enc"));
239
+ invalidateConfigCache();
240
+ });
241
+
242
+ afterEach(() => {
243
+ _setStorePath(null);
244
+ invalidateConfigCache();
245
+ });
246
+
247
+ test("empty config file loads cleanly with simplified defaults", () => {
248
+ writeConfig({});
249
+ const config = loadConfig();
250
+ expect(config.memory.simplified).toEqual({
251
+ enabled: false,
252
+ brief: { maxTokens: 4000 },
253
+ reducer: { idleDelayMs: 30_000, switchWaitMs: 5_000 },
254
+ archiveRecall: { maxSnippets: 10 },
255
+ });
256
+ });
257
+
258
+ test("no config file loads cleanly with simplified defaults", () => {
259
+ const config = loadConfig();
260
+ expect(config.memory.simplified).toEqual({
261
+ enabled: false,
262
+ brief: { maxTokens: 4000 },
263
+ reducer: { idleDelayMs: 30_000, switchWaitMs: 5_000 },
264
+ archiveRecall: { maxSnippets: 10 },
265
+ });
266
+ });
267
+
268
+ test("existing memory config with simplified addition loads cleanly", () => {
269
+ writeConfig({
270
+ memory: {
271
+ enabled: true,
272
+ simplified: { enabled: true, brief: { maxTokens: 2000 } },
273
+ },
274
+ });
275
+ const config = loadConfig();
276
+ expect(config.memory.enabled).toBe(true);
277
+ expect(config.memory.simplified.enabled).toBe(true);
278
+ expect(config.memory.simplified.brief.maxTokens).toBe(2000);
279
+ expect(config.memory.simplified.reducer.idleDelayMs).toBe(30_000);
280
+ });
281
+ });
@@ -894,7 +894,10 @@ describe("partial failure scenarios", () => {
894
894
  const blockerWorkspace = join(testDir, "blocker-workspace");
895
895
  mkdirSync(blockerWorkspace, { recursive: true });
896
896
  // Create "data" as a regular file — mkdirSync("data/db") will fail
897
- writeFileSync(join(blockerWorkspace, "data"), "I am a file, not a directory");
897
+ writeFileSync(
898
+ join(blockerWorkspace, "data"),
899
+ "I am a file, not a directory",
900
+ );
898
901
 
899
902
  const resolver = new DefaultPathResolver(undefined, blockerWorkspace);
900
903
  const result = commitImport({
@@ -299,7 +299,9 @@ describe("export data population", () => {
299
299
  const archiveData = new Uint8Array(await res.arrayBuffer());
300
300
  const entries = parseTarEntries(archiveData);
301
301
 
302
- const dbEntry = entries.find((e) => e.name === "workspace/data/db/assistant.db");
302
+ const dbEntry = entries.find(
303
+ (e) => e.name === "workspace/data/db/assistant.db",
304
+ );
303
305
  expect(dbEntry).toBeDefined();
304
306
  expect(dbEntry!.data.length).toBe(SQLITE_HEADER.length);
305
307
  // Verify the exported data matches the test fixture exactly
@@ -671,7 +671,12 @@ describe("commitImport", () => {
671
671
  test("creates parent directories if they do not exist", () => {
672
672
  // Use a workspace that does not exist yet
673
673
  const nonexistentWorkspace = join(testDir, "new-workspace");
674
- const expectedDbPath = join(nonexistentWorkspace, "data", "db", "assistant.db");
674
+ const expectedDbPath = join(
675
+ nonexistentWorkspace,
676
+ "data",
677
+ "db",
678
+ "assistant.db",
679
+ );
675
680
 
676
681
  const dbData = new Uint8Array([0x01, 0x02, 0x03]);
677
682
  const vbundle = createValidVBundle([
@@ -836,7 +841,11 @@ describe("commitImport — workspace clearing", () => {
836
841
  { path: "hooks/new-hook/hook.sh", data: hookData },
837
842
  ]);
838
843
 
839
- const resolver = new DefaultPathResolver(undefined, testDir, externalHooksDir);
844
+ const resolver = new DefaultPathResolver(
845
+ undefined,
846
+ testDir,
847
+ externalHooksDir,
848
+ );
840
849
  const result = commitImport({
841
850
  archiveData: vbundle,
842
851
  pathResolver: resolver,
@@ -847,7 +856,9 @@ describe("commitImport — workspace clearing", () => {
847
856
  if (!result.ok) return;
848
857
 
849
858
  // Hook written to the external hooks dir, not workspace/hooks/
850
- expect(existsSync(join(externalHooksDir, "new-hook", "hook.sh"))).toBe(true);
859
+ expect(existsSync(join(externalHooksDir, "new-hook", "hook.sh"))).toBe(
860
+ true,
861
+ );
851
862
  expect(
852
863
  readFileSync(join(externalHooksDir, "new-hook", "hook.sh"), "utf8"),
853
864
  ).toBe("#!/bin/sh\necho new");
@@ -864,7 +875,10 @@ describe("commitImport — workspace clearing", () => {
864
875
  );
865
876
 
866
877
  const vbundle = createValidVBundle([
867
- { path: "skills/new-skill/SKILL.md", data: new TextEncoder().encode("new") },
878
+ {
879
+ path: "skills/new-skill/SKILL.md",
880
+ data: new TextEncoder().encode("new"),
881
+ },
868
882
  ]);
869
883
 
870
884
  const resolver = new DefaultPathResolver(undefined, testDir);
@@ -743,9 +743,7 @@ describe("DefaultPathResolver", () => {
743
743
  undefined,
744
744
  "/home/user/.vellum/workspace",
745
745
  );
746
- expect(
747
- resolver.resolve("skills/../../../.ssh/authorized_keys"),
748
- ).toBeNull();
746
+ expect(resolver.resolve("skills/../../../.ssh/authorized_keys")).toBeNull();
749
747
  });
750
748
 
751
749
  test("returns null for skills paths when workspaceDir is not provided", () => {
@@ -98,7 +98,9 @@ describe("buildMultipartMime", () => {
98
98
  "base64",
99
99
  ).toString("utf-8");
100
100
 
101
- expect(decoded).toContain("To: victim@example.com Bcc: attacker@example.com");
101
+ expect(decoded).toContain(
102
+ "To: victim@example.com Bcc: attacker@example.com",
103
+ );
102
104
  expect(decoded).toContain("Subject: Fwd: Hello Cc: attacker@example.com");
103
105
  expect(decoded).toContain("Cc: team@example.com X-Injected: yes");
104
106
  expect(decoded).toContain("Bcc: audit@example.com X-Another: value");
@@ -108,5 +110,4 @@ describe("buildMultipartMime", () => {
108
110
  expect(decoded).not.toContain("\r\nBcc: attacker@example.com");
109
111
  expect(decoded).not.toContain("\r\nCc: attacker@example.com");
110
112
  });
111
-
112
113
  });
@@ -624,6 +624,17 @@ describe("access-request-helper unit tests", () => {
624
624
  });
625
625
 
626
626
  test("notifyGuardianOfAccessRequest skips vellum fallback for same-channel-only routing (telegram)", async () => {
627
+ // Set up a telegram guardian binding with the anchor principal so
628
+ // guardianResolutionSource resolves to "source-channel-contact" and
629
+ // sameChannelOnly is true.
630
+ createGuardianBinding({
631
+ channel: "telegram",
632
+ guardianExternalUserId: "guardian-user-456",
633
+ guardianDeliveryChatId: "guardian-chat-456",
634
+ guardianPrincipalId: anchorPrincipalId,
635
+ verifiedVia: "test",
636
+ });
637
+
627
638
  mockEmitResult = {
628
639
  signalId: "sig-no-vellum",
629
640
  deduplicated: false,
@@ -657,7 +668,7 @@ describe("access-request-helper unit tests", () => {
657
668
  (d) => d.destinationChannel === "telegram",
658
669
  );
659
670
 
660
- // Same-channel routing skips vellum delivery entirely no fallback record
671
+ // Guardian IS verified on telegram sameChannelOnly, no vellum fallback
661
672
  expect(vellum).toBeUndefined();
662
673
  expect(telegram).toBeDefined();
663
674
  expect(telegram!.destinationChatId).toBe("guardian-chat-456");
@@ -185,6 +185,58 @@ describe("identity context in notification decision engine", () => {
185
185
  expect(capturedSystemPrompt).not.toContain("</assistant-identity>");
186
186
  });
187
187
 
188
+ test("large identity context is truncated in system prompt", async () => {
189
+ // Create an identity context that exceeds the 2000-char budget
190
+ mockIdentityContext = "A".repeat(3000);
191
+
192
+ configuredProvider = {
193
+ sendMessage: async (
194
+ _messages: unknown,
195
+ _tools: unknown,
196
+ systemPrompt: unknown,
197
+ ) => {
198
+ capturedSystemPrompt = systemPrompt as string;
199
+ return { content: [] };
200
+ },
201
+ };
202
+ extractedToolUse = {
203
+ name: "record_notification_decision",
204
+ input: {
205
+ shouldNotify: true,
206
+ selectedChannels: ["vellum"],
207
+ reasoningSummary: "LLM decision with truncated identity",
208
+ renderedCopy: {
209
+ vellum: {
210
+ title: "Guardian Question",
211
+ body: "What is the gate code?",
212
+ },
213
+ },
214
+ dedupeKey: "identity-truncated-test",
215
+ confidence: 0.9,
216
+ },
217
+ };
218
+
219
+ const signal = makeSignal();
220
+ await evaluateSignal(signal, ["vellum"] as NotificationChannel[]);
221
+
222
+ expect(capturedSystemPrompt).toBeDefined();
223
+ expect(capturedSystemPrompt).toContain("<assistant-identity>");
224
+ // The identity block should exist but should NOT contain the full 3000-char string
225
+ expect(capturedSystemPrompt).not.toContain("A".repeat(3000));
226
+ // It should contain the truncation marker
227
+ expect(capturedSystemPrompt).toContain("…[truncated]");
228
+ // The identity content within the block should be at most 2000 chars
229
+ const identityMatch = capturedSystemPrompt!.match(
230
+ /<assistant-identity>([\s\S]*?)<\/assistant-identity>/,
231
+ );
232
+ expect(identityMatch).toBeTruthy();
233
+ // The identity block includes the instruction text + the truncated context.
234
+ // Verify the raw identity portion is bounded.
235
+ const identityBlock = identityMatch![1];
236
+ expect(identityBlock).toContain("…[truncated]");
237
+ expect(identityBlock).not.toContain("A".repeat(2001));
238
+ });
239
+
188
240
  test("fallback path does not include identity context", async () => {
189
241
  mockIdentityContext = "I am Jarvis, a helpful assistant";
190
242
 
@@ -0,0 +1,103 @@
1
+ import { describe, expect, mock, test } from "bun:test";
2
+
3
+ const mockGetApp = mock((_appId: string) => ({
4
+ id: "app-1",
5
+ providerKey: "google",
6
+ clientId: "client-1",
7
+ }));
8
+
9
+ const mockListConnections = mock(() => [
10
+ {
11
+ id: "conn-1",
12
+ providerKey: "google",
13
+ accountInfo: "{\"email\":\"alice@example.com\"}",
14
+ grantedScopes: '["email","profile"]',
15
+ status: "active",
16
+ hasRefreshToken: 1,
17
+ expiresAt: 1735689600000,
18
+ createdAt: 1735689500000,
19
+ updatedAt: 1735689550000,
20
+ },
21
+ {
22
+ id: "conn-2",
23
+ providerKey: "google",
24
+ accountInfo: null,
25
+ grantedScopes: [],
26
+ status: "active",
27
+ hasRefreshToken: 0,
28
+ expiresAt: null,
29
+ createdAt: 1735689601000,
30
+ updatedAt: 1735689602000,
31
+ },
32
+ ]);
33
+
34
+ mock.module("../oauth/oauth-store.js", () => ({
35
+ deleteApp: mock(() => Promise.resolve()),
36
+ disconnectOAuthProvider: mock(() => Promise.resolve()),
37
+ getApp: mockGetApp,
38
+ getAppClientSecret: mock(() => Promise.resolve(undefined)),
39
+ getConnection: mock(() => undefined),
40
+ getProvider: mock(() => undefined),
41
+ listApps: mock(() => []),
42
+ listConnections: mockListConnections,
43
+ upsertApp: mock(() =>
44
+ Promise.resolve({
45
+ id: "app-1",
46
+ providerKey: "google",
47
+ clientId: "client-1",
48
+ createdAt: 1735689500000,
49
+ updatedAt: 1735689550000,
50
+ }),
51
+ ),
52
+ }));
53
+
54
+ mock.module("../oauth/connect-orchestrator.js", () => ({
55
+ orchestrateOAuthConnect: mock(() =>
56
+ Promise.resolve({
57
+ success: true,
58
+ deferred: false,
59
+ grantedScopes: [],
60
+ accountInfo: null,
61
+ refreshTokenPresent: false,
62
+ }),
63
+ ),
64
+ }));
65
+
66
+ import { oauthAppsRouteDefinitions } from "../runtime/routes/oauth-apps.js";
67
+
68
+ const routes = oauthAppsRouteDefinitions();
69
+
70
+ function getRoute(method: string, endpoint: string) {
71
+ const route = routes.find(
72
+ (r) => r.method === method && r.endpoint === endpoint,
73
+ );
74
+ if (!route) throw new Error(`Route not found: ${method} ${endpoint}`);
75
+ return route;
76
+ }
77
+
78
+ describe("GET /v1/oauth/apps/:appId/connections", () => {
79
+ test("normalizes granted_scopes and has_refresh_token", async () => {
80
+ const req = new Request("http://localhost/v1/oauth/apps/app-1/connections");
81
+ const url = new URL(req.url);
82
+ const res = await getRoute("GET", "oauth/apps/:appId/connections").handler({
83
+ req,
84
+ url,
85
+ server: null as never,
86
+ authContext: null as never,
87
+ params: { appId: "app-1" },
88
+ });
89
+
90
+ expect(res.status).toBe(200);
91
+ const body = (await res.json()) as {
92
+ connections: Array<{
93
+ granted_scopes: unknown;
94
+ has_refresh_token: unknown;
95
+ }>;
96
+ };
97
+
98
+ expect(body.connections[0]?.granted_scopes).toEqual(["email", "profile"]);
99
+ expect(body.connections[0]?.has_refresh_token).toBe(true);
100
+ expect(body.connections[1]?.granted_scopes).toEqual([]);
101
+ expect(body.connections[1]?.has_refresh_token).toBe(false);
102
+ });
103
+ });