@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
package/ARCHITECTURE.md CHANGED
@@ -783,26 +783,26 @@ All client-server communication uses HTTP for request/response operations and Se
783
783
 
784
784
  The daemon emits two distinct error message types via SSE:
785
785
 
786
- | Message type | Scope | Purpose | Payload |
787
- | -------------------- | ------------------- | -------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- |
788
- | `conversation_error` | Conversation-scoped | Typed, actionable failures during conversation runtime (e.g., provider network error, rate limit, API failure) | `sessionId`, `code` (typed enum), `userMessage`, `retryable`, `debugDetails?` |
789
- | `error` | Global | Generic, non-session failures (e.g., daemon startup errors, unknown message types) | `message` (string) |
790
-
791
- **Design rationale:** `conversation_error` carries structured metadata (error code, retryable flag, debug details) so the client can present actionable UI — a toast with retry/dismiss buttons — rather than a generic error banner. The older `error` type is retained for backward compatibility with non-session contexts.
792
-
793
- ### Session Error Codes
794
-
795
- | Code | Meaning | Retryable |
796
- | --------------------------- | ----------------------------------------------------------------------- | --------- |
797
- | `PROVIDER_NETWORK` | Unable to reach the LLM provider (connection refused, timeout, DNS) | Yes |
798
- | `PROVIDER_RATE_LIMIT` | LLM provider rate-limited the request (HTTP 429) | Yes |
799
- | `PROVIDER_API` | Provider returned a server error (5xx) or retryable 4xx | Yes |
800
- | `PROVIDER_BILLING` | Invalid/expired API key or insufficient credits (HTTP 401, billing 4xx) | No |
801
- | `CONTEXT_TOO_LARGE` | Request exceeds the model's context window (HTTP 413, token limit) | No |
802
- | `SESSION_ABORTED` | Non-user abort interrupted the request | Yes |
803
- | `SESSION_PROCESSING_FAILED` | Catch-all for unexpected processing failures | No |
804
- | `REGENERATE_FAILED` | Failed to regenerate a previous response | Yes |
805
- | `UNKNOWN` | Unrecognized error that does not match any specific category | No |
786
+ | Message type | Scope | Purpose | Payload |
787
+ | -------------------- | ------------------- | -------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- |
788
+ | `conversation_error` | Conversation-scoped | Typed, actionable failures during conversation runtime (e.g., provider network error, rate limit, API failure) | `conversationId`, `code` (typed enum), `userMessage`, `retryable`, `debugDetails?` |
789
+ | `error` | Global | Generic, non-conversation failures (e.g., daemon startup errors, unknown message types) | `message` (string) |
790
+
791
+ **Design rationale:** `conversation_error` carries structured metadata (error code, retryable flag, debug details) so the client can present actionable UI — a toast with retry/dismiss buttons — rather than a generic error banner. The older `error` type is retained for backward compatibility with non-conversation contexts.
792
+
793
+ ### Conversation Error Codes
794
+
795
+ | Code | Meaning | Retryable |
796
+ | -------------------------------- | ----------------------------------------------------------------------- | --------- |
797
+ | `PROVIDER_NETWORK` | Unable to reach the LLM provider (connection refused, timeout, DNS) | Yes |
798
+ | `PROVIDER_RATE_LIMIT` | LLM provider rate-limited the request (HTTP 429) | Yes |
799
+ | `PROVIDER_API` | Provider returned a server error (5xx) or retryable 4xx | Yes |
800
+ | `PROVIDER_BILLING` | Invalid/expired API key or insufficient credits (HTTP 401, billing 4xx) | No |
801
+ | `CONTEXT_TOO_LARGE` | Request exceeds the model's context window (HTTP 413, token limit) | No |
802
+ | `CONVERSATION_ABORTED` | Non-user abort interrupted the request | Yes |
803
+ | `CONVERSATION_PROCESSING_FAILED` | Catch-all for unexpected processing failures | No |
804
+ | `REGENERATE_FAILED` | Failed to regenerate a previous response | Yes |
805
+ | `UNKNOWN` | Unrecognized error that does not match any specific category | No |
806
806
 
807
807
  ### Error Classification
808
808
 
@@ -826,7 +826,7 @@ sequenceDiagram
826
826
 
827
827
  Note over Daemon: LLM call fails or<br/>processing error occurs
828
828
  Daemon->>Daemon: classifyConversationError(error, ctx)
829
- Daemon->>DC: conversation_error {sessionId, code,<br/>userMessage, retryable, debugDetails?}
829
+ Daemon->>DC: conversation_error {conversationId, code,<br/>userMessage, retryable, debugDetails?}
830
830
  DC->>DC: broadcast to all subscribers
831
831
  DC->>VM: subscribe() stream delivers message
832
832
  VM->>VM: set conversationError property<br/>clear isThinking / isCancelling
@@ -837,7 +837,7 @@ sequenceDiagram
837
837
  alt User taps Retry (retryable == true)
