@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
@@ -3,10 +3,14 @@
3
3
  *
4
4
  * Directory layout:
5
5
  * ~/.vellum/apps/
6
- * <app-id>.json # app definition
7
- * <app-id>/
6
+ * <dirName>.json # app definition
7
+ * <dirName>/
8
8
  * records/
9
9
  * <record-id>.json # individual record
10
+ *
11
+ * `dirName` is a human-readable slug derived from the app name at creation
12
+ * time and frozen thereafter (renaming an app does NOT rename its directory).
13
+ * Pre-migration apps (no `dirName` in JSON) fall back to using `id` (UUID).
10
14
  */
11
15
 
12
16
  import { randomUUID } from "node:crypto";
@@ -43,12 +47,14 @@ export interface AppDefinition {
43
47
  schemaJson: string;
44
48
  htmlDefinition: string;
45
49
  version?: string;
46
- /** Additional pages keyed by filename (e.g. "settings.html" HTML content). */
50
+ /** Additional pages keyed by filename (e.g. "settings.html" -> HTML content). */
47
51
  pages?: Record<string, string>;
48
52
  createdAt: number;
49
53
  updatedAt: number;
50
54
  /** App format version. undefined or 1 = legacy single-HTML, 2 = multi-file TSX. */
51
55
  formatVersion?: number;
56
+ /** Filesystem directory/file stem. Frozen at creation -- never changes on rename. */
57
+ dirName?: string;
52
58
  }
53
59
 
