@vellumai/assistant 0.5.1 → 0.5.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (405) hide show
  1. package/ARCHITECTURE.md +163 -54
  2. package/docs/architecture/integrations.md +62 -67
  3. package/docs/credential-execution-service.md +3 -3
  4. package/docs/skills.md +100 -0
  5. package/package.json +1 -1
  6. package/src/__tests__/agent-loop.test.ts +111 -0
  7. package/src/__tests__/always-loaded-tools-guard.test.ts +3 -4
  8. package/src/__tests__/app-builder-tool-scripts.test.ts +13 -151
  9. package/src/__tests__/app-dir-path-guard.test.ts +78 -0
  10. package/src/__tests__/app-executors.test.ts +1 -291
  11. package/src/__tests__/app-git-history.test.ts +4 -4
  12. package/src/__tests__/app-routes-csp.test.ts +1 -0
  13. package/src/__tests__/app-store-dir-names.test.ts +426 -0
  14. package/src/__tests__/attachments-store.test.ts +169 -21
  15. package/src/__tests__/attachments.test.ts +115 -1
  16. package/src/__tests__/btw-routes.test.ts +1 -0
  17. package/src/__tests__/canonical-guardian-store.test.ts +38 -0
  18. package/src/__tests__/channel-reply-delivery.test.ts +55 -0
  19. package/src/__tests__/checker.test.ts +54 -0
  20. package/src/__tests__/claude-code-skill-regression.test.ts +2 -0
  21. package/src/__tests__/claude-code-tool-profiles.test.ts +2 -0
  22. package/src/__tests__/compaction.benchmark.test.ts +2 -1
  23. package/src/__tests__/config-schema-cmd.test.ts +68 -21
  24. package/src/__tests__/config-schema.test.ts +1 -1
  25. package/src/__tests__/conversation-agent-loop-overflow.test.ts +156 -5
  26. package/src/__tests__/conversation-agent-loop.test.ts +297 -2
  27. package/src/__tests__/conversation-attachments.test.ts +17 -19
  28. package/src/__tests__/conversation-disk-view-integration.test.ts +277 -0
  29. package/src/__tests__/conversation-disk-view.test.ts +810 -0
  30. package/src/__tests__/conversation-error.test.ts +1 -1
  31. package/src/__tests__/conversation-fork-crud.test.ts +551 -0
  32. package/src/__tests__/conversation-fork-route.test.ts +386 -0
  33. package/src/__tests__/conversation-history-web-search.test.ts +1 -1
  34. package/src/__tests__/conversation-key-store-disk-view.test.ts +130 -0
  35. package/src/__tests__/conversation-media-retry.test.ts +8 -2
  36. package/src/__tests__/conversation-memory-dirty-tail.test.ts +150 -0
  37. package/src/__tests__/conversation-provider-retry-repair.test.ts +7 -0
  38. package/src/__tests__/conversation-queue.test.ts +36 -1
  39. package/src/__tests__/conversation-routes-disk-view.test.ts +439 -0
  40. package/src/__tests__/conversation-routes-guardian-reply.test.ts +2 -2
  41. package/src/__tests__/conversation-routes-slash-commands.test.ts +2 -7
  42. package/src/__tests__/conversation-runtime-assembly.test.ts +17 -2
  43. package/src/__tests__/conversation-skill-tools.test.ts +4 -9
  44. package/src/__tests__/conversation-slash-commands.test.ts +149 -0
  45. package/src/__tests__/conversation-store.test.ts +24 -21
  46. package/src/__tests__/conversation-surfaces-state-update.test.ts +246 -0
  47. package/src/__tests__/conversation-surfaces-task-progress.test.ts +1 -0
  48. package/src/__tests__/conversation-title-service.test.ts +137 -0
  49. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +25 -315
  50. package/src/__tests__/conversation-tool-setup-memory-scope.test.ts +1 -0
  51. package/src/__tests__/conversation-tool-setup-side-effect-flag.test.ts +1 -0
  52. package/src/__tests__/conversation-wipe.test.ts +226 -0
  53. package/src/__tests__/conversation-workspace-cache-state.test.ts +44 -2
  54. package/src/__tests__/conversation-workspace-injection.test.ts +11 -0
  55. package/src/__tests__/credential-security-invariants.test.ts +3 -0
  56. package/src/__tests__/credential-vault-unit.test.ts +5 -10
  57. package/src/__tests__/cu-unified-flow.test.ts +1 -0
  58. package/src/__tests__/db-conversation-fork-lineage-migration.test.ts +241 -0
  59. package/src/__tests__/db-llm-request-log-provider-migration.test.ts +214 -0
  60. package/src/__tests__/db-memory-archive-migration.test.ts +372 -0
  61. package/src/__tests__/db-memory-brief-state-migration.test.ts +213 -0
  62. package/src/__tests__/db-memory-reducer-checkpoints.test.ts +273 -0
  63. package/src/__tests__/diagnostics-export.test.ts +70 -1
  64. package/src/__tests__/first-greeting.test.ts +80 -0
  65. package/src/__tests__/gateway-only-guard.test.ts +1 -0
  66. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +3 -7
  67. package/src/__tests__/history-repair.test.ts +32 -10
  68. package/src/__tests__/http-conversation-lineage.test.ts +251 -0
  69. package/src/__tests__/image-source-path-reinject.test.ts +136 -0
  70. package/src/__tests__/inline-command-runner.test.ts +311 -0
  71. package/src/__tests__/inline-skill-authoring-guard.test.ts +220 -0
  72. package/src/__tests__/inline-skill-load-permissions.test.ts +435 -0
  73. package/src/__tests__/list-messages-attachments.test.ts +96 -0
  74. package/src/__tests__/llm-context-normalization.test.ts +1116 -0
  75. package/src/__tests__/llm-context-route-provider.test.ts +217 -0
  76. package/src/__tests__/llm-request-log-turn-query.test.ts +270 -0
  77. package/src/__tests__/media-generate-image.test.ts +47 -94
  78. package/src/__tests__/memory-brief-open-loops.test.ts +530 -0
  79. package/src/__tests__/memory-brief-time.test.ts +285 -0
  80. package/src/__tests__/memory-brief-wrapper.test.ts +311 -0
  81. package/src/__tests__/memory-chunk-archive.test.ts +400 -0
  82. package/src/__tests__/memory-chunk-dual-write.test.ts +453 -0
  83. package/src/__tests__/memory-episode-archive.test.ts +370 -0
  84. package/src/__tests__/memory-episode-dual-write.test.ts +626 -0
  85. package/src/__tests__/memory-lifecycle-e2e.test.ts +3 -1
  86. package/src/__tests__/memory-observation-archive.test.ts +375 -0
  87. package/src/__tests__/memory-observation-dual-write.test.ts +318 -0
  88. package/src/__tests__/memory-recall-quality.test.ts +7 -7
  89. package/src/__tests__/memory-reducer-store.test.ts +728 -0
  90. package/src/__tests__/memory-reducer-types.test.ts +699 -0
  91. package/src/__tests__/memory-reducer.test.ts +698 -0
  92. package/src/__tests__/memory-regressions.test.ts +6 -4
  93. package/src/__tests__/memory-simplified-config.test.ts +281 -0
  94. package/src/__tests__/migration-cross-version-compatibility.test.ts +4 -1
  95. package/src/__tests__/migration-export-http.test.ts +3 -1
  96. package/src/__tests__/migration-import-commit-http.test.ts +18 -4
  97. package/src/__tests__/migration-import-preflight-http.test.ts +1 -3
  98. package/src/__tests__/mime-builder.test.ts +3 -2
  99. package/src/__tests__/non-member-access-request.test.ts +12 -1
  100. package/src/__tests__/notification-decision-identity.test.ts +52 -0
  101. package/src/__tests__/oauth-apps-routes.test.ts +103 -0
  102. package/src/__tests__/oauth-store.test.ts +115 -0
  103. package/src/__tests__/parse-identity-fields.test.ts +129 -0
  104. package/src/__tests__/provider-error-scenarios.test.ts +1 -3
  105. package/src/__tests__/provider-failover-actual-provider.test.ts +66 -0
  106. package/src/__tests__/recording-handler.test.ts +17 -0
  107. package/src/__tests__/registry.test.ts +3 -8
  108. package/src/__tests__/relay-server.test.ts +1 -1
  109. package/src/__tests__/runtime-attachment-metadata.test.ts +7 -3
  110. package/src/__tests__/schema-transforms.test.ts +165 -5
  111. package/src/__tests__/server-history-render.test.ts +2 -2
  112. package/src/__tests__/skill-load-inline-command.test.ts +598 -0
  113. package/src/__tests__/skill-load-inline-includes.test.ts +644 -0
  114. package/src/__tests__/skills-inline-command-expansions.test.ts +301 -0
  115. package/src/__tests__/skills-transitive-hash.test.ts +333 -0
  116. package/src/__tests__/slack-app-setup-skill-regression.test.ts +3 -1
  117. package/src/__tests__/slack-inbound-verification.test.ts +2 -2
  118. package/src/__tests__/starter-task-flow.test.ts +1 -0
  119. package/src/__tests__/suggestion-routes.test.ts +443 -0
  120. package/src/__tests__/swarm-conversation-integration.test.ts +1 -0
  121. package/src/__tests__/swarm-recursion.test.ts +1 -0
  122. package/src/__tests__/swarm-tool.test.ts +1 -0
  123. package/src/__tests__/tool-execution-abort-cleanup.test.ts +1 -0
  124. package/src/__tests__/tool-preview-lifecycle.test.ts +32 -5
  125. package/src/__tests__/top-level-renderer.test.ts +22 -0
  126. package/src/__tests__/turn-boundary-resolution.test.ts +243 -0
  127. package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +320 -0
  128. package/src/__tests__/web-fetch.test.ts +6 -2
  129. package/src/__tests__/workspace-migration-006-services-config.test.ts +335 -0
  130. package/src/__tests__/workspace-migration-007-web-search-provider-rename.test.ts +312 -0
  131. package/src/__tests__/workspace-migration-009-backfill-conversation-disk-view.test.ts +278 -0
  132. package/src/__tests__/workspace-migration-010-app-dir-rename.test.ts +275 -0
  133. package/src/__tests__/workspace-migration-012-rename-conversation-disk-view-dirs.test.ts +77 -0
  134. package/src/__tests__/workspace-migration-013-repair-conversation-disk-view.test.ts +401 -0
  135. package/src/__tests__/workspace-migration-backfill-installation-id.test.ts +328 -0
  136. package/src/__tests__/workspace-migration-seed-device-id.test.ts +6 -10
  137. package/src/agent/attachments.ts +27 -1
  138. package/src/agent/loop.ts +29 -1
  139. package/src/avatar/traits-png-sync.ts +80 -25
  140. package/src/bundler/app-bundler.ts +4 -4
  141. package/src/calls/call-domain.ts +1 -0
  142. package/src/calls/voice-session-bridge.ts +1 -0
  143. package/src/cli/commands/auth.ts +92 -0
  144. package/src/cli/commands/avatar.ts +7 -6
  145. package/src/cli/commands/config.ts +2 -0
  146. package/src/cli/commands/oauth/providers.ts +29 -0
  147. package/src/cli/program.ts +12 -0
  148. package/src/cli.ts +15 -48
  149. package/src/config/bundled-skills/app-builder/SKILL.md +103 -28
  150. package/src/config/bundled-skills/app-builder/TOOLS.json +5 -199
  151. package/src/config/bundled-skills/app-builder/tools/{app-query.ts → app-refresh.ts} +2 -2
  152. package/src/config/bundled-skills/contacts/tools/google-contacts.ts +2 -3
  153. package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +6 -9
  154. package/src/config/bundled-skills/gmail/tools/gmail-attachments.ts +4 -6
  155. package/src/config/bundled-skills/gmail/tools/gmail-draft.ts +2 -3
  156. package/src/config/bundled-skills/gmail/tools/gmail-filters.ts +2 -3
  157. package/src/config/bundled-skills/gmail/tools/gmail-follow-up.ts +2 -3
  158. package/src/config/bundled-skills/gmail/tools/gmail-forward.ts +2 -3
  159. package/src/config/bundled-skills/gmail/tools/gmail-label.ts +4 -6
  160. package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +2 -3
  161. package/src/config/bundled-skills/gmail/tools/gmail-send-draft.ts +2 -3
  162. package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +2 -3
  163. package/src/config/bundled-skills/gmail/tools/gmail-trash.ts +2 -3
  164. package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +2 -3
  165. package/src/config/bundled-skills/gmail/tools/gmail-vacation.ts +2 -3
  166. package/src/config/bundled-skills/google-calendar/tools/shared.ts +1 -1
  167. package/src/config/bundled-skills/image-studio/SKILL.md +2 -2
  168. package/src/config/bundled-skills/image-studio/TOOLS.json +2 -2
  169. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +45 -72
  170. package/src/config/bundled-skills/media-processing/tools/extract-keyframes.ts +2 -2
  171. package/src/config/bundled-skills/messaging/tools/shared.ts +1 -1
  172. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +19 -3
  173. package/src/config/bundled-skills/skill-management/SKILL.md +1 -1
  174. package/src/config/bundled-skills/skill-management/TOOLS.json +2 -2
  175. package/src/config/bundled-skills/slack/tools/shared.ts +19 -4
  176. package/src/config/bundled-skills/slack/tools/slack-scan-digest.ts +2 -3
  177. package/src/config/bundled-skills/transcribe/SKILL.md +1 -1
  178. package/src/config/bundled-skills/transcribe/TOOLS.json +2 -6
  179. package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +19 -83
  180. package/src/config/bundled-tool-registry.ts +2 -14
  181. package/src/config/feature-flag-registry.json +24 -0
  182. package/src/config/loader.ts +65 -0
  183. package/src/config/raw-config-utils.ts +58 -0
  184. package/src/config/schema-utils.ts +28 -7
  185. package/src/config/schema.ts +20 -0
  186. package/src/config/schemas/elevenlabs.ts +18 -0
  187. package/src/config/schemas/memory-lifecycle.ts +4 -2
  188. package/src/config/schemas/memory-simplified.ts +101 -0
  189. package/src/config/schemas/memory-storage.ts +1 -1
  190. package/src/config/schemas/memory.ts +4 -0
  191. package/src/config/schemas/services.ts +8 -6
  192. package/src/config/skills.ts +50 -4
  193. package/src/contacts/contact-store.ts +13 -6
  194. package/src/contacts/contacts-write.ts +0 -1
  195. package/src/context/window-manager.ts +13 -2
  196. package/src/daemon/conversation-agent-loop-handlers.ts +54 -8
  197. package/src/daemon/conversation-agent-loop.ts +127 -20
  198. package/src/daemon/conversation-attachments.ts +18 -36
  199. package/src/daemon/conversation-error.ts +2 -1
  200. package/src/daemon/conversation-history.ts +18 -4
  201. package/src/daemon/conversation-lifecycle.ts +50 -16
  202. package/src/daemon/conversation-messaging.ts +70 -26
  203. package/src/daemon/conversation-process.ts +58 -34
  204. package/src/daemon/conversation-runtime-assembly.ts +22 -38
  205. package/src/daemon/conversation-slash.ts +121 -256
  206. package/src/daemon/conversation-surfaces.ts +170 -24
  207. package/src/daemon/conversation-tool-setup.ts +0 -6
  208. package/src/daemon/conversation-workspace.ts +21 -1
  209. package/src/daemon/conversation.ts +69 -30
  210. package/src/daemon/first-greeting.ts +35 -0
  211. package/src/daemon/handlers/config-embeddings.ts +156 -0
  212. package/src/daemon/handlers/config-model.ts +62 -26
  213. package/src/daemon/handlers/conversations.ts +0 -23
  214. package/src/daemon/handlers/identity.ts +12 -1
  215. package/src/daemon/handlers/recording.ts +26 -21
  216. package/src/daemon/host-cu-proxy.ts +2 -2
  217. package/src/daemon/lifecycle.ts +115 -65
  218. package/src/daemon/message-protocol.ts +3 -0
  219. package/src/daemon/message-types/conversations.ts +18 -0
  220. package/src/daemon/message-types/messages.ts +1 -0
  221. package/src/daemon/message-types/shared.ts +2 -0
  222. package/src/daemon/message-types/surfaces.ts +2 -0
  223. package/src/daemon/message-types/upgrades.ts +23 -0
  224. package/src/daemon/server.ts +83 -12
  225. package/src/daemon/shutdown-handlers.ts +8 -5
  226. package/src/daemon/startup-error.ts +9 -0
  227. package/src/daemon/tool-side-effects.ts +11 -28
  228. package/src/events/tool-permission-telemetry-listener.ts +1 -3
  229. package/src/followups/followup-store.ts +47 -1
  230. package/src/instrument.ts +0 -4
  231. package/src/media/app-icon-generator.ts +2 -2
  232. package/src/memory/app-git-service.ts +28 -16
  233. package/src/memory/app-store.ts +230 -41
  234. package/src/memory/archive-store.ts +400 -0
  235. package/src/memory/attachments-store.ts +558 -130
  236. package/src/memory/brief-formatting.ts +33 -0
  237. package/src/memory/brief-open-loops.ts +266 -0
  238. package/src/memory/brief-time.ts +161 -0
  239. package/src/memory/brief.ts +75 -0
  240. package/src/memory/conversation-attention-store.ts +70 -0
  241. package/src/memory/conversation-crud.ts +591 -8
  242. package/src/memory/conversation-directories.ts +125 -0
  243. package/src/memory/conversation-disk-view.ts +390 -0
  244. package/src/memory/conversation-key-store.ts +17 -5
  245. package/src/memory/conversation-queries.ts +5 -1
  246. package/src/memory/conversation-title-service.ts +21 -49
  247. package/src/memory/db-init.ts +40 -0
  248. package/src/memory/embedding-backend.ts +42 -53
  249. package/src/memory/embedding-gemini.test.ts +4 -4
  250. package/src/memory/embedding-local.ts +1 -3
  251. package/src/memory/embedding-ollama.ts +1 -3
  252. package/src/memory/embedding-openai.ts +1 -3
  253. package/src/memory/indexer.ts +114 -21
  254. package/src/memory/items-extractor.ts +42 -13
  255. package/src/memory/job-handlers/conversation-starters.ts +6 -1
  256. package/src/memory/job-handlers/embedding.test.ts +2 -4
  257. package/src/memory/job-handlers/embedding.ts +83 -0
  258. package/src/memory/job-utils.ts +1 -1
  259. package/src/memory/jobs-store.ts +6 -0
  260. package/src/memory/jobs-worker.ts +12 -0
  261. package/src/memory/llm-request-log-store.ts +100 -1
  262. package/src/memory/migrations/102-alter-table-columns.ts +5 -0
  263. package/src/memory/migrations/146-schedule-oneshot-routing.ts +3 -3
  264. package/src/memory/migrations/147-migrate-reminders-to-schedules.ts +66 -70
  265. package/src/memory/migrations/148-drop-reminders-table.ts +5 -9
  266. package/src/memory/migrations/160-drop-loopback-port-column.ts +1 -3
  267. package/src/memory/migrations/174-rename-thread-starters-table.ts +0 -7
  268. package/src/memory/migrations/178-oauth-providers-managed-service-config-key.ts +15 -0
  269. package/src/memory/migrations/179-llm-request-log-message-id.ts +16 -0
  270. package/src/memory/migrations/180-backfill-inline-attachments-to-disk.ts +66 -0
  271. package/src/memory/migrations/181-rename-thread-starters-checkpoints.ts +46 -0
  272. package/src/memory/migrations/182-oauth-providers-display-metadata.ts +20 -0
  273. package/src/memory/migrations/183-add-conversation-fork-lineage.ts +22 -0
  274. package/src/memory/migrations/184-llm-request-log-provider.ts +12 -0
  275. package/src/memory/migrations/185-memory-brief-state.ts +52 -0
  276. package/src/memory/migrations/186-memory-archive.ts +109 -0
  277. package/src/memory/migrations/187-memory-reducer-checkpoints.ts +19 -0
  278. package/src/memory/migrations/index.ts +10 -0
  279. package/src/memory/migrations/registry.ts +13 -0
  280. package/src/memory/qdrant-client.ts +23 -4
  281. package/src/memory/reducer-store.ts +271 -0
  282. package/src/memory/reducer-types.ts +99 -0
  283. package/src/memory/reducer.ts +453 -0
  284. package/src/memory/retriever.test.ts +601 -2
  285. package/src/memory/retriever.ts +85 -9
  286. package/src/memory/schema/conversations.ts +9 -0
  287. package/src/memory/schema/index.ts +2 -0
  288. package/src/memory/schema/infrastructure.ts +13 -7
  289. package/src/memory/schema/memory-archive.ts +121 -0
  290. package/src/memory/schema/memory-brief.ts +55 -0
  291. package/src/memory/schema/oauth.ts +6 -0
  292. package/src/memory/search/semantic.ts +17 -4
  293. package/src/messaging/providers/gmail/mime-builder.ts +3 -1
  294. package/src/notifications/copy-composer.ts +26 -0
  295. package/src/notifications/decision-engine.ts +14 -1
  296. package/src/notifications/emit-signal.ts +1 -1
  297. package/src/notifications/signal.ts +36 -0
  298. package/src/oauth/byo-connection.test.ts +1 -45
  299. package/src/oauth/byo-connection.ts +2 -8
  300. package/src/oauth/connect-orchestrator.ts +15 -11
  301. package/src/oauth/connection-resolver.test.ts +191 -0
  302. package/src/oauth/connection-resolver.ts +66 -38
  303. package/src/oauth/connection.ts +0 -1
  304. package/src/oauth/oauth-store.ts +99 -47
  305. package/src/oauth/platform-connection.test.ts +0 -1
  306. package/src/oauth/platform-connection.ts +11 -3
  307. package/src/oauth/seed-providers.ts +78 -3
  308. package/src/oauth/token-persistence.ts +16 -10
  309. package/src/permissions/checker.ts +160 -14
  310. package/src/permissions/defaults.ts +14 -0
  311. package/src/prompts/templates/BOOTSTRAP.md +2 -0
  312. package/src/providers/anthropic/client.ts +8 -1
  313. package/src/providers/failover.ts +4 -1
  314. package/src/providers/gemini/client.ts +50 -0
  315. package/src/providers/model-catalog.ts +92 -0
  316. package/src/providers/model-intents.ts +29 -20
  317. package/src/providers/openai/client.ts +49 -0
  318. package/src/providers/types.ts +2 -0
  319. package/src/runtime/access-request-helper.ts +16 -7
  320. package/src/runtime/auth/credential-service.ts +3 -1
  321. package/src/runtime/auth/route-policy.ts +14 -1
  322. package/src/runtime/btw-sidechain.ts +101 -0
  323. package/src/runtime/channel-reply-delivery.ts +17 -1
  324. package/src/runtime/http-router.ts +3 -1
  325. package/src/runtime/http-server.ts +196 -141
  326. package/src/runtime/http-types.ts +1 -0
  327. package/src/runtime/migrations/vbundle-builder.ts +5 -1
  328. package/src/runtime/routes/access-request-decision.ts +41 -0
  329. package/src/runtime/routes/app-management-routes.ts +6 -3
  330. package/src/runtime/routes/app-routes.ts +7 -3
  331. package/src/runtime/routes/approval-routes.ts +1 -0
  332. package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +34 -2
  333. package/src/runtime/routes/attachment-routes.ts +45 -15
  334. package/src/runtime/routes/btw-routes.ts +21 -61
  335. package/src/runtime/routes/conversation-management-routes.ts +74 -0
  336. package/src/runtime/routes/conversation-query-routes.ts +187 -10
  337. package/src/runtime/routes/conversation-routes.ts +269 -28
  338. package/src/runtime/routes/conversation-starter-routes.ts +9 -11
  339. package/src/runtime/routes/diagnostics-routes.ts +1 -0
  340. package/src/runtime/routes/identity-routes.ts +2 -35
  341. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +2 -2
  342. package/src/runtime/routes/llm-context-normalization.ts +1212 -0
  343. package/src/runtime/routes/log-export-routes.ts +3 -0
  344. package/src/runtime/routes/memory-item-routes.test.ts +34 -0
  345. package/src/runtime/routes/memory-item-routes.ts +94 -5
  346. package/src/runtime/routes/migration-routes.ts +4 -1
  347. package/src/runtime/routes/oauth-apps.ts +291 -0
  348. package/src/runtime/routes/secret-routes.ts +30 -1
  349. package/src/runtime/routes/settings-routes.ts +14 -0
  350. package/src/runtime/routes/surface-action-routes.ts +68 -1
  351. package/src/runtime/routes/trace-event-routes.ts +4 -1
  352. package/src/schedule/schedule-store.ts +30 -21
  353. package/src/security/secure-keys.ts +21 -0
  354. package/src/signals/bash.ts +1 -1
  355. package/src/skills/inline-command-expansions.ts +204 -0
  356. package/src/skills/inline-command-render.ts +127 -0
  357. package/src/skills/inline-command-runner.ts +242 -0
  358. package/src/skills/transitive-version-hash.ts +88 -0
  359. package/src/swarm/backend-claude-code.ts +3 -6
  360. package/src/tasks/task-store.ts +43 -1
  361. package/src/telemetry/usage-telemetry-reporter.test.ts +3 -2
  362. package/src/telemetry/usage-telemetry-reporter.ts +3 -1
  363. package/src/tools/AGENTS.md +6 -10
  364. package/src/tools/apps/executors.ts +17 -232
  365. package/src/tools/claude-code/claude-code.ts +2 -3
  366. package/src/tools/credentials/vault.ts +7 -12
  367. package/src/tools/host-filesystem/read.ts +13 -10
  368. package/src/tools/network/__tests__/web-search.test.ts +4 -2
  369. package/src/tools/permission-checker.ts +8 -1
  370. package/src/tools/schedule/list.ts +2 -7
  371. package/src/tools/schema-transforms.ts +5 -0
  372. package/src/tools/shared/filesystem/format-diff.ts +2 -7
  373. package/src/tools/skills/execute.ts +1 -1
  374. package/src/tools/skills/load.ts +140 -6
  375. package/src/tools/tool-manifest.ts +0 -6
  376. package/src/tools/ui-surface/definitions.ts +2 -2
  377. package/src/util/device-id.ts +28 -5
  378. package/src/util/platform.ts +24 -0
  379. package/src/util/pricing.ts +1 -0
  380. package/src/util/retry.ts +1 -3
  381. package/src/workspace/migrations/003-seed-device-id.ts +3 -4
  382. package/src/workspace/migrations/006-services-config.ts +5 -0
  383. package/src/workspace/migrations/008-voice-timeout-and-max-steps.ts +12 -0
  384. package/src/workspace/migrations/009-backfill-conversation-disk-view.ts +10 -0
  385. package/src/workspace/migrations/010-app-dir-rename.ts +223 -0
  386. package/src/workspace/migrations/{002-backfill-installation-id.ts → 011-backfill-installation-id.ts} +24 -13
  387. package/src/workspace/migrations/012-rename-conversation-disk-view-dirs.ts +64 -0
  388. package/src/workspace/migrations/013-repair-conversation-disk-view.ts +11 -0
  389. package/src/workspace/migrations/rebuild-conversation-disk-view.ts +186 -0
  390. package/src/workspace/migrations/registry.ts +11 -1
  391. package/src/workspace/top-level-renderer.ts +12 -0
  392. package/src/__tests__/asset-materialize-tool.test.ts +0 -523
  393. package/src/__tests__/asset-search-tool.test.ts +0 -536
  394. package/src/__tests__/fixtures/media-reuse-fixtures.ts +0 -56
  395. package/src/__tests__/media-reuse-story.e2e.test.ts +0 -762
  396. package/src/__tests__/media-visibility-policy.test.ts +0 -190
  397. package/src/config/bundled-skills/app-builder/tools/app-file-edit.ts +0 -14
  398. package/src/config/bundled-skills/app-builder/tools/app-file-list.ts +0 -13
  399. package/src/config/bundled-skills/app-builder/tools/app-file-read.ts +0 -21
  400. package/src/config/bundled-skills/app-builder/tools/app-file-write.ts +0 -14
  401. package/src/config/bundled-skills/app-builder/tools/app-list.ts +0 -13
  402. package/src/config/bundled-skills/app-builder/tools/app-update.ts +0 -23
  403. package/src/daemon/media-visibility-policy.ts +0 -59
  404. package/src/tools/assets/materialize.ts +0 -248
  405. package/src/tools/assets/search.ts +0 -400
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
 