838
838
  UI->>VM: retryAfterConversationError()
839
839
  VM->>VM: dismissConversationError()<br/>+ regenerateLastMessage()
840
- VM->>DC: regenerate {sessionId}
840
+ VM->>DC: regenerate {conversationId}
841
841
  DC->>Daemon: HTTP POST /v1/messages
842
842
  else User taps Dismiss
843
843
  UI->>VM: dismissConversationError()
@@ -939,7 +939,7 @@ graph TB
939
939
  end
940
940
 
941
941
  SUBMIT --> SLASH_CHECK
942
- SLASH_CHECK -->|"Yes (/model, /status, etc.)"| QA_ROUTE
942
+ SLASH_CHECK -->|"Yes (/models, /status, etc.)"| QA_ROUTE
943
943
  SLASH_CHECK -->|"No"| VOICE_CHECK
944
944
  VOICE_CHECK -->|"Yes"| QA_ROUTE
945
945
  VOICE_CHECK -->|"No"| CLASSIFIER
@@ -1017,12 +1017,12 @@ graph TB
1017
1017
 
1018
1018
  INPUT --> RESOLVE
1019
1019
  RESOLVE -->|"kind: passthrough"| PASSTHROUGH
1020
- RESOLVE -->|"kind: unknown<br/>(/model, /status, /commands, /pair,<br/>/models, provider shortcuts)"| HANDLED
1020
+ RESOLVE -->|"kind: unknown<br/>(/models, /status, /commands, /pair)"| HANDLED
1021
1021
  ```
1022
1022
 
1023
1023
  Key behaviors:
1024
1024
 
1025
- - **Built-in commands**: `/model`, `/models`, `/status`, `/commands`, `/pair`, and provider shortcuts (`/opus`, `/sonnet`, `/gpt4`, etc.) are handled directly by `resolveSlash()`. A deterministic `assistant_text_delta` + `message_complete` is emitted. No message persistence or model call occurs.
1025
+ - **Built-in commands**: `/models`, `/status`, `/commands`, and `/pair` are handled directly by `resolveSlash()`. A deterministic `assistant_text_delta` + `message_complete` is emitted. No message persistence or model call occurs.
1026
1026
  - **Passthrough**: Any input that does not match a built-in command passes through to the normal agent loop, including slash-like tokens that are not recognized.
1027
1027
  - **Queue**: Queued messages receive the same slash resolution.
1028
1028
 
@@ -1183,7 +1183,7 @@ The following capabilities ship as bundled skills in `assistant/src/config/bundl
1183
1183
  | `claude-code` | Claude Code tool | Delegate coding tasks to Claude Code subprocess |
1184
1184
  | `computer-use` | `computer_use_observe`, `computer_use_click`, `computer_use_type_text`, `computer_use_key`, `computer_use_scroll`, `computer_use_drag`, `computer_use_wait`, `computer_use_open_app`, `computer_use_run_applescript`, `computer_use_done`, `computer_use_respond` | Computer-use proxy tools — preactivated via `preactivatedSkillIds` in desktop sessions. Each tool forwards actions to the connected macOS client via `HostCuProxy`, which handles request/resolve proxying, step counting, loop detection, and observation formatting within the unified agent loop. |
1185
1185
  | `weather` | `get-weather` | Fetch current weather data |
1186
- | `app-builder` | `app_create`, `app_list`, `app_query`, `app_update`, `app_delete`, `app_file_list`, `app_file_read`, `app_file_edit`, `app_file_write` | Dynamic app authoring — CRUD and file-level editing for persistent apps (activated via `skill_load app-builder`; `app_open` remains a core proxy tool) |
1186
+ | `app-builder` | `app_create`, `app_delete`, `app_refresh`, `app_generate_icon` | Dynamic app authoring — create and manage persistent apps; file editing uses generic file tools plus `app_refresh` (activated via `skill_load app-builder`; `app_open` remains a core proxy tool) |
1187
1187
  | `self-upgrade` | (instruction-only) | Self-improvement workflow |
1188
1188
  | `start-the-day` | (instruction-only) | Morning briefing routine |
1189
1189
 
@@ -1525,7 +1525,7 @@ sequenceDiagram
1525
1525
 
1526
1526
  ### Key design decisions
1527
1527
 
1528
- - **Recursion guard**: A module-level `Set<sessionId>` prevents concurrent swarms within the same session while allowing independent sessions to run their own swarms in parallel.
1528
+ - **Recursion guard**: A module-level `Set<conversationId>` prevents concurrent swarms within the same conversation while allowing independent conversations to run their own swarms in parallel.
1529
1529
  - **Abort signal**: The tool checks `context.signal?.aborted` before planning and before execution. The signal is also forwarded into `executeSwarm` and the worker backend, enabling cooperative cancellation of in-flight workers.