54
60
  /**
@@ -78,6 +84,29 @@ function validateId(id: string): void {
78
84
  }
79
85
  }
80
86
 
87
+ /**
88
+ * Validate a dirName read from persisted JSON.
89
+ * Superset of validateId rules plus git pathspec metacharacters,
90
+ * since dirName is used directly in git pathspecs by app-git-service.
91
+ */
92
+ export function validateDirName(dirName: string): void {
93
+ if (
94
+ !dirName ||
95
+ dirName.includes("/") ||
96
+ dirName.includes("\\") ||
97
+ dirName.includes("..") ||
98
+ dirName !== dirName.trim()
99
+ ) {
100
+ throw new Error(`Invalid dirName: ${dirName}`);
101
+ }
102
+ // Reject git pathspec metacharacters
103
+ if (/[*?[\]:()]/.test(dirName)) {
104
+ throw new Error(
105
+ `Invalid dirName: contains git pathspec characters: ${dirName}`,
106
+ );
107
+ }
108
+ }
109
+
81
110
  /**
82
111
  * Validate a page filename to prevent path traversal and ensure it is a safe
83
112
  * relative filename (e.g. "settings.html").
@@ -103,6 +132,121 @@ export function getAppsDir(): string {
103
132
  return dir;
104
133
  }
105
134
 
135
+ // ---------------------------------------------------------------------------
136
+ // Slug generation
137
+ // ---------------------------------------------------------------------------
138
+
139
+ /**
140
+ * Convert a name to a filesystem-safe slug.
141
+ * - Lowercase
142
+ * - Replace non-alphanumeric (except hyphens) with hyphens
143
+ * - Collapse consecutive hyphens, trim leading/trailing
144
+ * - Truncate to 60 chars (re-trim trailing hyphen)
145
+ * - Fall back to "app-<random>" if result is empty (e.g. emoji-only names)
146
+ */
147
+ export function slugify(name: string): string {
148
+ let slug = name
149
+ .toLowerCase()
150
+ .replace(/[^a-z0-9-]/g, "-")
151
+ .replace(/-{2,}/g, "-")
152
+ .replace(/^-+|-+$/g, "");
153
+
154
+ if (slug.length > 60) {
155
+ slug = slug.slice(0, 60).replace(/-+$/, "");
156
+ }
157
+
158
+ if (!slug) {
159
+ slug = `app-${randomUUID().slice(0, 8)}`;
160
+ }
161
+
162
+ return slug;
163
+ }
164
+
165
+ /**
166
+ * Generate a unique directory name from an app name.
167
+ * Appends -2, -3, etc. if the base slug collides with existing names.
168
+ */
169
+ export function generateAppDirName(
170
+ name: string,
171
+ existingNames: Set<string>,
172
+ ): string {
173
+ const base = slugify(name);
174
+ if (!existingNames.has(base)) return base;
175
+ let counter = 2;
176
+ while (existingNames.has(`${base}-${counter}`)) {
177
+ counter++;
178
+ }
179
+ return `${base}-${counter}`;
180
+ }
181
+
182
+ // ---------------------------------------------------------------------------
183
+ // App directory resolution (id -> dirName)
184
+ // ---------------------------------------------------------------------------
185
+
186
+ /** Cache of id -> dirName mappings to avoid repeated filesystem scans. */
187
+ const idToDirNameCache = new Map<string, string>();
188
+
189
+ /**
190
+ * Resolve an app's directory name and path from its ID.
191
+ * Scans JSON files if not cached. Falls back to `id` for pre-migration apps.
192
+ */
193
+ export function resolveAppDir(id: string): {
194
+ dirName: string;
195
+ appDir: string;
196
+ } {
197
+ validateId(id);
198
+
199
+ // Check cache first
200
+ const cached = idToDirNameCache.get(id);
201
+ if (cached) {
202
+ return { dirName: cached, appDir: join(getAppsDir(), cached) };
203
+ }
204
+
205
+ const dir = getAppsDir();
206
+ const entries = readdirSync(dir);
207
+
208
+ for (const entry of entries) {
209
+ if (!entry.endsWith(".json")) continue;
210
+ const filePath = join(dir, entry);
211
+ try {
212
+ const raw = readFileSync(filePath, "utf-8");
213
+ const parsed = JSON.parse(raw) as { id?: string; dirName?: string };
214
+ if (parsed.id === id) {
215
+ const dirName = parsed.dirName || id;
216
+ if (parsed.dirName) {
217
+ validateDirName(dirName);
218
+ }
219
+ idToDirNameCache.set(id, dirName);
220
+ return { dirName, appDir: join(dir, dirName) };
221
+ }
222
+ } catch {
223
+ // skip malformed files
224
+ }
225
+ }
226
+
227
+ // If no JSON file found, fall back to id (backward compat)
228
+ idToDirNameCache.set(id, id);
229
+ return { dirName: id, appDir: join(dir, id) };
230
+ }
231
+
232
+ /** Convenience wrapper: returns the app directory path for the given app ID. */
233
+ export function getAppDirPath(appId: string): string {
234
+ return resolveAppDir(appId).appDir;
235
+ }
236
+
237
+ /** Invalidate the id->dirName cache for a specific app or all apps. */
238
+ function invalidateDirNameCache(appId?: string): void {
239
+ if (appId) {
240
+ idToDirNameCache.delete(appId);
241
+ } else {
242
+ idToDirNameCache.clear();
243
+ }
244
+ }
245
+
246
+ // ---------------------------------------------------------------------------
247
+ // File path validation
248
+ // ---------------------------------------------------------------------------
249
+
106
250
  /**
107
251
  * Validate a relative file path within an app directory.
108
252
  * Prevents path traversal and access to protected directories.
@@ -123,7 +267,7 @@ function validateFilePath(appId: string, path: string): string {
123
267
  if (normalized === "records" || normalized.startsWith("records/")) {
124
268
  throw new Error(`Invalid file path: 'records/' directory is protected`);
125
269
  }
126
- const appDir = join(getAppsDir(), appId);
270
+ const appDir = getAppDirPath(appId);
127
271
  const resolved = resolve(appDir, path);
128
272
  // Ensure the resolved path is still within the app directory
129
273
  if (!resolved.startsWith(appDir + "/") && resolved !== appDir) {
@@ -132,7 +276,7 @@ function validateFilePath(appId: string, path: string): string {
132
276
  // Follow symlinks to the real path so a symlink inside the app directory
133
277
  // cannot escape the boundary. For non-existent paths, walk up to the
134
278
  // nearest existing ancestor and resolve it, then re-append trailing
135
- // components catches symlinked parent directories on new file writes.
279
+ // components -- catches symlinked parent directories on new file writes.
136
280
  let realResolved: string;
137
281
  if (existsSync(resolved)) {
138
282
  realResolved = realpathSync(resolved);
@@ -163,9 +307,13 @@ function validateFilePath(appId: string, path: string): string {
163
307
  return resolved;
164
308
  }
165
309
 
166
- /** Persist pages as individual files under ~/.vellum/apps/{appId}/pages/. */
167
- function savePages(appId: string, pages: Record<string, string>): void {
168
- const pagesDir = join(getAppsDir(), appId, "pages");
310
+ // ---------------------------------------------------------------------------
311
+ // Pages helpers
312
+ // ---------------------------------------------------------------------------
313
+
314
+ /** Persist pages as individual files under the app's pages/ subdirectory. */
315
+ function savePages(appDirPath: string, pages: Record<string, string>): void {
316
+ const pagesDir = join(appDirPath, "pages");
169
317
  mkdirSync(pagesDir, { recursive: true });
170
318
  for (const [filename, content] of Object.entries(pages)) {
171
319
  validatePageFilename(filename);
@@ -179,8 +327,8 @@ function savePages(appId: string, pages: Record<string, string>): void {
179
327
  }
180
328
 
181
329
  /** Load pages from disk. Returns undefined if no pages directory exists. */
182
- function loadPages(appId: string): Record<string, string> | undefined {
183
- const pagesDir = join(getAppsDir(), appId, "pages");
330
+ function loadPages(appDirPath: string): Record<string, string> | undefined {
331
+ const pagesDir = join(appDirPath, "pages");
184
332
  if (!existsSync(pagesDir)) return undefined;
185
333
  const entries = readdirSync(pagesDir);
186
334
  if (entries.length === 0) return undefined;
@@ -191,6 +339,33 @@ function loadPages(appId: string): Record<string, string> | undefined {
191
339
  return pages;
192
340
  }
193
341
 
342
+ // ---------------------------------------------------------------------------
343
+ // Existing dirName collector (for dedup)
344
+ // ---------------------------------------------------------------------------
345
+
346
+ /** Scan all JSON files and build a set of existing dirName values. */
347
+ function collectExistingDirNames(): Set<string> {
348
+ const dir = getAppsDir();
349
+ const entries = readdirSync(dir);
350
+ const names = new Set<string>();
351
+ for (const entry of entries) {
352
+ if (!entry.endsWith(".json")) continue;
353
+ try {
354
+ const raw = readFileSync(join(dir, entry), "utf-8");
355
+ const parsed = JSON.parse(raw) as { id?: string; dirName?: string };
356
+ // Use dirName if present, otherwise fall back to id (pre-migration)
357
+ names.add(parsed.dirName ?? parsed.id ?? entry.replace(/\.json$/, ""));
358
+ } catch {
359
+ // skip malformed
360
+ }
361
+ }
362
+ return names;
363
+ }
364
+
365
+ // ---------------------------------------------------------------------------
366
+ // CRUD operations
367
+ // ---------------------------------------------------------------------------
368
+
194
369
  export function createApp(params: {
195
370
  name: string;
196
371
  description?: string;
@@ -204,6 +379,11 @@ export function createApp(params: {
204
379
  }): AppDefinition {
205
380
  const dir = getAppsDir();
206
381
  const now = Date.now();
382
+
383
+ // Generate a unique dirName from the app name
384
+ const existingNames = collectExistingDirNames();
385
+ const dirName = generateAppDirName(params.name, existingNames);
386
+
207
387
  const app: AppDefinition = {
208
388
  id: randomUUID(),
209
389
  name: params.name,
@@ -216,10 +396,11 @@ export function createApp(params: {
216
396
  createdAt: now,
217
397
  updatedAt: now,
218
398
  formatVersion: params.formatVersion,
399
+ dirName,
219
400
  };
220
401
 
221
- // Write htmlDefinition to {appId}/index.html on disk
222
- const appDir = join(dir, app.id);
402
+ // Write htmlDefinition to {dirName}/index.html on disk
403
+ const appDir = join(dir, dirName);
223
404
  mkdirSync(appDir, { recursive: true });
224
405
  if (typeof params.htmlDefinition !== "string") {
225
406
  throw new Error(
@@ -230,21 +411,24 @@ export function createApp(params: {
230
411
 
231
412
  // Write preview to companion file to keep the JSON small
232
413
  if (params.preview) {
233
- writeFileSync(join(dir, `${app.id}.preview`), params.preview, "utf-8");
414
+ writeFileSync(join(dir, `${dirName}.preview`), params.preview, "utf-8");
234
415
  }
235
416
 
236
- // Strip htmlDefinition, pages, and preview from the JSON file only store metadata
417
+ // Strip htmlDefinition, pages, and preview from the JSON file -- only store metadata
237
418
  const {
238
419
  htmlDefinition: _html,
239
420
  pages: _pages,
240
421
  preview: _preview,
241
422
  ...jsonDef
242
423
  } = app;
243
- writeFileSync(join(dir, `${app.id}.json`), JSON.stringify(jsonDef, null, 2));
424
+ writeFileSync(join(dir, `${dirName}.json`), JSON.stringify(jsonDef, null, 2));
425
+
426
+ // Update cache
427
+ idToDirNameCache.set(app.id, dirName);
244
428
 
245
429
  // Persist additional pages as separate files
246
430
  if (params.pages && Object.keys(params.pages).length > 0) {
247
- savePages(app.id, params.pages);
431
+ savePages(appDir, params.pages);
248
432
  app.pages = params.pages;
249
433
  }
250
434
 
@@ -253,26 +437,27 @@ export function createApp(params: {
253
437
 
254
438
  export function getApp(id: string): AppDefinition | null {
255
439
  validateId(id);
440
+ const { dirName, appDir } = resolveAppDir(id);
256
441
  const dir = getAppsDir();
257
- const filePath = join(dir, `${id}.json`);
442
+ const filePath = join(dir, `${dirName}.json`);
258
443
  if (!existsSync(filePath)) return null;
259
444
  const raw = readFileSync(filePath, "utf-8");
260
445
  const app = JSON.parse(raw) as AppDefinition;
261
446
 
262
- // Read htmlDefinition from {appId}/index.html on disk
263
- const indexPath = join(dir, id, "index.html");
447
+ // Read htmlDefinition from {dirName}/index.html on disk
448
+ const indexPath = join(appDir, "index.html");
264
449
  app.htmlDefinition = existsSync(indexPath)
265
450
  ? readFileSync(indexPath, "utf-8")
266
451
  : (app.htmlDefinition ?? "");
267
452
 
268
453
  // Load preview from companion file
269
- const previewPath = join(dir, `${id}.preview`);
454
+ const previewPath = join(dir, `${dirName}.preview`);
270
455
  if (existsSync(previewPath)) {
271
456
  app.preview = readFileSync(previewPath, "utf-8");
272
457
  }
273
458
 
274
459
  // Load pages from disk
275
- const pages = loadPages(id);
460
+ const pages = loadPages(appDir);
276
461
  if (pages) {
277
462
  app.pages = pages;
278
463
  }
@@ -286,8 +471,9 @@ export function getApp(id: string): AppDefinition | null {
286
471
  */
287
472
  export function getAppPreview(id: string): string | null {
288
473
  validateId(id);
474
+ const { dirName } = resolveAppDir(id);
289
475
  const dir = getAppsDir();
290
- const previewPath = join(dir, `${id}.preview`);
476
+ const previewPath = join(dir, `${dirName}.preview`);
291
477
  if (existsSync(previewPath)) {
292
478
  return readFileSync(previewPath, "utf-8");
293
479
  }
@@ -334,6 +520,8 @@ export function updateApp(
334
520
  const existing = getApp(id);
335
521
  if (!existing) throw new Error(`App not found: ${id}`);
336
522
 
523
+ const { dirName, appDir } = resolveAppDir(id);
524
+
337
525
  // Extract pages, htmlDefinition, and preview before spreading into the JSON-persisted definition
338
526
  const {
339
527
  pages,
@@ -348,9 +536,8 @@ export function updateApp(
348
536
  updatedAt: Date.now(),
349
537
  };
350
538
 
351
- // Write htmlDefinition to {appId}/index.html if provided in updates
539
+ // Write htmlDefinition to {dirName}/index.html if provided in updates
352
540
  const dir = getAppsDir();
353
- const appDir = join(dir, id);
354
541
  if (htmlUpdate !== undefined) {
355
542
  updated.htmlDefinition = htmlUpdate;
356
543
  mkdirSync(appDir, { recursive: true });
@@ -360,29 +547,29 @@ export function updateApp(
360
547
  // Write preview to companion file
361
548
  if (previewUpdate !== undefined) {
362
549
  updated.preview = previewUpdate;
363
- writeFileSync(join(dir, `${id}.preview`), previewUpdate, "utf-8");
550
+ writeFileSync(join(dir, `${dirName}.preview`), previewUpdate, "utf-8");
364
551
  }
365
552
 
366
- // Don't persist htmlDefinition, pages, or preview in the JSON file they live as separate files
553
+ // Don't persist htmlDefinition, pages, or preview in the JSON file -- they live as separate files
367
554
  const {
368
555
  pages: _existingPages,
369
556
  htmlDefinition: _html,
370
557
  preview: _preview,
371
558
  ...jsonDef
372
559
  } = updated;
373
- writeFileSync(join(dir, `${id}.json`), JSON.stringify(jsonDef, null, 2));
560
+ writeFileSync(join(dir, `${dirName}.json`), JSON.stringify(jsonDef, null, 2));
374
561
 
375
562
  // Clear existing pages directory before writing new pages to prevent stale files
376
563
  if (pages && Object.keys(pages).length > 0) {
377
- const pagesDir = join(getAppsDir(), id, "pages");
564
+ const pagesDir = join(appDir, "pages");
378
565
  if (existsSync(pagesDir)) {
379
566
  rmSync(pagesDir, { recursive: true, force: true });
380
567
  }
381
- savePages(id, pages);
568
+ savePages(appDir, pages);
382
569
  }
383
570
 
384
571
  // Re-attach pages to the returned object
385
- const loadedPages = loadPages(id);
572
+ const loadedPages = loadPages(appDir);
386
573
  if (loadedPages) {
387
574
  updated.pages = loadedPages;
388
575
  }
@@ -392,17 +579,18 @@ export function updateApp(
392
579
 
393
580
  export function deleteApp(id: string): void {
394
581
  validateId(id);
582
+ const { dirName, appDir } = resolveAppDir(id);
395
583
  const dir = getAppsDir();
396
- const filePath = join(dir, `${id}.json`);
584
+ const filePath = join(dir, `${dirName}.json`);
397
585
  if (existsSync(filePath)) {
398
586
  unlinkSync(filePath);
399
587
  }
400
- const previewPath = join(dir, `${id}.preview`);
588
+ const previewPath = join(dir, `${dirName}.preview`);
401
589
  if (existsSync(previewPath)) {
402
590
  unlinkSync(previewPath);
403
591
  }
404
- const appDir = join(dir, id);
405
592
  rmSync(appDir, { recursive: true, force: true });
593
+ invalidateDirNameCache(id);
406
594
  }
407
595
 
408
596
  export function createAppRecord(
@@ -412,7 +600,7 @@ export function createAppRecord(
412
600
  validateId(appId);
413
601
  const app = getApp(appId);
414
602
  if (!app) throw new Error(`App not found: ${appId}`);
415
- const recordsDir = join(getAppsDir(), appId, "records");
603
+ const recordsDir = join(getAppDirPath(appId), "records");
416
604
  mkdirSync(recordsDir, { recursive: true });
417
605
  const now = Date.now();
418
606
  const record: AppRecord = {
@@ -435,7 +623,7 @@ export function getAppRecord(
435
623
  ): AppRecord | null {
436
624
  validateId(appId);
437
625
  validateId(recordId);
438
- const filePath = join(getAppsDir(), appId, "records", `${recordId}.json`);
626
+ const filePath = join(getAppDirPath(appId), "records", `${recordId}.json`);
439
627
  if (!existsSync(filePath)) return null;
440
628
  const raw = readFileSync(filePath, "utf-8");
441
629
  return JSON.parse(raw) as AppRecord;
@@ -443,7 +631,7 @@ export function getAppRecord(
443
631
 
444
632
  export function queryAppRecords(appId: string): AppRecord[] {
445
633
  validateId(appId);
446
- const recordsDir = join(getAppsDir(), appId, "records");
634
+ const recordsDir = join(getAppDirPath(appId), "records");
447
635
  if (!existsSync(recordsDir)) return [];
448
636
  const entries = readdirSync(recordsDir);
449
637
  const records: AppRecord[] = [];
@@ -474,7 +662,7 @@ export function updateAppRecord(
474
662
  updatedAt: Date.now(),
475
663
  };
476
664
  writeFileSync(
477
- join(getAppsDir(), appId, "records", `${recordId}.json`),
665
+ join(getAppDirPath(appId), "records", `${recordId}.json`),
478
666
  JSON.stringify(updated, null, 2),
479
667
  );
480
668
  return updated;
@@ -483,7 +671,7 @@ export function updateAppRecord(
483
671
  export function deleteAppRecord(appId: string, recordId: string): void {
484
672
  validateId(appId);
485
673
  validateId(recordId);
486
- const filePath = join(getAppsDir(), appId, "records", `${recordId}.json`);
674
+ const filePath = join(getAppDirPath(appId), "records", `${recordId}.json`);
487
675
  if (existsSync(filePath)) {
488
676
  unlinkSync(filePath);
489
677
  }
@@ -494,12 +682,13 @@ export function deleteAppRecord(appId: string, recordId: string): void {
494
682
  // ---------------------------------------------------------------------------
495
683
 
496
684
  /**
497
- * Recursively list all files under `{appId}/`, excluding `records/` subdirectory
498
- * and `app.json`. Returns relative paths like `index.html`, `styles.css`, `js/app.js`.
685
+ * Recursively list all files under the app's directory, excluding `records/`
686
+ * subdirectory and `app.json`. Returns relative paths like `index.html`,
687
+ * `styles.css`, `js/app.js`.
499
688
  */
500
689
  export function listAppFiles(appId: string): string[] {
501
690
  validateId(appId);
502
- const appDir = join(getAppsDir(), appId);
691
+ const appDir = getAppDirPath(appId);
503
692
  if (!existsSync(appDir)) return [];
504
693
 
505
694
  const results: string[] = [];