@@ -1261,6 +1261,115 @@ graph TB
1261
1261
  TRUST -->|"Deny rule matches"| DENY["Blocked"]
1262
1262
  ```
1263
1263
 
1264
+ ### Inline Skill Command Expansion
1265
+
1266
+ Skills can embed dynamic shell output in their SKILL.md body using `!`command``tokens. When`skill_load` processes a skill containing these tokens, the commands are executed at load time through a sandboxed runner and their output is substituted inline. This enables externally authored skills to include project-specific context (e.g., directory listings, config values) without requiring manual edits.
1267
+
1268
+ **Feature flag:** `feature_flags.inline-skill-commands.enabled` (default: enabled). When disabled, loading a skill that contains `!`command`` tokens fails closed with an error rather than leaving raw tokens in the prompt.
1269
+
1270
+ #### Syntax and Parsing
1271
+
1272
+ The `!`command``syntax is parsed by`parseInlineCommandExpansions()` from the SKILL.md body after frontmatter extraction. The parser:
1273
+
1274
+ - Extracts all `!`command`` tokens outside fenced code blocks (documentation examples in fenced blocks are ignored)
1275
+ - Assigns each token a stable `placeholderId` (0-indexed encounter order)
1276
+ - Rejects malformed tokens fail-closed: empty commands, nested backticks, and unmatched opening backticks produce `InlineCommandExpansionError` entries rather than best-effort expansions
1277
+
1278
+ #### Transitive Version Hash
1279
+
1280
+ When a skill contains inline command expansions, the permission system computes a **transitive version hash** (`tv1:<sha256>`) that covers the root skill and all its included children (DFS pre-order). The hash folds:
1281
+
1282
+ 1. Each visited skill ID (graph structure)
1283
+ 2. Each visited skill's directory content hash (file changes)
1284
+
1285
+ Editing any file in the root skill or any included child invalidates the transitive hash, which forces re-approval. The hash is computed by `computeTransitiveSkillVersionHash()` and fails closed (`TransitiveHashError`) on missing children or cycles in the include graph.
1286
+
1287
+ #### Permission Gating (`skill_load_dynamic:*`)
1288
+
1289
+ Skills containing inline command expansions use a separate permission candidate namespace (`skill_load_dynamic:*`) instead of the normal `skill_load:*` namespace. This prevents them from falling through to the permissive default `skill_load:*` allow rule. The permission checker emits candidates in specificity order:
1290
+
1291
+ 1. `skill_load_dynamic:<skill-id>@<transitive-hash>` — version-pinned approval (most specific)
1292
+ 2. `skill_load_dynamic:<skill-id>` — any-version approval
1293
+
1294
+ A default ask rule at priority 200 (`default:ask-skill_load_dynamic-global`) catches these candidates, ensuring the guardian is always prompted before inline commands execute. The user can create a pinned trust rule for a specific transitive hash to auto-approve known-good versions. Non-interactive sessions (no human present) deny dynamic skill loads rather than silently auto-approving.
1295
+
1296
+ ```mermaid
1297
+ graph TB
1298
+ LOAD["skill_load(selector)"] --> PARSE["Parse SKILL.md body"]
1299
+ PARSE --> CHECK{"Has !\x60command\x60<br/>tokens?"}
1300
+ CHECK -->|"No"| NORMAL["Normal skill_load:* candidate<br/>(auto-allowed)"]
1301
+ CHECK -->|"Yes"| FLAG{"inline-skill-commands<br/>flag enabled?"}
1302
+ FLAG -->|"No"| FAIL_FLAG["Fail closed:<br/>error returned"]
1303
+ FLAG -->|"Yes"| SOURCE{"Eligible source?<br/>(bundled/managed/workspace)"}
1304
+ SOURCE -->|"No (extra)"| FAIL_SOURCE["Fail closed:<br/>source not eligible"]
1305
+ SOURCE -->|"Yes"| HASH["Compute transitive hash"]
1306
+ HASH --> DYN["skill_load_dynamic:id@hash<br/>candidate emitted"]
1307
+ DYN --> PERM["PermissionChecker"]
1308
+ PERM --> RULE{"Trust rule?"}
1309
+ RULE -->|"Pinned allow"| RENDER["Execute + render"]
1310
+ RULE -->|"No rule"| PROMPT["Prompt guardian"]
1311
+ RULE -->|"Deny"| DENY["Blocked"]
1312
+ ```
1313
+
1314
+ #### Sandbox-Only Execution
1315
+
1316
+ Inline commands are executed through `runInlineCommand()`, a purpose-built sandbox runner with strict security constraints:
1317
+
1318
+ - **Sandbox enforced**: The sandbox is always enabled with `networkMode: "off"` — no outbound network connections
1319
+ - **Sanitized environment**: Uses `buildSanitizedEnv()` — no API keys, tokens, credentials, gateway URLs, or workspace paths in the environment
1320
+ - **No host fallback**: Unlike the general `bash` tool, there is no fallback to host execution when the sandbox is unavailable
1321
+ - **No credential proxy**: No CES client, no credential materialization
1322
+ - **Timeout**: 10-second wall-clock limit (killed with SIGKILL on timeout)
1323
+ - **Output cap**: 20,000 characters maximum (truncated with `[output truncated]` marker)
1324
+ - **Binary rejection**: Output with >10% non-printable characters (after ANSI stripping) is rejected
1325
+ - **Stdout only**: stderr is discarded; ANSI escape sequences are stripped from stdout
1326
+
1327
+ The runner returns a deterministic `InlineCommandResult` with machine-readable failure reasons (`timeout`, `non_zero_exit`, `binary_output`, `spawn_failure`) — raw stderr is never surfaced.
1328
+
1329
+ #### Rendering Flow
1330
+
1331
+ The `renderInlineCommands()` function processes expansions sequentially (not in parallel) to maintain deterministic order. Each `!`command`` token is replaced with an XML-wrapped result:
1332
+
1333
+ - **Success**: `<inline_skill_command index="N">...output...</inline_skill_command>`
1334
+ - **Failure**: `<inline_skill_command index="N">[inline command unavailable: <reason>]</inline_skill_command>`
1335
+
1336
+ Rendering applies at two levels during `skill_load`:
1337
+
1338
+ 1. **Root skill**: If the loaded skill has inline expansions, they are rendered before the skill body is emitted. A root skill with inline commands that fail the feature-flag or source-eligibility check returns an error (fail closed, no `<loaded_skill>` marker).
1339
+ 2. **Included children**: Each included child skill's body is rendered independently. A render failure in one child does not prevent sibling rendering — the failed child's body falls back to raw (unexpanded) text with a warning log.
1340
+
1341
+ #### v1 Source Restriction
1342
+
1343
+ In the initial release, only skills from **bundled**, **managed**, and **workspace** sources are eligible for inline command expansion. Skills from **extra** (third-party) roots are explicitly rejected with an error message. The `INLINE_COMMAND_ELIGIBLE_SOURCES` set in `load.ts` enforces this restriction. Unknown or future source types also fail closed.
1344
+
1345
+ #### Fail-Closed Behavior Summary
1346
+
1347
+ Every layer in the pipeline defaults to rejection rather than silent degradation:
1348
+
1349
+ | Layer | Failure mode | Behavior |
1350
+ | ---------------- | ---------------------------------------------------- | ------------------------------------------------------ |
1351
+ | Parser | Malformed token (empty, nested backtick, unmatched) | Logged as error, not expanded |
1352
+ | Feature flag | Flag disabled | `skill_load` returns error, no `<loaded_skill>` marker |
1353
+ | Source check | `extra` or unknown source | `skill_load` returns error, no `<loaded_skill>` marker |
1354
+ | Transitive hash | Missing child or cycle in include graph | `TransitiveHashError` thrown, permission check fails |
1355
+ | Permission | No trust rule and non-interactive | Denied (never silently auto-approved) |
1356
+ | Sandbox runner | Timeout, non-zero exit, binary output, spawn failure | Deterministic stub rendered, no raw stderr |
1357
+ | Renderer (root) | Feature flag off or ineligible source | Error returned from `skill_load` |
1358
+ | Renderer (child) | Exception during render | Raw body used, sibling rendering continues |
1359
+
1360
+ #### Key Source Files
1361
+
1362
+ | File | Role |
1363
+ | --------------------------------------------------- | -------------------------------------------------------------------------------- |
1364
+ | `assistant/src/skills/inline-command-expansions.ts` | `parseInlineCommandExpansions()` — parser for `!`command`` tokens |
1365
+ | `assistant/src/skills/inline-command-runner.ts` | `runInlineCommand()` — sandbox-only command executor |
1366
+ | `assistant/src/skills/inline-command-render.ts` | `renderInlineCommands()` — token replacement and XML wrapping |
1367
+ | `assistant/src/skills/transitive-version-hash.ts` | `computeTransitiveSkillVersionHash()` — hash covering root + included children |
1368
+ | `assistant/src/tools/skills/load.ts` | `skill_load` execute path — feature flag check, source check, render integration |
1369
+ | `assistant/src/permissions/checker.ts` | `skill_load_dynamic:*` candidate emission and allowlist options |
1370
+ | `assistant/src/permissions/defaults.ts` | `default:ask-skill_load_dynamic-global` rule (priority 200) |
1371
+ | `meta/feature-flags/feature-flag-registry.json` | `inline-skill-commands` flag definition |
1372
+
1264
1373
  ### Key Source Files
1265
1374
 
1266
1375
  | File | Role |
@@ -1525,7 +1634,7 @@ sequenceDiagram
1525
1634
 
1526
1635
  ### Key design decisions
1527
1636
 
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.
1637
+ - **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
1638
  - **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
1639
  - **DAG scheduling**: Tasks with dependencies are topologically ordered. Independent tasks run in parallel up to `maxWorkers`.
1531
1640
  - **Per-task retries**: Failed tasks retry up to `maxRetriesPerTask` before being marked failed. Dependents are transitively blocked.
@@ -1568,7 +1677,7 @@ sequenceDiagram
1568
1677
 
1569
1678
  Note over Daemon: Processing previous request...<br/>Reaches safe tool-loop checkpoint
1570
1679
 
1571
- Daemon-->>DC: generation_handoff (sessionId, queuedCount)
1680
+ Daemon-->>DC: generation_handoff (conversationId, queuedCount)
1572
1681
  Note over Daemon: Daemon yields current generation
1573
1682
 
1574
1683
  Daemon-->>DC: message_dequeued
@@ -1585,7 +1694,7 @@ sequenceDiagram
1585
1694
 
1586
1695
  ## Trace System — Debug Panel Data Flow
1587
1696
 
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.
1697
+ 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
1698
 
1590
1699
  ```mermaid