1530
1530
  - **DAG scheduling**: Tasks with dependencies are topologically ordered. Independent tasks run in parallel up to `maxWorkers`.
1531
1531
  - **Per-task retries**: Failed tasks retry up to `maxRetriesPerTask` before being marked failed. Dependents are transitively blocked.
@@ -1568,7 +1568,7 @@ sequenceDiagram
1568
1568
 
1569
1569
  Note over Daemon: Processing previous request...<br/>Reaches safe tool-loop checkpoint
1570
1570
 
1571
- Daemon-->>DC: generation_handoff (sessionId, queuedCount)
1571
+ Daemon-->>DC: generation_handoff (conversationId, queuedCount)
1572
1572
  Note over Daemon: Daemon yields current generation
1573
1573
 
1574
1574
  Daemon-->>DC: message_dequeued
@@ -1585,7 +1585,7 @@ sequenceDiagram
1585
1585
 
1586
1586
  ## Trace System — Debug Panel Data Flow
1587
1587
 
1588
- The trace system provides real-time observability of daemon session internals. Each session creates a `TraceEmitter` that emits structured `trace_event` SSE events as the session processes requests, makes LLM calls, and executes tools.
1588
+ The trace system provides real-time observability of daemon conversation internals. Each conversation creates a `TraceEmitter` that emits structured `trace_event` SSE events as the conversation processes requests, makes LLM calls, and executes tools.
1589
1589
 
1590
1590
  ```mermaid
1591
1591
  sequenceDiagram
@@ -1636,41 +1636,41 @@ sequenceDiagram
1636
1636
  TE-->>DC: trace_event (message_complete)
1637
1637
  DC-->>TS: ingest()
1638
1638
 
1639
- Note over TS: Events deduplicated by eventId,<br/>ordered by sequence + timestampMs,<br/>grouped by session and requestId,<br/>capped at 5000 per session
1639
+ Note over TS: Events deduplicated by eventId,<br/>ordered by sequence + timestampMs,<br/>grouped by conversation and requestId,<br/>capped at 5000 per conversation
1640
1640
 
1641
- TS-->>DP: @Published eventsBySession
1641
+ TS-->>DP: @Published eventsByConversation
1642
1642
  Note over DP: Metrics strip: requests, LLM calls,<br/>tokens (in/out), avg latency, failures<br/>Timeline: events grouped by requestId
1643
1643
  ```
1644
1644
 
1645
1645
  ### Trace Event Kinds
1646
1646
 
1647
- Events emitted during a session lifecycle:
1648
-
1649
- | Kind | Emitted by | When |
1650
- | --------------------------- | ------------------ | ----------------------------------------------------------------------------------------------- |
1651
- | `request_received` | Handlers / Session | User message or surface action arrives |
1652
- | `request_queued` | Handlers / Session | Message queued while session is busy |
1653
- | `request_dequeued` | Session | Queued message begins processing |
1654
- | `llm_call_started` | Session | LLM API call initiated |
1655
- | `llm_call_finished` | Session | LLM API call completed (carries `inputTokens`, `outputTokens`, `latencyMs`) |
1656
- | `assistant_message` | Session | Assistant response assembled (carries `toolUseCount`) |
1657
- | `tool_started` | ToolTraceListener | Tool execution begins |
1658
- | `tool_permission_requested` | ToolTraceListener | Permission check needed (carries `riskLevel`) |
1659
- | `tool_permission_decided` | ToolTraceListener | Permission granted or denied (carries `decision`) |
1660
- | `tool_finished` | ToolTraceListener | Tool execution completed (carries `durationMs`) |
1661
- | `tool_failed` | ToolTraceListener | Tool execution failed (carries `durationMs`) |
1662
- | `secret_detected` | ToolTraceListener | Secret found in tool output |
1663
- | `generation_handoff` | Session | Yielding to next queued message |
1664
- | `message_complete` | Session | Full request processing finished |
1665
- | `generation_cancelled` | Session | User cancelled the generation |
1666
- | `request_error` | Handlers / Session | Unrecoverable error during processing (includes queue-full rejection and persist-failure paths) |
1647
+ Events emitted during a conversation lifecycle:
1648
+
1649
+ | Kind | Emitted by | When |
1650
+ | --------------------------- | ----------------------- | ----------------------------------------------------------------------------------------------- |
1651
+ | `request_received` | Handlers / Conversation | User message or surface action arrives |
1652
+ | `request_queued` | Handlers / Conversation | Message queued while conversation is busy |
1653
+ | `request_dequeued` | Conversation | Queued message begins processing |
1654
+ | `llm_call_started` | Conversation | LLM API call initiated |
1655
+ | `llm_call_finished` | Conversation | LLM API call completed (carries `inputTokens`, `outputTokens`, `latencyMs`) |
1656
+ | `assistant_message` | Conversation | Assistant response assembled (carries `toolUseCount`) |
1657
+ | `tool_started` | ToolTraceListener | Tool execution begins |
1658
+ | `tool_permission_requested` | ToolTraceListener | Permission check needed (carries `riskLevel`) |
1659
+ | `tool_permission_decided` | ToolTraceListener | Permission granted or denied (carries `decision`) |
1660
+ | `tool_finished` | ToolTraceListener | Tool execution completed (carries `durationMs`) |
1661
+ | `tool_failed` | ToolTraceListener | Tool execution failed (carries `durationMs`) |
1662
+ | `secret_detected` | ToolTraceListener | Secret found in tool output |
1663
+ | `generation_handoff` | Conversation | Yielding to next queued message |
1664
+ | `message_complete` | Conversation | Full request processing finished |
1665
+ | `generation_cancelled` | Conversation | User cancelled the generation |
1666
+ | `request_error` | Handlers / Conversation | Unrecoverable error during processing (includes queue-full rejection and persist-failure paths) |
1667
1667
 
