@vellumai/assistant 0.5.0 → 0.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (347) hide show
  1. package/ARCHITECTURE.md +54 -54
  2. package/docs/architecture/integrations.md +62 -67
  3. package/docs/credential-execution-service.md +3 -3
  4. package/package.json +1 -1
  5. package/src/__tests__/agent-loop.test.ts +111 -0
  6. package/src/__tests__/always-loaded-tools-guard.test.ts +3 -4
  7. package/src/__tests__/app-builder-tool-scripts.test.ts +13 -151
  8. package/src/__tests__/app-dir-path-guard.test.ts +78 -0
  9. package/src/__tests__/app-executors.test.ts +1 -291
  10. package/src/__tests__/app-git-history.test.ts +4 -4
  11. package/src/__tests__/app-routes-csp.test.ts +1 -0
  12. package/src/__tests__/app-store-dir-names.test.ts +426 -0
  13. package/src/__tests__/assistant-feature-flags-integration.test.ts +7 -9
  14. package/src/__tests__/attachments-store.test.ts +169 -21
  15. package/src/__tests__/attachments.test.ts +115 -1
  16. package/src/__tests__/btw-routes.test.ts +1 -0
  17. package/src/__tests__/canonical-guardian-store.test.ts +38 -0
  18. package/src/__tests__/channel-reply-delivery.test.ts +55 -0
  19. package/src/__tests__/checker.test.ts +54 -0
  20. package/src/__tests__/claude-code-skill-regression.test.ts +2 -0
  21. package/src/__tests__/claude-code-tool-profiles.test.ts +2 -0
  22. package/src/__tests__/compaction.benchmark.test.ts +2 -1
  23. package/src/__tests__/config-schema-cmd.test.ts +68 -21
  24. package/src/__tests__/config-schema.test.ts +1 -1
  25. package/src/__tests__/conversation-agent-loop-overflow.test.ts +149 -5
  26. package/src/__tests__/conversation-agent-loop.test.ts +290 -2
  27. package/src/__tests__/conversation-attachments.test.ts +17 -19
  28. package/src/__tests__/conversation-disk-view-integration.test.ts +277 -0
  29. package/src/__tests__/conversation-disk-view.test.ts +810 -0
  30. package/src/__tests__/conversation-error.test.ts +1 -1
  31. package/src/__tests__/conversation-fork-crud.test.ts +551 -0
  32. package/src/__tests__/conversation-fork-route.test.ts +386 -0
  33. package/src/__tests__/conversation-history-web-search.test.ts +1 -1
  34. package/src/__tests__/conversation-key-store-disk-view.test.ts +130 -0
  35. package/src/__tests__/conversation-media-retry.test.ts +8 -2
  36. package/src/__tests__/conversation-queue.test.ts +36 -1
  37. package/src/__tests__/conversation-routes-disk-view.test.ts +439 -0
  38. package/src/__tests__/conversation-routes-guardian-reply.test.ts +2 -2
  39. package/src/__tests__/conversation-routes-slash-commands.test.ts +2 -7
  40. package/src/__tests__/conversation-runtime-assembly.test.ts +17 -2
  41. package/src/__tests__/conversation-skill-tools.test.ts +4 -9
  42. package/src/__tests__/conversation-slash-commands.test.ts +149 -0
  43. package/src/__tests__/conversation-store.test.ts +24 -21
  44. package/src/__tests__/conversation-surfaces-state-update.test.ts +246 -0
  45. package/src/__tests__/conversation-surfaces-task-progress.test.ts +1 -0
  46. package/src/__tests__/conversation-title-service.test.ts +137 -0
  47. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +25 -315
  48. package/src/__tests__/conversation-tool-setup-memory-scope.test.ts +1 -0
  49. package/src/__tests__/conversation-tool-setup-side-effect-flag.test.ts +1 -0
  50. package/src/__tests__/conversation-workspace-cache-state.test.ts +44 -2
  51. package/src/__tests__/conversation-workspace-injection.test.ts +11 -0
  52. package/src/__tests__/credential-execution-feature-gates.test.ts +3 -3
  53. package/src/__tests__/credential-security-invariants.test.ts +3 -0
  54. package/src/__tests__/credential-vault-unit.test.ts +5 -10
  55. package/src/__tests__/cu-unified-flow.test.ts +1 -0
  56. package/src/__tests__/db-conversation-fork-lineage-migration.test.ts +241 -0
  57. package/src/__tests__/db-llm-request-log-provider-migration.test.ts +214 -0
  58. package/src/__tests__/diagnostics-export.test.ts +70 -1
  59. package/src/__tests__/filesystem-tools.test.ts +4 -2
  60. package/src/__tests__/first-greeting.test.ts +80 -0
  61. package/src/__tests__/gateway-only-guard.test.ts +1 -0
  62. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +3 -7
  63. package/src/__tests__/history-repair.test.ts +103 -10
  64. package/src/__tests__/http-conversation-lineage.test.ts +251 -0
  65. package/src/__tests__/image-source-path-reinject.test.ts +136 -0
  66. package/src/__tests__/llm-context-normalization.test.ts +1116 -0
  67. package/src/__tests__/llm-context-route-provider.test.ts +217 -0
  68. package/src/__tests__/llm-request-log-turn-query.test.ts +270 -0
  69. package/src/__tests__/media-generate-image.test.ts +47 -94
  70. package/src/__tests__/memory-lifecycle-e2e.test.ts +3 -1
  71. package/src/__tests__/memory-recall-quality.test.ts +5 -5
  72. package/src/__tests__/migration-cross-version-compatibility.test.ts +4 -1
  73. package/src/__tests__/migration-export-http.test.ts +3 -1
  74. package/src/__tests__/migration-import-commit-http.test.ts +18 -4
  75. package/src/__tests__/migration-import-preflight-http.test.ts +1 -3
  76. package/src/__tests__/mime-builder.test.ts +3 -2
  77. package/src/__tests__/non-member-access-request.test.ts +12 -1
  78. package/src/__tests__/notification-decision-identity.test.ts +52 -0
  79. package/src/__tests__/oauth-apps-routes.test.ts +103 -0
  80. package/src/__tests__/oauth-store.test.ts +115 -0
  81. package/src/__tests__/provider-error-scenarios.test.ts +1 -3
  82. package/src/__tests__/provider-failover-actual-provider.test.ts +66 -0
  83. package/src/__tests__/recording-handler.test.ts +17 -0
  84. package/src/__tests__/registry.test.ts +3 -8
  85. package/src/__tests__/relay-server.test.ts +1 -1
  86. package/src/__tests__/runtime-attachment-metadata.test.ts +7 -3
  87. package/src/__tests__/schema-transforms.test.ts +165 -5
  88. package/src/__tests__/server-history-render.test.ts +2 -2
  89. package/src/__tests__/skill-feature-flags-integration.test.ts +18 -17
  90. package/src/__tests__/skill-feature-flags.test.ts +13 -13
  91. package/src/__tests__/skill-load-feature-flag.test.ts +4 -4
  92. package/src/__tests__/slack-app-setup-skill-regression.test.ts +3 -1
  93. package/src/__tests__/slack-inbound-verification.test.ts +2 -2
  94. package/src/__tests__/starter-task-flow.test.ts +1 -0
  95. package/src/__tests__/suggestion-routes.test.ts +443 -0
  96. package/src/__tests__/swarm-conversation-integration.test.ts +1 -0
  97. package/src/__tests__/swarm-recursion.test.ts +1 -0
  98. package/src/__tests__/swarm-tool.test.ts +1 -0
  99. package/src/__tests__/system-prompt.test.ts +8 -0
  100. package/src/__tests__/tool-execution-abort-cleanup.test.ts +1 -0
  101. package/src/__tests__/tool-preview-lifecycle.test.ts +32 -5
  102. package/src/__tests__/top-level-renderer.test.ts +22 -0
  103. package/src/__tests__/turn-boundary-resolution.test.ts +243 -0
  104. package/src/__tests__/web-fetch.test.ts +6 -2
  105. package/src/__tests__/workspace-migration-006-services-config.test.ts +335 -0
  106. package/src/__tests__/workspace-migration-007-web-search-provider-rename.test.ts +312 -0
  107. package/src/__tests__/workspace-migration-009-backfill-conversation-disk-view.test.ts +278 -0
  108. package/src/__tests__/workspace-migration-010-app-dir-rename.test.ts +275 -0
  109. package/src/__tests__/workspace-migration-012-rename-conversation-disk-view-dirs.test.ts +77 -0
  110. package/src/__tests__/workspace-migration-013-repair-conversation-disk-view.test.ts +401 -0
  111. package/src/__tests__/workspace-migration-backfill-installation-id.test.ts +328 -0
  112. package/src/__tests__/workspace-migration-seed-device-id.test.ts +6 -10
  113. package/src/agent/attachments.ts +27 -1
  114. package/src/agent/loop.ts +29 -1
  115. package/src/avatar/traits-png-sync.ts +80 -25
  116. package/src/bundler/app-bundler.ts +4 -4
  117. package/src/calls/call-domain.ts +1 -0
  118. package/src/calls/voice-session-bridge.ts +1 -0
  119. package/src/cli/commands/auth.ts +92 -0
  120. package/src/cli/commands/avatar.ts +7 -6
  121. package/src/cli/commands/config.ts +2 -0
  122. package/src/cli/commands/oauth/providers.ts +29 -0
  123. package/src/cli/program.ts +12 -0
  124. package/src/cli.ts +15 -48
  125. package/src/config/bundled-skills/app-builder/SKILL.md +103 -28
  126. package/src/config/bundled-skills/app-builder/TOOLS.json +5 -199
  127. package/src/config/bundled-skills/app-builder/tools/{app-query.ts → app-refresh.ts} +2 -2
  128. package/src/config/bundled-skills/contacts/tools/google-contacts.ts +2 -3
  129. package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +6 -9
  130. package/src/config/bundled-skills/gmail/tools/gmail-attachments.ts +4 -6
  131. package/src/config/bundled-skills/gmail/tools/gmail-draft.ts +2 -3
  132. package/src/config/bundled-skills/gmail/tools/gmail-filters.ts +2 -3
  133. package/src/config/bundled-skills/gmail/tools/gmail-follow-up.ts +2 -3
  134. package/src/config/bundled-skills/gmail/tools/gmail-forward.ts +2 -3
  135. package/src/config/bundled-skills/gmail/tools/gmail-label.ts +4 -6
  136. package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +2 -3
  137. package/src/config/bundled-skills/gmail/tools/gmail-send-draft.ts +2 -3
  138. package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +2 -3
  139. package/src/config/bundled-skills/gmail/tools/gmail-trash.ts +2 -3
  140. package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +2 -3
  141. package/src/config/bundled-skills/gmail/tools/gmail-vacation.ts +2 -3
  142. package/src/config/bundled-skills/google-calendar/tools/shared.ts +1 -1
  143. package/src/config/bundled-skills/image-studio/SKILL.md +2 -2
  144. package/src/config/bundled-skills/image-studio/TOOLS.json +2 -2
  145. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +45 -72
  146. package/src/config/bundled-skills/media-processing/tools/extract-keyframes.ts +2 -2
  147. package/src/config/bundled-skills/messaging/tools/shared.ts +1 -1
  148. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +19 -3
  149. package/src/config/bundled-skills/skill-management/TOOLS.json +2 -2
  150. package/src/config/bundled-skills/slack/tools/shared.ts +19 -4
  151. package/src/config/bundled-skills/slack/tools/slack-scan-digest.ts +2 -3
  152. package/src/config/bundled-skills/transcribe/SKILL.md +1 -1
  153. package/src/config/bundled-skills/transcribe/TOOLS.json +2 -6
  154. package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +19 -83
  155. package/src/config/bundled-tool-registry.ts +2 -14
  156. package/src/config/feature-flag-registry.json +16 -0
  157. package/src/config/loader.ts +64 -0
  158. package/src/config/raw-config-utils.ts +30 -0
  159. package/src/config/schema-utils.ts +28 -7
  160. package/src/config/schema.ts +8 -0
  161. package/src/config/schemas/elevenlabs.ts +18 -0
  162. package/src/config/schemas/memory-lifecycle.ts +4 -2
  163. package/src/config/schemas/memory-storage.ts +1 -1
  164. package/src/config/schemas/services.ts +8 -6
  165. package/src/contacts/contact-store.ts +13 -6
  166. package/src/contacts/contacts-write.ts +0 -1
  167. package/src/context/window-manager.ts +13 -2
  168. package/src/daemon/conversation-agent-loop-handlers.ts +46 -42
  169. package/src/daemon/conversation-agent-loop.ts +56 -19
  170. package/src/daemon/conversation-attachments.ts +18 -36
  171. package/src/daemon/conversation-error.ts +2 -1
  172. package/src/daemon/conversation-history.ts +18 -4
  173. package/src/daemon/conversation-lifecycle.ts +39 -15
  174. package/src/daemon/conversation-messaging.ts +70 -26
  175. package/src/daemon/conversation-process.ts +58 -34
  176. package/src/daemon/conversation-runtime-assembly.ts +21 -38
  177. package/src/daemon/conversation-slash.ts +121 -256
  178. package/src/daemon/conversation-surfaces.ts +143 -20
  179. package/src/daemon/conversation-tool-setup.ts +0 -6
  180. package/src/daemon/conversation-workspace.ts +21 -1
  181. package/src/daemon/conversation.ts +51 -29
  182. package/src/daemon/first-greeting.ts +35 -0
  183. package/src/daemon/handlers/config-embeddings.ts +148 -0
  184. package/src/daemon/handlers/config-model.ts +71 -26
  185. package/src/daemon/handlers/conversations.ts +0 -23
  186. package/src/daemon/handlers/recording.ts +26 -21
  187. package/src/daemon/history-repair.ts +28 -8
  188. package/src/daemon/host-cu-proxy.ts +2 -2
  189. package/src/daemon/lifecycle.ts +106 -64
  190. package/src/daemon/message-protocol.ts +3 -0
  191. package/src/daemon/message-types/conversations.ts +19 -0
  192. package/src/daemon/message-types/messages.ts +1 -0
  193. package/src/daemon/message-types/shared.ts +2 -0
  194. package/src/daemon/message-types/surfaces.ts +2 -0
  195. package/src/daemon/message-types/upgrades.ts +23 -0
  196. package/src/daemon/server.ts +83 -12
  197. package/src/daemon/shutdown-handlers.ts +8 -5
  198. package/src/daemon/startup-error.ts +9 -0
  199. package/src/daemon/tool-side-effects.ts +11 -28
  200. package/src/events/tool-permission-telemetry-listener.ts +1 -3
  201. package/src/instrument.ts +0 -4
  202. package/src/media/app-icon-generator.ts +2 -2
  203. package/src/memory/app-git-service.ts +28 -16
  204. package/src/memory/app-store.ts +230 -41
  205. package/src/memory/attachments-store.ts +558 -130
  206. package/src/memory/conversation-attention-store.ts +70 -0
  207. package/src/memory/conversation-crud.ts +442 -3
  208. package/src/memory/conversation-directories.ts +125 -0
  209. package/src/memory/conversation-disk-view.ts +390 -0
  210. package/src/memory/conversation-key-store.ts +17 -5
  211. package/src/memory/conversation-queries.ts +5 -1
  212. package/src/memory/conversation-title-service.ts +21 -49
  213. package/src/memory/db-init.ts +28 -0
  214. package/src/memory/embedding-backend.ts +42 -53
  215. package/src/memory/embedding-gemini.test.ts +4 -4
  216. package/src/memory/embedding-local.ts +1 -3
  217. package/src/memory/embedding-ollama.ts +1 -3
  218. package/src/memory/embedding-openai.ts +1 -3
  219. package/src/memory/indexer.ts +9 -7
  220. package/src/memory/items-extractor.ts +42 -13
  221. package/src/memory/job-handlers/conversation-starters.ts +6 -1
  222. package/src/memory/job-handlers/embedding.test.ts +1 -4
  223. package/src/memory/llm-request-log-store.ts +100 -1
  224. package/src/memory/migrations/102-alter-table-columns.ts +5 -0
  225. package/src/memory/migrations/146-schedule-oneshot-routing.ts +3 -3
  226. package/src/memory/migrations/147-migrate-reminders-to-schedules.ts +66 -70
  227. package/src/memory/migrations/148-drop-reminders-table.ts +5 -9
  228. package/src/memory/migrations/160-drop-loopback-port-column.ts +1 -3
  229. package/src/memory/migrations/174-rename-thread-starters-table.ts +0 -7
  230. package/src/memory/migrations/178-oauth-providers-managed-service-config-key.ts +15 -0
  231. package/src/memory/migrations/179-llm-request-log-message-id.ts +16 -0
  232. package/src/memory/migrations/180-backfill-inline-attachments-to-disk.ts +66 -0
  233. package/src/memory/migrations/181-rename-thread-starters-checkpoints.ts +46 -0
  234. package/src/memory/migrations/182-oauth-providers-display-metadata.ts +20 -0
  235. package/src/memory/migrations/183-add-conversation-fork-lineage.ts +22 -0
  236. package/src/memory/migrations/184-llm-request-log-provider.ts +12 -0
  237. package/src/memory/migrations/index.ts +7 -0
  238. package/src/memory/migrations/registry.ts +13 -0
  239. package/src/memory/retriever.test.ts +601 -2
  240. package/src/memory/retriever.ts +85 -9
  241. package/src/memory/schema/conversations.ts +6 -0
  242. package/src/memory/schema/infrastructure.ts +13 -7
  243. package/src/memory/schema/oauth.ts +6 -0
  244. package/src/messaging/providers/gmail/mime-builder.ts +3 -1
  245. package/src/notifications/copy-composer.ts +26 -0
  246. package/src/notifications/decision-engine.ts +14 -1
  247. package/src/notifications/emit-signal.ts +1 -1
  248. package/src/notifications/signal.ts +36 -0
  249. package/src/oauth/byo-connection.test.ts +1 -45
  250. package/src/oauth/byo-connection.ts +2 -8
  251. package/src/oauth/connect-orchestrator.ts +15 -11
  252. package/src/oauth/connection-resolver.test.ts +191 -0
  253. package/src/oauth/connection-resolver.ts +66 -38
  254. package/src/oauth/connection.ts +0 -1
  255. package/src/oauth/oauth-store.ts +97 -47
  256. package/src/oauth/platform-connection.test.ts +0 -1
  257. package/src/oauth/platform-connection.ts +11 -3
  258. package/src/oauth/seed-providers.ts +78 -3
  259. package/src/oauth/token-persistence.ts +16 -10
  260. package/src/permissions/checker.ts +62 -19
  261. package/src/prompts/system-prompt.ts +2 -0
  262. package/src/prompts/templates/BOOTSTRAP.md +2 -0
  263. package/src/providers/anthropic/client.ts +8 -1
  264. package/src/providers/failover.ts +4 -1
  265. package/src/providers/gemini/client.ts +50 -0
  266. package/src/providers/model-catalog.ts +92 -0
  267. package/src/providers/model-intents.ts +29 -20
  268. package/src/providers/openai/client.ts +49 -0
  269. package/src/providers/types.ts +2 -0
  270. package/src/runtime/access-request-helper.ts +16 -7
  271. package/src/runtime/auth/credential-service.ts +3 -1
  272. package/src/runtime/auth/route-policy.ts +14 -1
  273. package/src/runtime/btw-sidechain.ts +101 -0
  274. package/src/runtime/channel-reply-delivery.ts +17 -1
  275. package/src/runtime/http-router.ts +3 -1
  276. package/src/runtime/http-server.ts +196 -141
  277. package/src/runtime/http-types.ts +1 -0
  278. package/src/runtime/migrations/vbundle-builder.ts +5 -1
  279. package/src/runtime/routes/access-request-decision.ts +41 -0
  280. package/src/runtime/routes/app-management-routes.ts +6 -3
  281. package/src/runtime/routes/app-routes.ts +7 -3
  282. package/src/runtime/routes/approval-routes.ts +1 -0
  283. package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +34 -2
  284. package/src/runtime/routes/attachment-routes.ts +45 -15
  285. package/src/runtime/routes/btw-routes.ts +21 -61
  286. package/src/runtime/routes/conversation-management-routes.ts +68 -0
  287. package/src/runtime/routes/conversation-query-routes.ts +180 -10
  288. package/src/runtime/routes/conversation-routes.ts +222 -28
  289. package/src/runtime/routes/conversation-starter-routes.ts +9 -11
  290. package/src/runtime/routes/diagnostics-routes.ts +1 -0
  291. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +2 -2
  292. package/src/runtime/routes/llm-context-normalization.ts +1199 -0
  293. package/src/runtime/routes/log-export-routes.ts +3 -0
  294. package/src/runtime/routes/memory-item-routes.test.ts +34 -0
  295. package/src/runtime/routes/memory-item-routes.ts +4 -0
  296. package/src/runtime/routes/migration-routes.ts +4 -1
  297. package/src/runtime/routes/oauth-apps.ts +291 -0
  298. package/src/runtime/routes/secret-routes.ts +28 -1
  299. package/src/runtime/routes/settings-routes.ts +14 -0
  300. package/src/runtime/routes/trace-event-routes.ts +4 -1
  301. package/src/schedule/schedule-store.ts +9 -21
  302. package/src/security/secure-keys.ts +21 -0
  303. package/src/signals/bash.ts +1 -1
  304. package/src/swarm/backend-claude-code.ts +3 -6
  305. package/src/telemetry/usage-telemetry-reporter.test.ts +3 -2
  306. package/src/telemetry/usage-telemetry-reporter.ts +3 -1
  307. package/src/tools/AGENTS.md +6 -10
  308. package/src/tools/apps/executors.ts +17 -232
  309. package/src/tools/claude-code/claude-code.ts +2 -3
  310. package/src/tools/credentials/vault.ts +7 -12
  311. package/src/tools/host-filesystem/read.ts +13 -10
  312. package/src/tools/network/__tests__/web-search.test.ts +4 -2
  313. package/src/tools/schedule/list.ts +2 -7
  314. package/src/tools/schema-transforms.ts +5 -0
  315. package/src/tools/shared/filesystem/format-diff.ts +4 -21
  316. package/src/tools/skills/execute.ts +1 -1
  317. package/src/tools/tool-manifest.ts +0 -6
  318. package/src/tools/ui-surface/definitions.ts +2 -2
  319. package/src/util/device-id.ts +28 -5
  320. package/src/util/platform.ts +6 -0
  321. package/src/util/pricing.ts +1 -0
  322. package/src/util/retry.ts +1 -3
  323. package/src/workspace/migrations/002-backfill-installation-id.ts +23 -12
  324. package/src/workspace/migrations/003-seed-device-id.ts +3 -4
  325. package/src/workspace/migrations/006-services-config.ts +5 -0
  326. package/src/workspace/migrations/008-voice-timeout-and-max-steps.ts +12 -0
  327. package/src/workspace/migrations/009-backfill-conversation-disk-view.ts +10 -0
  328. package/src/workspace/migrations/010-app-dir-rename.ts +223 -0
  329. package/src/workspace/migrations/012-rename-conversation-disk-view-dirs.ts +64 -0
  330. package/src/workspace/migrations/013-repair-conversation-disk-view.ts +11 -0
  331. package/src/workspace/migrations/rebuild-conversation-disk-view.ts +186 -0
  332. package/src/workspace/migrations/registry.ts +10 -0
  333. package/src/workspace/top-level-renderer.ts +12 -0
  334. package/src/__tests__/asset-materialize-tool.test.ts +0 -523
  335. package/src/__tests__/asset-search-tool.test.ts +0 -536
  336. package/src/__tests__/fixtures/media-reuse-fixtures.ts +0 -56
  337. package/src/__tests__/media-reuse-story.e2e.test.ts +0 -762
  338. package/src/__tests__/media-visibility-policy.test.ts +0 -190
  339. package/src/config/bundled-skills/app-builder/tools/app-file-edit.ts +0 -14
  340. package/src/config/bundled-skills/app-builder/tools/app-file-list.ts +0 -13
  341. package/src/config/bundled-skills/app-builder/tools/app-file-read.ts +0 -21
  342. package/src/config/bundled-skills/app-builder/tools/app-file-write.ts +0 -14
  343. package/src/config/bundled-skills/app-builder/tools/app-list.ts +0 -13
  344. package/src/config/bundled-skills/app-builder/tools/app-update.ts +0 -23
  345. package/src/daemon/media-visibility-policy.ts +0 -59
  346. package/src/tools/assets/materialize.ts +0 -248
  347. package/src/tools/assets/search.ts +0 -400