1591
1700
  sequenceDiagram
@@ -1636,41 +1745,41 @@ sequenceDiagram
1636
1745
  TE-->>DC: trace_event (message_complete)
1637
1746
  DC-->>TS: ingest()
1638
1747
 
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
1748
+ 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
1749
 
1641
- TS-->>DP: @Published eventsBySession
1750
+ TS-->>DP: @Published eventsByConversation
1642
1751
  Note over DP: Metrics strip: requests, LLM calls,<br/>tokens (in/out), avg latency, failures<br/>Timeline: events grouped by requestId
1643
1752
  ```
1644
1753
 
1645
1754
  ### Trace Event Kinds
1646
1755
 
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) |
1756
+ Events emitted during a conversation lifecycle:
1757
+
1758
+ | Kind | Emitted by | When |
1759
+ | --------------------------- | ----------------------- | ----------------------------------------------------------------------------------------------- |
1760
+ | `request_received` | Handlers / Conversation | User message or surface action arrives |
1761
+ | `request_queued` | Handlers / Conversation | Message queued while conversation is busy |
1762
+ | `request_dequeued` | Conversation | Queued message begins processing |
1763
+ | `llm_call_started` | Conversation | LLM API call initiated |
1764
+ | `llm_call_finished` | Conversation | LLM API call completed (carries `inputTokens`, `outputTokens`, `latencyMs`) |
1765
+ | `assistant_message` | Conversation | Assistant response assembled (carries `toolUseCount`) |
1766
+ | `tool_started` | ToolTraceListener | Tool execution begins |
1767
+ | `tool_permission_requested` | ToolTraceListener | Permission check needed (carries `riskLevel`) |
1768
+ | `tool_permission_decided` | ToolTraceListener | Permission granted or denied (carries `decision`) |
1769
+ | `tool_finished` | ToolTraceListener | Tool execution completed (carries `durationMs`) |
1770
+ | `tool_failed` | ToolTraceListener | Tool execution failed (carries `durationMs`) |
1771
+ | `secret_detected` | ToolTraceListener | Secret found in tool output |
1772
+ | `generation_handoff` | Conversation | Yielding to next queued message |
1773
+ | `message_complete` | Conversation | Full request processing finished |
1774
+ | `generation_cancelled` | Conversation | User cancelled the generation |
1775
+ | `request_error` | Handlers / Conversation | Unrecoverable error during processing (includes queue-full rejection and persist-failure paths) |
1667
1776
 
1668
1777
  ### Architecture
1669
1778
 
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`.
1779
+ - **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.
1780
+ - **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
1781
  - **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).
1782
+ - **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
1783
  - **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
1784
 
1676
1785
  ---
@@ -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