1668
1668
  ### Architecture
1669
1669
 
1670
- - **TraceEmitter** (daemon, per-session): Constructed with a `sessionId` and a `sendToClient` callback. Maintains a monotonic sequence counter for stable ordering. Truncates summaries to 200 chars and attribute values to 500 chars. Each call to `emit()` sends a `trace_event` SSE event to connected clients.
1671
- - **ToolTraceListener** (daemon): Subscribes to the session's `EventBus` via `onAny()` and translates tool domain events (`tool.execution.started`, `tool.execution.finished`, `tool.execution.failed`, `tool.permission.requested`, `tool.permission.decided`, `tool.secret.detected`) into trace events through the `TraceEmitter`.
1670
+ - **TraceEmitter** (daemon, per-conversation): Constructed with a `conversationId` and a `sendToClient` callback. Maintains a monotonic sequence counter for stable ordering. Truncates summaries to 200 chars and attribute values to 500 chars. Each call to `emit()` sends a `trace_event` SSE event to connected clients.
1671
+ - **ToolTraceListener** (daemon): Subscribes to the conversation's `EventBus` via `onAny()` and translates tool domain events (`tool.execution.started`, `tool.execution.finished`, `tool.execution.failed`, `tool.permission.requested`, `tool.permission.decided`, `tool.secret.detected`) into trace events through the `TraceEmitter`.
1672
1672
  - **DaemonClient** (Swift, shared): Decodes `trace_event` SSE events into `TraceEventMessage` structs and invokes the `onTraceEvent` callback.
1673
- - **TraceStore** (Swift, macOS): `@MainActor ObservableObject` that ingests `TraceEventMessage` structs. Deduplicates by `eventId`, maintains stable sort order (sequence, then timestampMs, then insertion order), groups events by session and requestId, and enforces a retention cap of 5,000 events per session. Each request group is classified with a terminal status: `completed` (via `message_complete`), `cancelled` (via `generation_cancelled`), `handedOff` (via `generation_handoff`), `error` (via `request_error` or any event with `status == "error"`), or `active` (no terminal event yet).
1673
+ - **TraceStore** (Swift, macOS): `@MainActor ObservableObject` that ingests `TraceEventMessage` structs. Deduplicates by `eventId`, maintains stable sort order (sequence, then timestampMs, then insertion order), groups events by conversation and requestId, and enforces a retention cap of 5,000 events per conversation. Each request group is classified with a terminal status: `completed` (via `message_complete`), `cancelled` (via `generation_cancelled`), `handedOff` (via `generation_handoff`), `error` (via `request_error` or any event with `status == "error"`), or `active` (no terminal event yet).
1674
1674
  - **DebugPanel** (Swift, macOS): SwiftUI view that observes `TraceStore`. Displays a metrics strip (request count, LLM calls, total tokens, average latency, tool failures) and a `TraceTimelineView` showing events grouped by requestId with color-coded status indicators. The timeline auto-scrolls to new events while the user is at the bottom; scrolling up pauses auto-scroll and shows a "Jump to bottom" button that resumes it.
1675
1675
 
1676
1676
  ---
@@ -1,6 +1,6 @@
1
1
  # Integrations Architecture
2
2
 
3
- OAuth, messaging adapters, script proxy, and asset-tool architecture.
3
+ OAuth, messaging adapters, script proxy, and conversation disk view architecture.
4
4
 
5
5
  ## Integrations — OAuth2 + Unified Messaging
6
6
 
@@ -514,90 +514,85 @@ The proxy subsystem is fully wired, including credential injection. The session
514
514
 
515
515
  ---
516
516
 
517
- ## Asset Search and Materialize Cross-Conversation Media Reuse
517
+ ## Conversation Disk ViewFilesystem-Based Conversation Access
518
518
 