@@ -0,0 +1,426 @@
1
+ import {
2
+ existsSync,
3
+ mkdirSync,
4
+ readFileSync,
5
+ rmSync,
6
+ writeFileSync,
7
+ } from "node:fs";
8
+ import { tmpdir } from "node:os";
9
+ import { join } from "node:path";
10
+ import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
11
+
12
+ // ---------------------------------------------------------------------------
13
+ // Mock getDataDir to use a temp directory
14
+ // ---------------------------------------------------------------------------
15
+
16
+ let testDataDir: string;
17
+
18
+ mock.module("../util/platform.js", () => ({
19
+ getDataDir: () => testDataDir,
20
+ getProjectDir: () => testDataDir,
21
+ }));
22
+
23
+ // Re-import app-store after mocking so it uses our temp dir
24
+ const {
25
+ slugify,
26
+ generateAppDirName,
27
+ validateDirName,
28
+ resolveAppDir,
29
+ getAppDirPath,
30
+ getAppsDir,
31
+ createApp,
32
+ getApp,
33
+ updateApp,
34
+ deleteApp,
35
+ } = await import("../memory/app-store.js");
36
+
37
+ // ---------------------------------------------------------------------------
38
+ // Helpers
39
+ // ---------------------------------------------------------------------------
40
+
41
+ function freshTempDir(): string {
42
+ return join(
43
+ tmpdir(),
44
+ `vellum-app-dir-test-${Date.now()}-${Math.random().toString(36).slice(2)}`,
45
+ );
46
+ }
47
+
48
+ function makeAppParams(name: string) {
49
+ return {
50
+ name,
51
+ schemaJson: "{}",
52
+ htmlDefinition: "<h1>Hello</h1>",
53
+ };
54
+ }
55
+
56
+ // ---------------------------------------------------------------------------
57
+ // Setup / teardown
58
+ // ---------------------------------------------------------------------------
59
+
60
+ beforeEach(() => {
61
+ testDataDir = freshTempDir();
62
+ mkdirSync(join(testDataDir, "apps"), { recursive: true });
63
+ });
64
+
65
+ afterEach(() => {
66
+ if (existsSync(testDataDir)) {
67
+ rmSync(testDataDir, { recursive: true, force: true });
68
+ }
69
+ });
70
+
71
+ // ---------------------------------------------------------------------------
72
+ // slugify()
73
+ // ---------------------------------------------------------------------------
74
+
75
+ describe("slugify()", () => {
76
+ test("normal names", () => {
77
+ expect(slugify("My Cool App")).toBe("my-cool-app");
78
+ });
79
+
80
+ test("special characters are replaced with hyphens", () => {
81
+ expect(slugify("hello@world!#$%")).toBe("hello-world");
82
+ });
83
+
84
+ test("unicode characters are replaced", () => {
85
+ expect(slugify("café résumé")).toBe("caf-r-sum");
86
+ });
87
+
88
+ test("emoji-only names produce fallback slug", () => {
89
+ const result = slugify("🚀🎉");
90
+ expect(result).toMatch(/^app-[a-f0-9]{8}$/);
91
+ });
92
+
93
+ test("empty string produces fallback slug", () => {
94
+ const result = slugify("");
95
+ expect(result).toMatch(/^app-[a-f0-9]{8}$/);
96
+ });
97
+
98
+ test("very long names are truncated to 60 chars", () => {
99
+ const longName = "a".repeat(100);
100
+ const result = slugify(longName);
101
+ expect(result.length).toBeLessThanOrEqual(60);
102
+ expect(result).toBe("a".repeat(60));
103
+ });
104
+
105
+ test("truncation removes trailing hyphens", () => {
106
+ // Create a name where position 60 lands on a hyphen sequence
107
+ const name = "a".repeat(58) + "--bbb";
108
+ const result = slugify(name);
109
+ expect(result.length).toBeLessThanOrEqual(60);
110
+ expect(result).not.toMatch(/-$/);
111
+ });
112
+
113
+ test("names with only hyphens produce fallback slug", () => {
114
+ const result = slugify("---");
115
+ expect(result).toMatch(/^app-[a-f0-9]{8}$/);
116
+ });
117
+
118
+ test("leading and trailing hyphens are stripped", () => {
119
+ expect(slugify("-hello-world-")).toBe("hello-world");
120
+ });
121
+
122
+ test("consecutive hyphens are collapsed", () => {
123
+ expect(slugify("hello---world")).toBe("hello-world");
124
+ });
125
+ });
126
+
127
+ // ---------------------------------------------------------------------------
128
+ // generateAppDirName()
129
+ // ---------------------------------------------------------------------------
130
+
131
+ describe("generateAppDirName()", () => {
132
+ test("returns base slug when no collision", () => {
133
+ const result = generateAppDirName("My App", new Set());
134
+ expect(result).toBe("my-app");
135
+ });
136
+
137
+ test("appends -2 on first collision", () => {
138
+ const result = generateAppDirName("My App", new Set(["my-app"]));
139
+ expect(result).toBe("my-app-2");
140
+ });
141
+
142
+ test("escalates numeric suffix on multiple collisions", () => {
143
+ const existing = new Set(["my-app", "my-app-2", "my-app-3"]);
144
+ const result = generateAppDirName("My App", existing);
145
+ expect(result).toBe("my-app-4");
146
+ });
147
+
148
+ test("collision with truncated names still deduplicates", () => {
149
+ const longName = "a".repeat(100);
150
+ const base = slugify(longName);
151
+ const existing = new Set([base]);
152
+ const result = generateAppDirName(longName, existing);
153
+ expect(result).toBe(`${base}-2`);
154
+ });
155
+ });
156
+
157
+ // ---------------------------------------------------------------------------
158
+ // createApp() — directory named after slug
159
+ // ---------------------------------------------------------------------------
160
+
161
+ describe("createApp()", () => {
162
+ test("directory is named after slug, not UUID", () => {
163
+ const app = createApp(makeAppParams("My Test App"));
164
+ const appsDir = getAppsDir();
165
+
166
+ // dirName should be the slug
167
+ expect(app.dirName).toBe("my-test-app");
168
+
169
+ // JSON file should be named after slug
170
+ expect(existsSync(join(appsDir, "my-test-app.json"))).toBe(true);
171
+
172
+ // Directory should be named after slug
173
+ expect(existsSync(join(appsDir, "my-test-app"))).toBe(true);
174
+
175
+ // UUID-named files should NOT exist
176
+ expect(existsSync(join(appsDir, `${app.id}.json`))).toBe(false);
177
+ });
178
+
179
+ test("dirName is persisted in JSON", () => {
180
+ const app = createApp(makeAppParams("Slug Test"));
181
+ const appsDir = getAppsDir();
182
+ const jsonPath = join(appsDir, "slug-test.json");
183
+ const persisted = JSON.parse(readFileSync(jsonPath, "utf-8"));
184
+ expect(persisted.dirName).toBe("slug-test");
185
+ expect(persisted.id).toBe(app.id);
186
+ });
187
+
188
+ test("index.html is in the slugified directory", () => {
189
+ createApp(makeAppParams("Html App"));
190
+ const appsDir = getAppsDir();
191
+ const indexPath = join(appsDir, "html-app", "index.html");
192
+ expect(existsSync(indexPath)).toBe(true);
193
+ expect(readFileSync(indexPath, "utf-8")).toBe("<h1>Hello</h1>");
194
+ });
195
+
196
+ test("deduplicates dirNames across multiple creates", () => {
197
+ const app1 = createApp(makeAppParams("Duplicate"));
198
+ const app2 = createApp(makeAppParams("Duplicate"));
199
+ expect(app1.dirName).toBe("duplicate");
200
+ expect(app2.dirName).toBe("duplicate-2");
201
+ });
202
+ });
203
+
204
+ // ---------------------------------------------------------------------------
205
+ // createApp() + updateApp() — frozen dirName invariant
206
+ // ---------------------------------------------------------------------------
207
+
208
+ describe("frozen dirName invariant", () => {
209
+ test("renaming an app does NOT change its directory name", () => {
210
+ const app = createApp(makeAppParams("Original Name"));
211
+ const appsDir = getAppsDir();
212
+
213
+ expect(app.dirName).toBe("original-name");
214
+ expect(existsSync(join(appsDir, "original-name.json"))).toBe(true);
215
+
216
+ // Rename the app
217
+ const updated = updateApp(app.id, { name: "New Name" });
218
+ expect(updated.name).toBe("New Name");
219
+
220
+ // Directory and files should still be at original slug
221
+ expect(existsSync(join(appsDir, "original-name.json"))).toBe(true);
222
+ expect(existsSync(join(appsDir, "original-name"))).toBe(true);
223
+
224
+ // New name slug should NOT exist as files
225
+ expect(existsSync(join(appsDir, "new-name.json"))).toBe(false);
226
+ });
227
+ });
228
+
229
+ // ---------------------------------------------------------------------------
230
+ // getApp() — lookup by UUID with slugified dirs
231
+ // ---------------------------------------------------------------------------
232
+
233
+ describe("getApp()", () => {
234
+ test("lookup by UUID works with slugified dirs", () => {
235
+ const created = createApp(makeAppParams("Lookup App"));
236
+ const fetched = getApp(created.id);
237
+ expect(fetched).not.toBeNull();
238
+ expect(fetched!.id).toBe(created.id);
239
+ expect(fetched!.name).toBe("Lookup App");
240
+ expect(fetched!.htmlDefinition).toBe("<h1>Hello</h1>");
241
+ });
242
+
243
+ test("backward compat: works when JSON has no dirName (uses id as fallback)", () => {
244
+ const appsDir = getAppsDir();
245
+ const fakeId = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee";
246
+
247
+ // Write a JSON file with no dirName, using the UUID as the filename
248
+ const appData = {
249
+ id: fakeId,
250
+ name: "Legacy App",
251
+ schemaJson: "{}",
252
+ createdAt: Date.now(),
253
+ updatedAt: Date.now(),
254
+ };
255
+ writeFileSync(
256
+ join(appsDir, `${fakeId}.json`),
257
+ JSON.stringify(appData, null, 2),
258
+ );
259
+
260
+ // Create directory with index.html
261
+ const appDir = join(appsDir, fakeId);
262
+ mkdirSync(appDir, { recursive: true });
263
+ writeFileSync(join(appDir, "index.html"), "<p>legacy</p>", "utf-8");
264
+
265
+ const fetched = getApp(fakeId);
266
+ expect(fetched).not.toBeNull();
267
+ expect(fetched!.id).toBe(fakeId);
268
+ expect(fetched!.name).toBe("Legacy App");
269
+ expect(fetched!.htmlDefinition).toBe("<p>legacy</p>");
270
+ });
271
+ });
272
+
273
+ // ---------------------------------------------------------------------------
274
+ // deleteApp() — cleans up slugified files
275
+ // ---------------------------------------------------------------------------
276
+
277
+ describe("deleteApp()", () => {
278
+ test("cleans up slugified directory, JSON, and preview files", () => {
279
+ const app = createApp({
280
+ ...makeAppParams("Delete Me"),
281
+ preview: "base64-preview-data",
282
+ });
283
+ const appsDir = getAppsDir();
284
+
285
+ // Verify files exist before deletion
286
+ expect(existsSync(join(appsDir, "delete-me.json"))).toBe(true);
287
+ expect(existsSync(join(appsDir, "delete-me"))).toBe(true);
288
+ expect(existsSync(join(appsDir, "delete-me.preview"))).toBe(true);
289
+
290
+ deleteApp(app.id);
291
+
292
+ // All files should be gone
293
+ expect(existsSync(join(appsDir, "delete-me.json"))).toBe(false);
294
+ expect(existsSync(join(appsDir, "delete-me"))).toBe(false);
295
+ expect(existsSync(join(appsDir, "delete-me.preview"))).toBe(false);
296
+ });
297
+ });
298
+
299
+ // ---------------------------------------------------------------------------
300
+ // resolveAppDir() — validation rejects malicious dirName values
301
+ // When a JSON file has an invalid dirName, resolveAppDir defensively falls
302
+ // back to using the app ID instead of the malicious dirName. The
303
+ // validateDirName() call inside the try/catch causes the invalid entry to
304
+ // be skipped, and the function returns the safe fallback.
305
+ // ---------------------------------------------------------------------------
306
+
307
+ describe("resolveAppDir() validation", () => {
308
+ test("falls back to id when dirName contains path traversal (..)", () => {
309
+ const appsDir = getAppsDir();
310
+ const fakeId = "bbbbbbbb-cccc-dddd-eeee-ffffffffffff";
311
+ writeFileSync(
312
+ join(appsDir, `${fakeId}.json`),
313
+ JSON.stringify({ id: fakeId, name: "Evil", dirName: "../etc" }),
314
+ );
315
+
316
+ // Should NOT use the malicious dirName — falls back to id
317
+ const result = resolveAppDir(fakeId);
318
+ expect(result.dirName).toBe(fakeId);
319
+ expect(result.appDir).toBe(join(appsDir, fakeId));
320
+ });
321
+
322
+ test("falls back to id when dirName contains forward slash", () => {
323
+ const appsDir = getAppsDir();
324
+ const fakeId = "cccccccc-dddd-eeee-ffff-aaaaaaaaaaaa";
325
+ writeFileSync(
326
+ join(appsDir, `${fakeId}.json`),
327
+ JSON.stringify({ id: fakeId, name: "Evil", dirName: "foo/bar" }),
328
+ );
329
+
330
+ const result = resolveAppDir(fakeId);
331
+ expect(result.dirName).toBe(fakeId);
332
+ });
333
+
334
+ test("falls back to id when dirName contains backslash", () => {
335
+ const appsDir = getAppsDir();
336
+ const fakeId = "dddddddd-eeee-ffff-aaaa-bbbbbbbbbbbb";
337
+ writeFileSync(
338
+ join(appsDir, `${fakeId}.json`),
339
+ JSON.stringify({ id: fakeId, name: "Evil", dirName: "foo\\bar" }),
340
+ );
341
+
342
+ const result = resolveAppDir(fakeId);
343
+ expect(result.dirName).toBe(fakeId);
344
+ });
345
+
346
+ test("falls back to id when dirName is empty string", () => {
347
+ const appsDir = getAppsDir();
348
+ const fakeId = "eeeeeeee-ffff-aaaa-bbbb-cccccccccccc";
349
+ writeFileSync(
350
+ join(appsDir, `${fakeId}.json`),
351
+ JSON.stringify({ id: fakeId, name: "Evil", dirName: "" }),
352
+ );
353
+
354
+ // Empty string is falsy, so `parsed.dirName || id` falls back to the app ID.
355
+ // This prevents appDir from resolving to the apps root directory.
356
+ const result = resolveAppDir(fakeId);
357
+ expect(result.dirName).toBe(fakeId);
358
+ expect(result.appDir).toBe(join(appsDir, fakeId));
359
+ });
360
+ });
361
+
362
+ // ---------------------------------------------------------------------------
363
+ // validateDirName() — direct unit tests
364
+ // ---------------------------------------------------------------------------
365
+
366
+ describe("validateDirName()", () => {
367
+ test("accepts valid slug names", () => {
368
+ expect(() => validateDirName("my-cool-app")).not.toThrow();
369
+ expect(() => validateDirName("app-123")).not.toThrow();
370
+ });
371
+
372
+ test("rejects git pathspec metacharacters", () => {
373
+ expect(() => validateDirName("app*")).toThrow("git pathspec");
374
+ expect(() => validateDirName("app?")).toThrow("git pathspec");
375
+ expect(() => validateDirName("app[0]")).toThrow("git pathspec");
376
+ expect(() => validateDirName("app:foo")).toThrow("git pathspec");
377
+ expect(() => validateDirName("app(1)")).toThrow("git pathspec");
378
+ });
379
+
380
+ test("rejects path traversal", () => {
381
+ expect(() => validateDirName("..")).toThrow("Invalid dirName");
382
+ expect(() => validateDirName("foo/bar")).toThrow("Invalid dirName");
383
+ });
384
+ });
385
+
386
+ // ---------------------------------------------------------------------------
387
+ // getAppDirPath() — returns correct path
388
+ // ---------------------------------------------------------------------------
389
+
390
+ describe("getAppDirPath()", () => {
391
+ test("returns correct path for slugified apps", () => {
392
+ const app = createApp(makeAppParams("Path Test"));
393
+ const appsDir = getAppsDir();
394
+ const result = getAppDirPath(app.id);
395
+ expect(result).toBe(join(appsDir, "path-test"));
396
+ });
397
+
398
+ test("returns correct path for legacy apps (no dirName)", () => {
399
+ const appsDir = getAppsDir();
400
+ const fakeId = "11111111-2222-3333-4444-555555555555";
401
+ // Write a legacy JSON with no dirName
402
+ writeFileSync(
403
+ join(appsDir, `${fakeId}.json`),
404
+ JSON.stringify({ id: fakeId, name: "Legacy" }),
405
+ );
406
+
407
+ const result = getAppDirPath(fakeId);
408
+ expect(result).toBe(join(appsDir, fakeId));
409
+ });
410
+ });
411
+
412
+ // ---------------------------------------------------------------------------
413
+ // Guard test: no file outside app-store.ts constructs getAppsDir() + appId
414
+ // (Note: the primary guard test is in app-dir-path-guard.test.ts; this is
415
+ // a complementary check that we can import and use the guard-relevant
416
+ // functions without issues.)
417
+ // ---------------------------------------------------------------------------
418
+
419
+ describe("guard: getAppsDir + appId path construction", () => {
420
+ test("app-dir-path-guard.test.ts exists and covers this concern", () => {
421
+ // This is a meta-test to ensure the guard test file is present
422
+ expect(existsSync(join(__dirname, "app-dir-path-guard.test.ts"))).toBe(
423
+ true,
424
+ );
425
+ });
426
+ });
@@ -228,7 +228,7 @@ describe("buildSystemPrompt assistant feature flag filtering", () => {
228
228
  expect(result).not.toContain(`**${DECLARED_SKILL_ID}**`);
229
229
  });