519
- The `asset_search` and `asset_materialize` tools enable the assistant to discover and use previously uploaded media assets (images, documents, audio) across conversations. Assets are stored as base64-encoded blobs in the `attachments` table and linked to messages via the `message_attachments` join table.
519
+ The conversation disk view projects conversation metadata, messages, and attachments to a browsable filesystem layout under `~/.vellum/workspace/conversations/`. This enables the assistant to search, read, and manipulate conversation data (including media attachments) using standard file tools (`read_file`, `glob`, `grep`) rather than dedicated asset search tools.
520
520
 
521
- ### Asset Discovery and Materialization Flow
521
+ ### Directory Layout
522
522
 
523
- ```mermaid
524
- sequenceDiagram
525
- participant Model as LLM
526
- participant Search as asset_search tool
527
- participant DB as SQLite (attachments)
528
- participant Visibility as media-visibility-policy
529
- participant Materialize as asset_materialize tool
530
- participant Sandbox as Sandbox filesystem
531
-
532
- Model->>Search: search(mime_type: "image/*", recency: "last_7_days")
533
- Search->>DB: query attachments (filters)
534
- DB-->>Search: matching rows (metadata only, no base64)
535
- Search->>Visibility: filterVisibleAttachments(results, currentContext)
536
- Note over Visibility: Private-conversation attachments filtered out<br/>unless viewer is in the same conversation
537
- Visibility-->>Search: visible results
538
- Search-->>Model: metadata list (IDs, filenames, types, sizes)
539
-
540
- Model->>Materialize: materialize(attachment_id, destination_path)
541
- Materialize->>Materialize: sandboxPolicy(destination_path)
542
- Materialize->>DB: load attachment (including base64 data)
543
- Materialize->>Visibility: isAttachmentVisible(attachmentCtx, currentCtx)
544
- Note over Visibility: Second visibility check at materialize time<br/>prevents TOCTOU between search and materialize
545
- Materialize->>Materialize: size check (max 100 MB)
546
- Materialize->>Sandbox: write decoded bytes to destination
547
- Materialize-->>Model: "Materialized 'photo.jpg' to /workspace/media/photo.jpg"
548
- ```
523
+ Each conversation is projected to a directory named `{isoDate}_{id}`:
549
524
 
550
- ### Private Conversation Visibility Gate
525
+ ```
526
+ ~/.vellum/workspace/conversations/
527
+ 2025-01-15T10-30-00.000Z_abc123/
528
+ meta.json # Conversation metadata (id, title, type, channel, timestamps)
529
+ messages.jsonl # Flattened message log (one JSON object per line)
530
+ attachments/ # Decoded attachment files (original filenames, collision-safe)
531
+ photo.png
532
+ document.pdf
533
+ ```
551
534
 
552
- Attachments from private conversations are only visible to the same private conversation. Standard-conversation attachments are visible everywhere. The policy is enforced at both the search and materialize stages to prevent cross-conversation data leakage.
535
+ ### Write-Through Sync
553
536
 
554
- ```mermaid
555
- graph TB
556
- subgraph "Visibility Rules"
557
- ATT_STD["Attachment from<br/>standard conversation"]
558
- ATT_PVT["Attachment from<br/>private conversation"]
537
+ The disk view is updated at the daemon level, not automatically by the DB CRUD layer. Conversation creation, metadata updates, and deletion are synced from `conversation-crud.ts`, but message sync (`syncMessageToDisk`) is only called from daemon-level code paths (e.g. `conversation-messaging.ts`) — not from the CRUD `addMessage()` function. This means `messages.jsonl` reflects messages processed through the daemon's messaging pipeline, not every message write. All disk writes are best-effort; failures are logged but never thrown, so the disk view cannot break DB operations.
559
538
 
560
- VIEWER_ANY["Any conversation<br/>(standard or private)"]
561
- VIEWER_SAME["Same private conversation<br/>(matching conversationId)"]
562
- VIEWER_OTHER["Different private conversation<br/>or standard conversation"]
563
- end
539
+ > **Privacy note:** Conversation disk-view files live under `~/.vellum/workspace/conversations/` and are **excluded** from diagnostic log exports ("Send logs to Vellum") via the `WORKSPACE_SKIP_DIRS` filter in `log-export-routes.ts`. However, the SQLite database (`assistant.db`) is included in exports as a SQL dump, and it contains conversation messages and attachment data in its tables. The disk-view exclusion prevents the raw conversation files and decoded attachments from being exported, but conversation content stored in the database may still be present in the export.
564
540
 
565
- ATT_STD -->|"always visible"| VIEWER_ANY
566
- ATT_PVT -->|"visible"| VIEWER_SAME
567
- ATT_PVT -->|"hidden"| VIEWER_OTHER
541
+ ```mermaid
542
+ sequenceDiagram
543
+ participant CRUD as conversation-crud.ts
544
+ participant Daemon as conversation-messaging.ts
545
+ participant DiskView as conversation-disk-view.ts
546
+ participant FS as Filesystem
547
+
548
+ Note over CRUD,FS: Conversation creation (CRUD layer)
549
+ CRUD->>CRUD: INSERT conversation row
550
+ CRUD->>DiskView: initConversationDir(conv)
551
+ DiskView->>FS: mkdir + write meta.json
552
+
553
+ Note over Daemon,FS: Message insertion (daemon layer)
554
+ Daemon->>CRUD: addMessage(convId, role, content)
555
+ CRUD->>CRUD: INSERT message row
556
+ Daemon->>DiskView: syncMessageToDisk(convId, msgId, createdAtMs)
557
+ DiskView->>DiskView: flattenContentBlocks(content)
558
+ DiskView->>FS: append JSONL record to messages.jsonl
559
+ DiskView->>FS: reference files already materialized in attachments/
560
+
561
+ Note over CRUD,FS: Conversation update (CRUD layer)
562
+ CRUD->>CRUD: UPDATE conversation row
563
+ CRUD->>DiskView: updateMetaFile(conv)
564
+ DiskView->>FS: rewrite meta.json
565
+
566
+ Note over CRUD,FS: Conversation deletion (CRUD layer)
567
+ CRUD->>CRUD: DELETE conversation row
568
+ CRUD->>DiskView: removeConversationDir(id, createdAtMs)
569
+ DiskView->>FS: rm -rf conversation directory
568
570
  ```
569
571
 
570
- **Source conversation lookup**: The `getAttachmentSourceConversations()` function traces an attachment's lineage through `message_attachments` -> `messages` -> `conversations` to determine which conversations it belongs to and whether any of them are private.
572
+ ### Content Flattening
571
573
 
572
- **Mixed-source attachments**: If an attachment is linked to messages in both standard and private conversations (e.g., the user shared the same file in two conversations), the attachment is treated as globally visible because at least one source is non-private.
574
+ Message content (stored as JSON `ContentBlock[]` in the DB) is flattened for the JSONL log:
573
575
 
574
- **Orphan attachments**: Attachments with no message linkage (orphans) are treated as universally visible rather than hidden, since they have no private-conversation provenance.
576
+ - **Text blocks** are concatenated into a single `content` string.
577
+ - **Tool use blocks** are extracted into a `toolCalls` array (`{ name, input }`).
578
+ - **Tool result blocks** are extracted into a `toolResults` array.
579
+ - **Image/file blocks** are skipped — they are represented via the `attachments/` subdirectory instead.
575
580
 
576
- ### Search Capabilities
581
+ ### Attachment Projection
577
582
 
578
- | Parameter | Type | Description |
579
- | ----------------- | ------ | ---------------------------------------------------------------------------------------------- |
580
- | `mime_type` | string | MIME type filter with wildcard support (`image/*`, `application/pdf`) |
581
- | `filename` | string | Case-insensitive substring match on original filename |
582
- | `recency` | enum | Time-based filter: `last_hour`, `last_24_hours`, `last_7_days`, `last_30_days`, `last_90_days` |
583
- | `conversation_id` | string | Scope results to attachments in a specific conversation |
584
- | `limit` | number | Maximum results (default 20, max 100) |
583
+ Attachments are materialized into `conversations/<conversation>/attachments/` as soon as they are linked to a message. During disk-view sync, the JSONL record reuses those filenames directly and only falls back to materializing legacy rows that have not been projected yet. Filename collisions are still resolved by appending a numeric suffix (e.g., `photo-2.png`, `photo-3.png`).
585
584
 
586
- ### Materialize Safeguards
585
+ ### Backfill Migration
587
586
 
588
- - **Sandbox path enforcement**: Destination path must resolve inside the sandbox working directory
589
- - **Size limit**: 100 MB ceiling prevents materializing excessively large attachments
590
- - **Double visibility check**: Both `asset_search` and `asset_materialize` independently verify visibility, preventing TOCTOU races between search and use
591
- - **Risk level**: Both tools are `RiskLevel.Low` since they read existing data and write only within the sandbox
587
+ Existing conversations created before the disk view was introduced are backfilled by workspace migration `009-backfill-conversation-disk-view`, which replays all conversations and their messages through the disk-view sync functions.
592
588
 
593
589
  ### Key Source Files
594
590
 
595
- | File | Role |
596
- | ------------------------------------------------- | --------------------------------------------------------------------------------------------- |
597
- | `assistant/src/tools/assets/search.ts` | `asset_search` toolcross-conversation attachment metadata search with visibility filtering |
598
- | `assistant/src/tools/assets/materialize.ts` | `asset_materialize` tooldecode and write attachment to sandbox path |
599
- | `assistant/src/daemon/media-visibility-policy.ts` | Pure policy module`isAttachmentVisible()`, `filterVisibleAttachments()` |
600
- | `assistant/src/memory/schema.ts` | `attachments` and `message_attachments` table schemas |
601
- | `assistant/src/memory/conversation-crud.ts` | `getConversationType()` — conversation type lookup for visibility context |
591
+ | File | Role |
592
+ | --------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |
593
+ | `assistant/src/memory/conversation-disk-view.ts` | Disk view module init, update, sync, remove, content flattening |
594
+ | `assistant/src/memory/conversation-crud.ts` | DB CRUD layer calls init, update, and remove disk-view functions (not message sync) |
595
+ | `assistant/src/daemon/conversation-messaging.ts` | Daemon messaging pipelinecalls `syncMessageToDisk` after message insertion |
596
+ | `assistant/src/workspace/migrations/009-backfill-conversation-disk-view.ts` | Backfill migration for pre-existing conversations |
602
597
 
603
598
  ---
@@ -46,7 +46,7 @@ CES exposes exactly three tools to the assistant, registered as a **deliberate e
46
46
 
47
47
  ### Tool registration
48
48
 
49
- CES tools use the standard `class ... implements Tool` registration pattern. This is explicitly approved as a deliberate exception to the no-new-tools policy because:
49
+ CES tools use the standard `class ... implements Tool` registration pattern. These are justified exceptions to the general preference for skills because:
50
50
 
51
51
  - The security boundary requires that credential materialization happens in a separate process
52
52
  - Skill scripts run inside the assistant process and cannot enforce the hard isolation invariant
@@ -223,7 +223,7 @@ These invariants are enforced by guard tests and code review:
223
223
 
224
224
  1. **No cross-package source imports**: `assistant/` must not import from `credential-executor/` and vice versa. Communication is RPC only. Shared types flow through `packages/` only.
225
225
  2. **No credential values in assistant process memory**: The assistant sends credential handles (not values) to CES. CES materializes and uses them internally.
226
- 3. **CES tools are the only approved exception to the no-new-tools policy** for credential-bearing execution. All other credential use continues through the existing broker for local deployments.
226
+ 3. **CES tools justify tool registrations over skills** for credential-bearing execution because of the hard process-boundary isolation requirement. All other credential use continues through the existing broker for local deployments.
227
227
  4. **Grants and audit logs are CES-internal**: The assistant cannot read CES grant tables or audit logs directly. CES exposes grant status and audit summaries via RPC responses.
228
228
  5. **No generic authenticated HTTP clients in secure commands**: `curl`, `wget`, `httpie`, interpreters, and shell trampolines are structurally denied as secure command entrypoints. This is checked at manifest validation and re-checked at execution time.
229
229
  6. **Managed CES container runs as non-root**: The CES Docker image runs as `uid 1001` (user `ces`). The CES data volume is owned by this user.
@@ -400,5 +400,5 @@ The following capabilities are intentionally deferred beyond v1:
400
400
 
401
401
  - [Security architecture](architecture/security.md) — existing credential broker and permission model
402
402
  - [AGENTS.md](../../AGENTS.md) — tooling direction and CES exception
403
- - [Tools AGENTS.md](../src/tools/AGENTS.md) — no-new-tools policy and CES exception
403
+ - [Tools AGENTS.md](../src/tools/AGENTS.md) — tooling direction and CES exception
404
404
  - [Network traffic matrix](../../../vellum-assistant-platform/docs/network-traffic-matrix.md) — managed pod network policies
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vellumai/assistant",
3
- "version": "0.5.0",
3
+ "version": "0.5.2",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": "./src/index.ts"
@@ -1629,4 +1629,115 @@ describe("AgentLoop", () => {
1629
1629
  );
1630
1630
  expect(textBlock!.text).toBe("Normal response with no placeholders.");
1631
1631
  });
1632
+
1633
+ // Tool error retry nudge — when a tool returns isError: true, the loop
1634
+ // should inject a system_notice nudging the LLM to retry instead of ending.
1635
+ test("injects retry nudge system_notice when tool returns an error", async () => {
1636
+ const { provider, calls } = createMockProvider([
1637
+ // First turn: LLM calls a tool that errors
1638
+ toolUseResponse("t1", "read_file", { path: "/missing.txt" }),
1639
+ // Second turn: LLM retries after seeing the error + nudge
1640
+ toolUseResponse("t2", "read_file", { path: "/existing.txt" }),
1641
+ // Third turn: LLM responds with success
1642
+ textResponse("Got the file."),
1643
+ ]);
1644
+
1645
+ let callCount = 0;
1646
+ const toolExecutor = async (
1647
+ _name: string,
1648
+ _input: Record<string, unknown>,
1649
+ ) => {
1650
+ callCount++;
1651
+ if (callCount === 1) {
1652
+ return {
1653
+ content:
1654
+ '{"error":"name is required and must be a non-empty string"}',
1655
+ isError: true,
1656
+ };
1657
+ }
1658
+ return { content: "file contents", isError: false };
1659
+ };
1660
+
1661
+ const loop = new AgentLoop(
1662
+ provider,
1663
+ "system",
1664
+ {},
1665
+ dummyTools,
1666
+ toolExecutor,
1667
+ );
1668
+ await loop.run([userMessage], () => {});
1669
+
1670
+ // Provider should have been called 3 times (error -> retry -> final text)
1671
+ expect(calls).toHaveLength(3);
1672
+
1673
+ // The second call's messages should contain the retry nudge system_notice
1674
+ const secondCallMessages = calls[1].messages;
1675
+ const toolResultMessage = secondCallMessages[secondCallMessages.length - 1];
1676
+ expect(toolResultMessage.role).toBe("user");
1677
+
1678
+ const retryNudge = toolResultMessage.content.find(
1679
+ (b): b is Extract<ContentBlock, { type: "text" }> =>
1680
+ b.type === "text" && b.text.includes("looks recoverable"),
1681
+ );
1682
+ expect(retryNudge).toBeDefined();
1683
+
1684
+ // The third call should NOT have the retry nudge (successful tool result)
1685
+ const thirdCallMessages = calls[2].messages;
1686
+ const thirdToolResultMessage =
1687
+ thirdCallMessages[thirdCallMessages.length - 1];
1688
+ const noRetryNudge = thirdToolResultMessage.content.find(
1689
+ (b): b is Extract<ContentBlock, { type: "text" }> =>
1690
+ b.type === "text" && b.text.includes("looks recoverable"),
1691
+ );
1692
+ expect(noRetryNudge).toBeUndefined();
1693
+ });
1694
+
1695
+ // Retry nudge stops after MAX_CONSECUTIVE_ERROR_NUDGES (3) consecutive errors
1696
+ test("stops injecting retry nudge after 3 consecutive error turns", async () => {
1697
+ const { provider, calls } = createMockProvider([
1698
+ // 4 consecutive error turns, then final text
1699
+ toolUseResponse("t1", "read_file", { path: "/a" }),
1700
+ toolUseResponse("t2", "read_file", { path: "/b" }),
1701
+ toolUseResponse("t3", "read_file", { path: "/c" }),
1702
+ toolUseResponse("t4", "read_file", { path: "/d" }),
1703
+ textResponse("Giving up."),
1704
+ ]);
1705
+
1706
+ const toolExecutor = async (
1707
+ _name: string,
1708
+ _input: Record<string, unknown>,
1709
+ ) => {
1710
+ return { content: "service unavailable", isError: true };
1711
+ };
1712
+
1713
+ const loop = new AgentLoop(
1714
+ provider,
1715
+ "system",
1716
+ {},
1717
+ dummyTools,
1718
+ toolExecutor,
1719
+ );
1720
+ await loop.run([userMessage], () => {});
1721
+
1722
+ expect(calls).toHaveLength(5);
1723
+
1724
+ // Helper to check if a call's last user message has the retry nudge
1725
+ const hasRetryNudge = (callIndex: number): boolean => {
1726
+ const msgs = calls[callIndex].messages;
1727
+ const lastMsg = msgs[msgs.length - 1];
1728
+ return lastMsg.content.some(
1729
+ (b) =>
1730
+ b.type === "text" &&
1731
+ "text" in b &&
1732
+ (b as { text: string }).text.includes("looks recoverable"),
1733
+ );
1734
+ };
1735
+
1736
+ // Turns 1-3 should have the nudge
1737
+ expect(hasRetryNudge(1)).toBe(true);
1738
+ expect(hasRetryNudge(2)).toBe(true);
1739
+ expect(hasRetryNudge(3)).toBe(true);
1740
+ // Turn 4 should NOT have the nudge (exceeded limit)
1741
+ expect(hasRetryNudge(4)).toBe(false);
1742
+ });
1632
1743
  });
@@ -2,8 +2,8 @@
2
2
  * Guard test: always-loaded tool count
3
3
  *
4
4
  * This test asserts the exact set of tools that are active when no client is
5
- * connected, no host proxy is available, no attachments are present, and no
6
- * special channel capabilities exist. This represents the minimal "always-loaded"
5
+ * connected, no host proxy is available, and no special channel capabilities
6
+ * exist. This represents the minimal "always-loaded"
7
7
  * baseline that is sent to the LLM on every turn.
8
8
  *
9
9
  * Adding a tool to this set increases token cost for every request. If this test
@@ -32,14 +32,13 @@ describe("always-loaded tool count", () => {
32
32
  await initializeTools();
33
33
  const allDefs = buildToolDefinitions();
34
34
 
35
- // Minimal context: no client, no attachments, no capabilities
35
+ // Minimal context: no client, no capabilities
36
36
  const minimalContext: SkillProjectionContext = {
37
37
  skillProjectionState: new Map(),
38
38
  skillProjectionCache: {},
39
39
  coreToolNames: new Set(),
40
40
  toolsDisabledDepth: 0,
41
41
  hasNoClient: true,
42
- hasAttachments: false,
43
42
  channelCapabilities: undefined,
44
43
  };
45
44