230
230
 
231
- test("declared skills hidden when no flag overrides set (registry defaults to false)", () => {
231
+ test("contacts visible but email-channel hidden when no flag overrides set (contacts defaults true, email-channel defaults false)", () => {
232
232
  createSkillOnDisk(
233
233
  DECLARED_SKILL_ID,
234
234
  "Contacts",
@@ -263,8 +263,8 @@ describe("buildSystemPrompt assistant feature flag filtering", () => {
263
263
 
264
264
  const result = buildSystemPrompt();
265
265
 
266
- // Both skills declare feature flags with registry defaultEnabled: false
267
- expect(result).not.toContain(`**${DECLARED_SKILL_ID}**`);
266
+ // contacts defaults to true, email-channel defaults to false
267
+ expect(result).toContain(`**${DECLARED_SKILL_ID}**`);
268
268
  expect(result).not.toContain("**email-channel**");
269
269
  });
270
270
 
@@ -466,12 +466,10 @@ describe("isAssistantFeatureFlagEnabled", () => {
466
466
 
467
467
  test("missing persisted value falls back to defaults registry defaultEnabled", () => {
468
468
  // No explicit config at all — should fall back to defaults registry
469
- // which has defaultEnabled: false for contacts
469
+ // which has defaultEnabled: true for contacts
470
470
  const config = {} as any;
471
471
 
472
- expect(isAssistantFeatureFlagEnabled(DECLARED_FLAG_KEY, config)).toBe(
473
- false,
474
- );
472
+ expect(isAssistantFeatureFlagEnabled(DECLARED_FLAG_KEY, config)).toBe(true);
475
473
  });
476
474
 
477
475
  test("unknown flag defaults to true when no persisted override", () => {
@@ -510,7 +508,7 @@ describe("isAssistantFeatureFlagEnabled with skillFlagKey", () => {
510
508
  ).toBe(false);
511
509
  });
512
510
 
513
- test("disabled when no override set (registry default is false)", () => {
511
+ test("enabled when no override set (registry default is true)", () => {
514
512
  const config = {} as any;
515
513
 
516
514
  expect(
@@ -518,6 +516,6 @@ describe("isAssistantFeatureFlagEnabled with skillFlagKey", () => {
518
516
  skillFlagKey({ featureFlag: DECLARED_FLAG_ID })!,
519
517
  config,
520
518
  ),
521
- ).toBe(false);
519
+ ).toBe(true);
522
520
  });
523
521
  });