@vellumai/assistant 0.5.1 → 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 (338) 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__/attachments-store.test.ts +169 -21
  14. package/src/__tests__/attachments.test.ts +115 -1
  15. package/src/__tests__/btw-routes.test.ts +1 -0
  16. package/src/__tests__/canonical-guardian-store.test.ts +38 -0
  17. package/src/__tests__/channel-reply-delivery.test.ts +55 -0
  18. package/src/__tests__/checker.test.ts +54 -0
  19. package/src/__tests__/claude-code-skill-regression.test.ts +2 -0
  20. package/src/__tests__/claude-code-tool-profiles.test.ts +2 -0
  21. package/src/__tests__/compaction.benchmark.test.ts +2 -1
  22. package/src/__tests__/config-schema-cmd.test.ts +68 -21
  23. package/src/__tests__/config-schema.test.ts +1 -1
  24. package/src/__tests__/conversation-agent-loop-overflow.test.ts +149 -5
  25. package/src/__tests__/conversation-agent-loop.test.ts +290 -2
  26. package/src/__tests__/conversation-attachments.test.ts +17 -19
  27. package/src/__tests__/conversation-disk-view-integration.test.ts +277 -0
  28. package/src/__tests__/conversation-disk-view.test.ts +810 -0
  29. package/src/__tests__/conversation-error.test.ts +1 -1
  30. package/src/__tests__/conversation-fork-crud.test.ts +551 -0
  31. package/src/__tests__/conversation-fork-route.test.ts +386 -0
  32. package/src/__tests__/conversation-history-web-search.test.ts +1 -1
  33. package/src/__tests__/conversation-key-store-disk-view.test.ts +130 -0
  34. package/src/__tests__/conversation-media-retry.test.ts +8 -2
  35. package/src/__tests__/conversation-queue.test.ts +36 -1
  36. package/src/__tests__/conversation-routes-disk-view.test.ts +439 -0
  37. package/src/__tests__/conversation-routes-guardian-reply.test.ts +2 -2
  38. package/src/__tests__/conversation-routes-slash-commands.test.ts +2 -7
  39. package/src/__tests__/conversation-runtime-assembly.test.ts +17 -2
  40. package/src/__tests__/conversation-skill-tools.test.ts +4 -9
  41. package/src/__tests__/conversation-slash-commands.test.ts +149 -0
  42. package/src/__tests__/conversation-store.test.ts +24 -21
  43. package/src/__tests__/conversation-surfaces-state-update.test.ts +246 -0
  44. package/src/__tests__/conversation-surfaces-task-progress.test.ts +1 -0
  45. package/src/__tests__/conversation-title-service.test.ts +137 -0
  46. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +25 -315
  47. package/src/__tests__/conversation-tool-setup-memory-scope.test.ts +1 -0
  48. package/src/__tests__/conversation-tool-setup-side-effect-flag.test.ts +1 -0
  49. package/src/__tests__/conversation-workspace-cache-state.test.ts +44 -2
  50. package/src/__tests__/conversation-workspace-injection.test.ts +11 -0
  51. package/src/__tests__/credential-security-invariants.test.ts +3 -0
  52. package/src/__tests__/credential-vault-unit.test.ts +5 -10
  53. package/src/__tests__/cu-unified-flow.test.ts +1 -0
  54. package/src/__tests__/db-conversation-fork-lineage-migration.test.ts +241 -0
  55. package/src/__tests__/db-llm-request-log-provider-migration.test.ts +214 -0
  56. package/src/__tests__/diagnostics-export.test.ts +70 -1
  57. package/src/__tests__/first-greeting.test.ts +80 -0
  58. package/src/__tests__/gateway-only-guard.test.ts +1 -0
  59. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +3 -7
  60. package/src/__tests__/history-repair.test.ts +32 -10
  61. package/src/__tests__/http-conversation-lineage.test.ts +251 -0
  62. package/src/__tests__/image-source-path-reinject.test.ts +136 -0
  63. package/src/__tests__/llm-context-normalization.test.ts +1116 -0
  64. package/src/__tests__/llm-context-route-provider.test.ts +217 -0
  65. package/src/__tests__/llm-request-log-turn-query.test.ts +270 -0
  66. package/src/__tests__/media-generate-image.test.ts +47 -94
  67. package/src/__tests__/memory-lifecycle-e2e.test.ts +3 -1
  68. package/src/__tests__/memory-recall-quality.test.ts +5 -5
  69. package/src/__tests__/migration-cross-version-compatibility.test.ts +4 -1
  70. package/src/__tests__/migration-export-http.test.ts +3 -1
  71. package/src/__tests__/migration-import-commit-http.test.ts +18 -4
  72. package/src/__tests__/migration-import-preflight-http.test.ts +1 -3
  73. package/src/__tests__/mime-builder.test.ts +3 -2
  74. package/src/__tests__/non-member-access-request.test.ts +12 -1
  75. package/src/__tests__/notification-decision-identity.test.ts +52 -0
  76. package/src/__tests__/oauth-apps-routes.test.ts +103 -0
  77. package/src/__tests__/oauth-store.test.ts +115 -0
  78. package/src/__tests__/provider-error-scenarios.test.ts +1 -3
  79. package/src/__tests__/provider-failover-actual-provider.test.ts +66 -0
  80. package/src/__tests__/recording-handler.test.ts +17 -0
  81. package/src/__tests__/registry.test.ts +3 -8
  82. package/src/__tests__/relay-server.test.ts +1 -1
  83. package/src/__tests__/runtime-attachment-metadata.test.ts +7 -3
  84. package/src/__tests__/schema-transforms.test.ts +165 -5
  85. package/src/__tests__/server-history-render.test.ts +2 -2
  86. package/src/__tests__/slack-app-setup-skill-regression.test.ts +3 -1
  87. package/src/__tests__/slack-inbound-verification.test.ts +2 -2
  88. package/src/__tests__/starter-task-flow.test.ts +1 -0
  89. package/src/__tests__/suggestion-routes.test.ts +443 -0
  90. package/src/__tests__/swarm-conversation-integration.test.ts +1 -0
  91. package/src/__tests__/swarm-recursion.test.ts +1 -0
  92. package/src/__tests__/swarm-tool.test.ts +1 -0
  93. package/src/__tests__/tool-execution-abort-cleanup.test.ts +1 -0
  94. package/src/__tests__/tool-preview-lifecycle.test.ts +32 -5
  95. package/src/__tests__/top-level-renderer.test.ts +22 -0
  96. package/src/__tests__/turn-boundary-resolution.test.ts +243 -0
  97. package/src/__tests__/web-fetch.test.ts +6 -2
  98. package/src/__tests__/workspace-migration-006-services-config.test.ts +335 -0
  99. package/src/__tests__/workspace-migration-007-web-search-provider-rename.test.ts +312 -0
  100. package/src/__tests__/workspace-migration-009-backfill-conversation-disk-view.test.ts +278 -0
  101. package/src/__tests__/workspace-migration-010-app-dir-rename.test.ts +275 -0
  102. package/src/__tests__/workspace-migration-012-rename-conversation-disk-view-dirs.test.ts +77 -0
  103. package/src/__tests__/workspace-migration-013-repair-conversation-disk-view.test.ts +401 -0
  104. package/src/__tests__/workspace-migration-backfill-installation-id.test.ts +328 -0
  105. package/src/__tests__/workspace-migration-seed-device-id.test.ts +6 -10
  106. package/src/agent/attachments.ts +27 -1
  107. package/src/agent/loop.ts +29 -1
  108. package/src/avatar/traits-png-sync.ts +80 -25
  109. package/src/bundler/app-bundler.ts +4 -4
  110. package/src/calls/call-domain.ts +1 -0
  111. package/src/calls/voice-session-bridge.ts +1 -0
  112. package/src/cli/commands/auth.ts +92 -0
  113. package/src/cli/commands/avatar.ts +7 -6
  114. package/src/cli/commands/config.ts +2 -0
  115. package/src/cli/commands/oauth/providers.ts +29 -0
  116. package/src/cli/program.ts +12 -0
  117. package/src/cli.ts +15 -48
  118. package/src/config/bundled-skills/app-builder/SKILL.md +103 -28
  119. package/src/config/bundled-skills/app-builder/TOOLS.json +5 -199
  120. package/src/config/bundled-skills/app-builder/tools/{app-query.ts → app-refresh.ts} +2 -2
  121. package/src/config/bundled-skills/contacts/tools/google-contacts.ts +2 -3
  122. package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +6 -9
  123. package/src/config/bundled-skills/gmail/tools/gmail-attachments.ts +4 -6
  124. package/src/config/bundled-skills/gmail/tools/gmail-draft.ts +2 -3
  125. package/src/config/bundled-skills/gmail/tools/gmail-filters.ts +2 -3
  126. package/src/config/bundled-skills/gmail/tools/gmail-follow-up.ts +2 -3
  127. package/src/config/bundled-skills/gmail/tools/gmail-forward.ts +2 -3
  128. package/src/config/bundled-skills/gmail/tools/gmail-label.ts +4 -6
  129. package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +2 -3
  130. package/src/config/bundled-skills/gmail/tools/gmail-send-draft.ts +2 -3
  131. package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +2 -3
  132. package/src/config/bundled-skills/gmail/tools/gmail-trash.ts +2 -3
  133. package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +2 -3
  134. package/src/config/bundled-skills/gmail/tools/gmail-vacation.ts +2 -3
  135. package/src/config/bundled-skills/google-calendar/tools/shared.ts +1 -1
  136. package/src/config/bundled-skills/image-studio/SKILL.md +2 -2
  137. package/src/config/bundled-skills/image-studio/TOOLS.json +2 -2
  138. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +45 -72
  139. package/src/config/bundled-skills/media-processing/tools/extract-keyframes.ts +2 -2
  140. package/src/config/bundled-skills/messaging/tools/shared.ts +1 -1
  141. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +19 -3
  142. package/src/config/bundled-skills/skill-management/TOOLS.json +2 -2
  143. package/src/config/bundled-skills/slack/tools/shared.ts +19 -4
  144. package/src/config/bundled-skills/slack/tools/slack-scan-digest.ts +2 -3
  145. package/src/config/bundled-skills/transcribe/SKILL.md +1 -1
  146. package/src/config/bundled-skills/transcribe/TOOLS.json +2 -6
  147. package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +19 -83
  148. package/src/config/bundled-tool-registry.ts +2 -14
  149. package/src/config/feature-flag-registry.json +8 -0
  150. package/src/config/loader.ts +64 -0
  151. package/src/config/raw-config-utils.ts +30 -0
  152. package/src/config/schema-utils.ts +28 -7
  153. package/src/config/schema.ts +8 -0
  154. package/src/config/schemas/elevenlabs.ts +18 -0
  155. package/src/config/schemas/memory-lifecycle.ts +4 -2
  156. package/src/config/schemas/memory-storage.ts +1 -1
  157. package/src/config/schemas/services.ts +8 -6
  158. package/src/contacts/contact-store.ts +13 -6
  159. package/src/contacts/contacts-write.ts +0 -1
  160. package/src/context/window-manager.ts +13 -2
  161. package/src/daemon/conversation-agent-loop-handlers.ts +48 -7
  162. package/src/daemon/conversation-agent-loop.ts +56 -19
  163. package/src/daemon/conversation-attachments.ts +18 -36
  164. package/src/daemon/conversation-error.ts +2 -1
  165. package/src/daemon/conversation-history.ts +18 -4
  166. package/src/daemon/conversation-lifecycle.ts +39 -15
  167. package/src/daemon/conversation-messaging.ts +70 -26
  168. package/src/daemon/conversation-process.ts +58 -34
  169. package/src/daemon/conversation-runtime-assembly.ts +21 -38
  170. package/src/daemon/conversation-slash.ts +121 -256
  171. package/src/daemon/conversation-surfaces.ts +143 -20
  172. package/src/daemon/conversation-tool-setup.ts +0 -6
  173. package/src/daemon/conversation-workspace.ts +21 -1
  174. package/src/daemon/conversation.ts +51 -29
  175. package/src/daemon/first-greeting.ts +35 -0
  176. package/src/daemon/handlers/config-embeddings.ts +148 -0
  177. package/src/daemon/handlers/config-model.ts +71 -26
  178. package/src/daemon/handlers/conversations.ts +0 -23
  179. package/src/daemon/handlers/recording.ts +26 -21
  180. package/src/daemon/host-cu-proxy.ts +2 -2
  181. package/src/daemon/lifecycle.ts +106 -64
  182. package/src/daemon/message-protocol.ts +3 -0
  183. package/src/daemon/message-types/conversations.ts +19 -0
  184. package/src/daemon/message-types/messages.ts +1 -0
  185. package/src/daemon/message-types/shared.ts +2 -0
  186. package/src/daemon/message-types/surfaces.ts +2 -0
  187. package/src/daemon/message-types/upgrades.ts +23 -0
  188. package/src/daemon/server.ts +83 -12
  189. package/src/daemon/shutdown-handlers.ts +8 -5
  190. package/src/daemon/startup-error.ts +9 -0
  191. package/src/daemon/tool-side-effects.ts +11 -28
  192. package/src/events/tool-permission-telemetry-listener.ts +1 -3
  193. package/src/instrument.ts +0 -4
  194. package/src/media/app-icon-generator.ts +2 -2
  195. package/src/memory/app-git-service.ts +28 -16
  196. package/src/memory/app-store.ts +230 -41
  197. package/src/memory/attachments-store.ts +558 -130
  198. package/src/memory/conversation-attention-store.ts +70 -0
  199. package/src/memory/conversation-crud.ts +442 -3
  200. package/src/memory/conversation-directories.ts +125 -0
  201. package/src/memory/conversation-disk-view.ts +390 -0
  202. package/src/memory/conversation-key-store.ts +17 -5
  203. package/src/memory/conversation-queries.ts +5 -1
  204. package/src/memory/conversation-title-service.ts +21 -49
  205. package/src/memory/db-init.ts +28 -0
  206. package/src/memory/embedding-backend.ts +42 -53
  207. package/src/memory/embedding-gemini.test.ts +4 -4
  208. package/src/memory/embedding-local.ts +1 -3
  209. package/src/memory/embedding-ollama.ts +1 -3
  210. package/src/memory/embedding-openai.ts +1 -3
  211. package/src/memory/indexer.ts +9 -7
  212. package/src/memory/items-extractor.ts +42 -13
  213. package/src/memory/job-handlers/conversation-starters.ts +6 -1
  214. package/src/memory/job-handlers/embedding.test.ts +1 -4
  215. package/src/memory/llm-request-log-store.ts +100 -1
  216. package/src/memory/migrations/102-alter-table-columns.ts +5 -0
  217. package/src/memory/migrations/146-schedule-oneshot-routing.ts +3 -3
  218. package/src/memory/migrations/147-migrate-reminders-to-schedules.ts +66 -70
  219. package/src/memory/migrations/148-drop-reminders-table.ts +5 -9
  220. package/src/memory/migrations/160-drop-loopback-port-column.ts +1 -3
  221. package/src/memory/migrations/174-rename-thread-starters-table.ts +0 -7
  222. package/src/memory/migrations/178-oauth-providers-managed-service-config-key.ts +15 -0
  223. package/src/memory/migrations/179-llm-request-log-message-id.ts +16 -0
  224. package/src/memory/migrations/180-backfill-inline-attachments-to-disk.ts +66 -0
  225. package/src/memory/migrations/181-rename-thread-starters-checkpoints.ts +46 -0
  226. package/src/memory/migrations/182-oauth-providers-display-metadata.ts +20 -0
  227. package/src/memory/migrations/183-add-conversation-fork-lineage.ts +22 -0
  228. package/src/memory/migrations/184-llm-request-log-provider.ts +12 -0
  229. package/src/memory/migrations/index.ts +7 -0
  230. package/src/memory/migrations/registry.ts +13 -0
  231. package/src/memory/retriever.test.ts +601 -2
  232. package/src/memory/retriever.ts +85 -9
  233. package/src/memory/schema/conversations.ts +6 -0
  234. package/src/memory/schema/infrastructure.ts +13 -7
  235. package/src/memory/schema/oauth.ts +6 -0
  236. package/src/messaging/providers/gmail/mime-builder.ts +3 -1
  237. package/src/notifications/copy-composer.ts +26 -0
  238. package/src/notifications/decision-engine.ts +14 -1
  239. package/src/notifications/emit-signal.ts +1 -1
  240. package/src/notifications/signal.ts +36 -0
  241. package/src/oauth/byo-connection.test.ts +1 -45
  242. package/src/oauth/byo-connection.ts +2 -8
  243. package/src/oauth/connect-orchestrator.ts +15 -11
  244. package/src/oauth/connection-resolver.test.ts +191 -0
  245. package/src/oauth/connection-resolver.ts +66 -38
  246. package/src/oauth/connection.ts +0 -1
  247. package/src/oauth/oauth-store.ts +97 -47
  248. package/src/oauth/platform-connection.test.ts +0 -1
  249. package/src/oauth/platform-connection.ts +11 -3
  250. package/src/oauth/seed-providers.ts +78 -3
  251. package/src/oauth/token-persistence.ts +16 -10
  252. package/src/permissions/checker.ts +71 -8
  253. package/src/prompts/templates/BOOTSTRAP.md +2 -0
  254. package/src/providers/anthropic/client.ts +8 -1
  255. package/src/providers/failover.ts +4 -1
  256. package/src/providers/gemini/client.ts +50 -0
  257. package/src/providers/model-catalog.ts +92 -0
  258. package/src/providers/model-intents.ts +29 -20
  259. package/src/providers/openai/client.ts +49 -0
  260. package/src/providers/types.ts +2 -0
  261. package/src/runtime/access-request-helper.ts +16 -7
  262. package/src/runtime/auth/credential-service.ts +3 -1
  263. package/src/runtime/auth/route-policy.ts +14 -1
  264. package/src/runtime/btw-sidechain.ts +101 -0
  265. package/src/runtime/channel-reply-delivery.ts +17 -1
  266. package/src/runtime/http-router.ts +3 -1
  267. package/src/runtime/http-server.ts +196 -141
  268. package/src/runtime/http-types.ts +1 -0
  269. package/src/runtime/migrations/vbundle-builder.ts +5 -1
  270. package/src/runtime/routes/access-request-decision.ts +41 -0
  271. package/src/runtime/routes/app-management-routes.ts +6 -3
  272. package/src/runtime/routes/app-routes.ts +7 -3
  273. package/src/runtime/routes/approval-routes.ts +1 -0
  274. package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +34 -2
  275. package/src/runtime/routes/attachment-routes.ts +45 -15
  276. package/src/runtime/routes/btw-routes.ts +21 -61
  277. package/src/runtime/routes/conversation-management-routes.ts +68 -0
  278. package/src/runtime/routes/conversation-query-routes.ts +180 -10
  279. package/src/runtime/routes/conversation-routes.ts +222 -28
  280. package/src/runtime/routes/conversation-starter-routes.ts +9 -11
  281. package/src/runtime/routes/diagnostics-routes.ts +1 -0
  282. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +2 -2
  283. package/src/runtime/routes/llm-context-normalization.ts +1199 -0
  284. package/src/runtime/routes/log-export-routes.ts +3 -0
  285. package/src/runtime/routes/memory-item-routes.test.ts +34 -0
  286. package/src/runtime/routes/memory-item-routes.ts +4 -0
  287. package/src/runtime/routes/migration-routes.ts +4 -1
  288. package/src/runtime/routes/oauth-apps.ts +291 -0
  289. package/src/runtime/routes/secret-routes.ts +28 -1
  290. package/src/runtime/routes/settings-routes.ts +14 -0
  291. package/src/runtime/routes/trace-event-routes.ts +4 -1
  292. package/src/schedule/schedule-store.ts +9 -21
  293. package/src/security/secure-keys.ts +21 -0
  294. package/src/signals/bash.ts +1 -1
  295. package/src/swarm/backend-claude-code.ts +3 -6
  296. package/src/telemetry/usage-telemetry-reporter.test.ts +3 -2
  297. package/src/telemetry/usage-telemetry-reporter.ts +3 -1
  298. package/src/tools/AGENTS.md +6 -10
  299. package/src/tools/apps/executors.ts +17 -232
  300. package/src/tools/claude-code/claude-code.ts +2 -3
  301. package/src/tools/credentials/vault.ts +7 -12
  302. package/src/tools/host-filesystem/read.ts +13 -10
  303. package/src/tools/network/__tests__/web-search.test.ts +4 -2
  304. package/src/tools/schedule/list.ts +2 -7
  305. package/src/tools/schema-transforms.ts +5 -0
  306. package/src/tools/shared/filesystem/format-diff.ts +2 -7
  307. package/src/tools/skills/execute.ts +1 -1
  308. package/src/tools/tool-manifest.ts +0 -6
  309. package/src/tools/ui-surface/definitions.ts +2 -2
  310. package/src/util/device-id.ts +28 -5
  311. package/src/util/platform.ts +6 -0
  312. package/src/util/pricing.ts +1 -0
  313. package/src/util/retry.ts +1 -3
  314. package/src/workspace/migrations/002-backfill-installation-id.ts +23 -12
  315. package/src/workspace/migrations/003-seed-device-id.ts +3 -4
  316. package/src/workspace/migrations/006-services-config.ts +5 -0
  317. package/src/workspace/migrations/008-voice-timeout-and-max-steps.ts +12 -0
  318. package/src/workspace/migrations/009-backfill-conversation-disk-view.ts +10 -0
  319. package/src/workspace/migrations/010-app-dir-rename.ts +223 -0
  320. package/src/workspace/migrations/012-rename-conversation-disk-view-dirs.ts +64 -0
  321. package/src/workspace/migrations/013-repair-conversation-disk-view.ts +11 -0
  322. package/src/workspace/migrations/rebuild-conversation-disk-view.ts +186 -0
  323. package/src/workspace/migrations/registry.ts +10 -0
  324. package/src/workspace/top-level-renderer.ts +12 -0
  325. package/src/__tests__/asset-materialize-tool.test.ts +0 -523
  326. package/src/__tests__/asset-search-tool.test.ts +0 -536
  327. package/src/__tests__/fixtures/media-reuse-fixtures.ts +0 -56
  328. package/src/__tests__/media-reuse-story.e2e.test.ts +0 -762
  329. package/src/__tests__/media-visibility-policy.test.ts +0 -190
  330. package/src/config/bundled-skills/app-builder/tools/app-file-edit.ts +0 -14
  331. package/src/config/bundled-skills/app-builder/tools/app-file-list.ts +0 -13
  332. package/src/config/bundled-skills/app-builder/tools/app-file-read.ts +0 -21
  333. package/src/config/bundled-skills/app-builder/tools/app-file-write.ts +0 -14
  334. package/src/config/bundled-skills/app-builder/tools/app-list.ts +0 -13
  335. package/src/config/bundled-skills/app-builder/tools/app-update.ts +0 -23
  336. package/src/daemon/media-visibility-policy.ts +0 -59
  337. package/src/tools/assets/materialize.ts +0 -248
  338. package/src/tools/assets/search.ts +0 -400
@@ -4,12 +4,19 @@ import {
4
4
  saveRawConfig,
5
5
  } from "../../config/loader.js";
6
6
  import { setServiceField } from "../../config/raw-config-utils.js";
7
+ import { VALID_INFERENCE_PROVIDERS } from "../../config/schemas/services.js";
8
+ import type { ProviderCatalogEntry } from "../../providers/model-catalog.js";
9
+ import {
10
+ isModelInCatalog,
11
+ PROVIDER_CATALOG,
12
+ } from "../../providers/model-catalog.js";
13
+ import { getProviderDefaultModel } from "../../providers/model-intents.js";
7
14
  import {
8
15
  getConfiguredProviders,
9
16
  isProviderAvailable,
10
17
  } from "../../providers/provider-availability.js";
11
18
  import { initializeProviders } from "../../providers/registry.js";
12
- import { MODEL_TO_PROVIDER } from "../conversation-slash.js";
19
+ import { getMaskedProviderKey } from "../../security/secure-keys.js";
13
20
  import type {
14
21
  ImageGenModelSetRequest,
15
22
  ModelSetRequest,
@@ -20,6 +27,13 @@ import {
20
27
  log,
21
28
  } from "./shared.js";
22
29
 
30
+ /** Reverse lookup: model ID → provider, derived from PROVIDER_CATALOG. */
31
+ export const MODEL_TO_PROVIDER: Record<string, string> = Object.fromEntries(
32
+ PROVIDER_CATALOG.flatMap((provider) =>
33
+ provider.models.map(({ id }) => [id, provider.id]),
34
+ ),
35
+ );
36
+
23
37
  // ---------------------------------------------------------------------------
24
38
  // Shared business logic (transport-agnostic)
25
39
  // ---------------------------------------------------------------------------
@@ -28,15 +42,29 @@ export interface ModelInfo {
28
42
  model: string;
29
43
  provider: string;
30
44
  configuredProviders?: string[];
45
+ availableModels?: Array<{ id: string; displayName: string }>;
46
+ allProviders?: ProviderCatalogEntry[];
47
+ maskedKeys?: Record<string, string>;
31
48
  }
32
49
 
33
50
  /** Return current model configuration. */
34
51
  export async function getModelInfo(): Promise<ModelInfo> {
35
52
  const config = getConfig();
53
+ const provider = config.services.inference.provider;
54
+
55
+ const maskedKeys: Record<string, string> = {};
56
+ for (const p of VALID_INFERENCE_PROVIDERS) {
57
+ const masked = await getMaskedProviderKey(p);
58
+ if (masked) maskedKeys[p] = masked;
59
+ }
60
+
36
61
  return {
37
62
  model: config.services.inference.model,
38
- provider: config.services.inference.provider,
63
+ provider,
39
64
  configuredProviders: await getConfiguredProviders(),
65
+ availableModels: PROVIDER_CATALOG.find((p) => p.id === provider)?.models,
66
+ allProviders: PROVIDER_CATALOG,
67
+ maskedKeys,
40
68
  };
41
69
  }
42
70
 
@@ -58,28 +86,52 @@ export interface ModelSetContext {
58
86
  /**
59
87
  * Set the active model. Returns the resulting ModelInfo, or throws on failure.
60
88
  * The caller is responsible for sending the response to the client.
89
+ *
90
+ * When `explicitProvider` is supplied, it takes precedence over automatic
91
+ * provider inference from the model ID. If the provider changes and the
92
+ * current model doesn't belong to the new provider's catalog, the model
93
+ * is auto-reset to the provider's default.
61
94
  */
62
95
  export async function setModel(
63
96
  modelId: string,
64
97
  ctx: ModelSetContext,
98
+ explicitProvider?: string,
65
99
  ): Promise<ModelInfo> {
66
- // If the requested model is already the current model AND the provider
67
- // is already aligned with what MODEL_TO_PROVIDER expects, skip expensive
68
- // reinitialization but still return model_info so the client confirms.
69
- {
70
- const current = getConfig();
71
- const expectedProvider = MODEL_TO_PROVIDER[modelId];
72
- const providerAligned =
73
- !expectedProvider ||
74
- current.services.inference.provider === expectedProvider;
75
- if (modelId === current.services.inference.model && providerAligned) {
76
- return await getModelInfo();
77
- }
100
+ const validProviders = new Set<string>(VALID_INFERENCE_PROVIDERS);
101
+
102
+ // Validate explicit provider against allowlist
103
+ if (explicitProvider && !validProviders.has(explicitProvider)) {
104
+ throw new Error(
105
+ `Invalid provider "${explicitProvider}". Valid providers: ${[...validProviders].join(", ")}`,
106
+ );
107
+ }
108
+
109
+ // Resolve provider: explicit > MODEL_TO_PROVIDER lookup > current
110
+ const current = getConfig();
111
+ const resolvedProvider =
112
+ explicitProvider ??
113
+ MODEL_TO_PROVIDER[modelId] ??
114
+ current.services.inference.provider;
115
+
116
+ // Auto-reset model when provider changes and current modelId doesn't
117
+ // belong to the new provider's catalog.
118
+ if (
119
+ resolvedProvider !== current.services.inference.provider &&
120
+ !isModelInCatalog(resolvedProvider, modelId)
121
+ ) {
122
+ modelId = getProviderDefaultModel(resolvedProvider);
123
+ }
124
+
125
+ // No-op guard: skip expensive reinitialization when nothing changed
126
+ if (
127
+ modelId === current.services.inference.model &&
128
+ resolvedProvider === current.services.inference.provider
129
+ ) {
130
+ return await getModelInfo();
78
131
  }
79
132
 
80
133
  // Validate provider availability (secure key, env var, or managed proxy) before switching
81
- const provider = MODEL_TO_PROVIDER[modelId];
82
- if (provider && !(await isProviderAvailable(provider))) {
134
+ if (!(await isProviderAvailable(resolvedProvider))) {
83
135
  // Return current model_info so the client resyncs its optimistic state
84
136
  return await getModelInfo();
85
137
  }
@@ -87,10 +139,7 @@ export async function setModel(
87
139
  // Use raw config to avoid persisting env-var API keys to disk
88
140
  const raw = loadRawConfig();
89
141
  setServiceField(raw, "inference", "model", modelId);
90
- // Infer provider from model ID to keep provider and model in sync
91
- if (provider) {
92
- setServiceField(raw, "inference", "provider", provider);
93
- }
142
+ setServiceField(raw, "inference", "provider", resolvedProvider);
94
143
 
95
144
  // Suppress the file watcher callback — setModel already does
96
145
  // the full reload sequence; a redundant watcher-triggered reload
@@ -128,11 +177,7 @@ export async function setModel(
128
177
 
129
178
  ctx.updateConfigFingerprint();
130
179
 
131
- return {
132
- model: config.services.inference.model,
133
- provider: config.services.inference.provider,
134
- configuredProviders: await getConfiguredProviders(),
135
- };
180
+ return await getModelInfo();
136
181
  }
137
182
 
138
183
  /**
@@ -179,7 +224,7 @@ export async function handleModelSet(
179
224
  ctx: HandlerContext,
180
225
  ): Promise<void> {
181
226
  try {
182
- const info = await setModel(msg.model, ctx);
227
+ const info = await setModel(msg.model, ctx, msg.provider);
183
228
  ctx.send({ type: "model_info", ...info });
184
229
  } catch (err) {
185
230
  const message = err instanceof Error ? err.message : String(err);
@@ -9,7 +9,6 @@ import { getConfig } from "../../config/loader.js";
9
9
  import {
10
10
  createCanonicalGuardianRequest,
11
11
  generateCanonicalRequestCode,
12
- resolveCanonicalGuardianRequest,
13
12
  } from "../../memory/canonical-guardian-store.js";
14
13
  import {
15
14
  batchSetDisplayOrders,
@@ -50,27 +49,6 @@ import {
50
49
  pendingStandaloneSecrets,
51
50
  } from "./shared.js";
52
51
 
53
- export function syncCanonicalStatusFromConfirmationDecision(
54
- requestId: string,
55
- decision: ConfirmationResponse["decision"],
56
- ): void {
57
- const targetStatus =
58
- decision === "deny" || decision === "always_deny"
59
- ? ("denied" as const)
60
- : ("approved" as const);
61
-
62
- try {
63
- resolveCanonicalGuardianRequest(requestId, "pending", {
64
- status: targetStatus,
65
- });
66
- } catch (err) {
67
- log.debug(
68
- { err, requestId, targetStatus },
69
- "Failed to resolve canonical request from local confirmation response",
70
- );
71
- }
72
- }
73
-
74
52
  export function makeEventSender(params: {
75
53
  ctx: HandlerContext;
76
54
  conversation: Conversation;
@@ -165,7 +143,6 @@ export function handleConfirmationResponse(
165
143
  undefined,
166
144
  { source: "button" },
167
145
  );
168
- syncCanonicalStatusFromConfirmationDecision(msg.requestId, msg.decision);
169
146
  pendingInteractions.resolve(msg.requestId);
170
147
  return;
171
148
  }
@@ -4,10 +4,10 @@ import * as path from "node:path";
4
4
  import { v4 as uuid } from "uuid";
5
5
 
6
6
  import {
7
- linkAttachmentToMessage,
8
- uploadFileBackedAttachment,
7
+ attachFileBackedAttachmentToMessage,
9
8
  } from "../../memory/attachments-store.js";
10
- import { addMessage } from "../../memory/conversation-crud.js";
9
+ import { addMessage, getConversation } from "../../memory/conversation-crud.js";
10
+ import { syncMessageToDisk } from "../../memory/conversation-disk-view.js";
11
11
  import type { RecordingOptions, RecordingStatus } from "../message-protocol.js";
12
12
  import { type HandlerContext, log } from "./shared.js";
13
13
 
@@ -616,23 +616,6 @@ export async function finalizeAndPublishRecording(params: {
616
616
  const ext = filename.split(".").pop()?.toLowerCase();
617
617
  const mimeType = (ext && RECORDING_MIME_TYPES.get(ext)) || "video/mp4";
618
618
 
619
- // Store as file-backed attachment (avoids reading large files into memory)
620
- const attachment = uploadFileBackedAttachment(
621
- filename,
622
- mimeType,
623
- resolvedPath,
624
- sizeBytes,
625
- );
626
- log.info(
627
- {
628
- recordingId,
629
- attachmentId: attachment.id,
630
- sizeBytes,
631
- filePath: resolvedPath,
632
- },
633
- "Created attachment for standalone recording",
634
- );
635
-
636
619
  // Always create a new assistant message for the recording attachment.
637
620
  // Reusing the last assistant message would attach the recording to an
638
621
  // unrelated older message after reload.
@@ -648,12 +631,34 @@ export async function finalizeAndPublishRecording(params: {
648
631
  "Created assistant message for recording attachment",
649
632
  );
650
633
 
651
- linkAttachmentToMessage(messageId, attachment.id, 0);
634
+ const attachment = attachFileBackedAttachmentToMessage(
635
+ messageId,
636
+ 0,
637
+ filename,
638
+ mimeType,
639
+ resolvedPath,
640
+ sizeBytes,
641
+ );
642
+ log.info(
643
+ {
644
+ recordingId,
645
+ attachmentId: attachment.id,
646
+ sizeBytes,
647
+ filePath: resolvedPath,
648
+ },
649
+ "Created attachment for standalone recording",
650
+ );
652
651
  log.info(
653
652
  { recordingId, messageId, attachmentId: attachment.id },
654
653
  "Linked recording attachment to assistant message",
655
654
  );
656
655
 
656
+ // Sync the recording message (with attachment) to the disk view
657
+ const convForDisk = getConversation(conversationId);
658
+ if (convForDisk) {
659
+ syncMessageToDisk(conversationId, messageId, convForDisk.createdAt);
660
+ }
661
+
657
662
  // Skip server-side thumbnail generation for recordings — the client
658
663
  // generates thumbnails natively from the local file path using
659
664
  // AVAssetImageGenerator, which is faster and doesn't depend on ffmpeg.
@@ -10,6 +10,7 @@
10
10
  import { v4 as uuid } from "uuid";
11
11
 
12
12
  import { escapeAxTreeContent } from "../agent/loop.js";
13
+ import { loadConfig } from "../config/loader.js";
13
14
  import type { ContentBlock } from "../providers/types.js";
14
15
  import type { ToolExecutionResult } from "../tools/types.js";
15
16
  import { AssistantError, ErrorCode } from "../util/errors.js";
@@ -23,7 +24,6 @@ const log = getLogger("host-cu-proxy");
23
24
  // ---------------------------------------------------------------------------
24
25
 
25
26
  const REQUEST_TIMEOUT_SEC = 60;
26
- const MAX_STEPS = 50;
27
27
  const MAX_HISTORY_ENTRIES = 10;
28
28
  const LOOP_DETECTION_WINDOW = 3;
29
29
  const CONSECUTIVE_UNCHANGED_WARNING_THRESHOLD = 2;
@@ -79,7 +79,7 @@ export class HostCuProxy {
79
79
  constructor(
80
80
  sendToClient: (msg: ServerMessage) => void,
81
81
  onInternalResolve?: (requestId: string) => void,
82
- maxSteps = MAX_STEPS,
82
+ maxSteps = loadConfig().maxStepsPerSession,
83
83
  ) {
84
84
  this.sendToClient = sendToClient;
85
85
  this.onInternalResolve = onInternalResolve;
@@ -142,7 +142,6 @@ export async function runDaemon(): Promise<void> {
142
142
  await initLogfire();
143
143
 
144
144
  ensureDataDir();
145
- await runWorkspaceMigrations(getWorkspaceDir(), WORKSPACE_MIGRATIONS);
146
145
 
147
146
  // Load (or generate + persist) the auth signing key so tokens survive
148
147
  // daemon restarts. Must happen after ensureDataDir() creates the
@@ -150,14 +149,16 @@ export async function runDaemon(): Promise<void> {
150
149
  const signingKey = loadOrCreateSigningKey();
151
150
  initAuthSigningKey(signingKey);
152
151
 
153
- log.info("Daemon startup: migrations complete");
154
-
155
152
  seedInterfaceFiles();
156
153
 
157
154
  log.info("Daemon startup: installing templates and initializing DB");
158
155
  installTemplates();
159
156
  ensurePromptFiles();
160
157
 
158
+ // DB must be initialized before workspace migrations because some
159
+ // workspace migrations (e.g. 009-backfill-conversation-disk-view)
160
+ // depend on DB migrations having run (e.g. the inline-attachment-to-disk
161
+ // backfill that populates attachment filePaths).
161
162
  initializeDb();
162
163
  // Seed well-known OAuth provider configurations (insert-if-not-exists)
163
164
  seedOAuthProviders();
@@ -174,6 +175,9 @@ export async function runDaemon(): Promise<void> {
174
175
  }
175
176
  log.info("Daemon startup: DB initialized");
176
177
 
178
+ await runWorkspaceMigrations(getWorkspaceDir(), WORKSPACE_MIGRATIONS);
179
+ log.info("Daemon startup: workspace migrations complete");
180
+
177
181
  // Purge private (temporary) conversations from the previous daemon run.
178
182
  // These are ephemeral by design and should not survive daemon restarts.
179
183
  const { count: purgedCount, deletedMemory } = purgePrivateConversations();
@@ -210,17 +214,21 @@ export async function runDaemon(): Promise<void> {
210
214
  }
211
215
  }
212
216
 
213
- // Expire pending interaction-bound canonical guardian requests left over
214
- // from before this process started. Their in-memory pending-interaction
215
- // session references are gone, so they can never be completed. Only
216
- // interaction-bound kinds (tool_approval, pending_question) are expired;
217
- // persistent kinds (access_request, tool_grant_request) remain valid
218
- // across restarts.
217
+ // Expire stale pending canonical guardian requests left over from before
218
+ // this process started. Two categories are cleaned up:
219
+ //
220
+ // 1. Interaction-bound kinds (tool_approval, pending_question) their
221
+ // in-memory pending-interaction session references are gone, so they
222
+ // can never be completed.
223
+ // 2. Any pending request whose expiresAt has already passed — persistent
224
+ // kinds (access_request, tool_grant_request) that expired while the
225
+ // daemon was stopped are transitioned so dedup logic doesn't return
226
+ // stale rows.
219
227
  const expiredCount = expireAllPendingCanonicalRequests();
220
228
  if (expiredCount > 0) {
221
229
  log.info(
222
230
  { event: "startup_expired_stale_requests", expiredCount },
223
- `Expired ${expiredCount} stale interaction-bound canonical request(s) from previous process`,
231
+ `Expired ${expiredCount} stale canonical request(s) from previous process`,
224
232
  );
225
233
  }
226
234
 
@@ -329,56 +337,74 @@ export async function runDaemon(): Promise<void> {
329
337
  await server.start();
330
338
  log.info("Daemon startup: DaemonServer started");
331
339
 
332
- // Initialize Qdrant vector store non-fatal so the daemon stays up without it
333
- // Prefer QDRANT_HTTP_PORT (locally-spawned Qdrant on a specific port) over
334
- // QDRANT_URL (external Qdrant instance) so the CLI can set the port without
335
- // triggering QdrantManager's external mode which skips local process spawn.
336
- const qdrantHttpPort = getQdrantHttpPortEnv();
337
- const qdrantUrl = qdrantHttpPort
338
- ? `http://127.0.0.1:${qdrantHttpPort}`
339
- : getQdrantUrlEnv() || config.memory.qdrant.url;
340
- log.info({ qdrantUrl }, "Daemon startup: initializing Qdrant");
341
- const qdrantManager = new QdrantManager({ url: qdrantUrl });
342
- try {
343
- await qdrantManager.start();
344
- const embeddingSelection = await selectEmbeddingBackend(config);
345
- const embeddingModel = embeddingSelection.backend
346
- ? `${embeddingSelection.backend.provider}:${embeddingSelection.backend.model}:sparse-v${SPARSE_EMBEDDING_VERSION}`
347
- : undefined;
348
- const qdrantClient = initQdrantClient({
349
- url: qdrantUrl,
350
- collection: config.memory.qdrant.collection,
351
- vectorSize: config.memory.qdrant.vectorSize,
352
- onDisk: config.memory.qdrant.onDisk,
353
- quantization: config.memory.qdrant.quantization,
354
- embeddingModel,
355
- });
340
+ // Mutable refs for Qdrant and memory worker so background init can assign
341
+ // them and the shutdown handler always sees the latest value.
342
+ const bgRefs: {
343
+ qdrantManager: QdrantManager | null;
344
+ memoryWorker: { stop(): void } | null;
345
+ } = { qdrantManager: null, memoryWorker: null };
346
+
347
+ // Initialize Qdrant vector store and memory worker in the background so the
348
+ // RuntimeHttpServer can start accepting requests without waiting for Qdrant.
349
+ async function initializeQdrantAndMemory(): Promise<void> {
350
+ // Prefer QDRANT_HTTP_PORT (locally-spawned Qdrant on a specific port) over
351
+ // QDRANT_URL (external Qdrant instance) so the CLI can set the port without
352
+ // triggering QdrantManager's external mode which skips local process spawn.
353
+ const qdrantHttpPort = getQdrantHttpPortEnv();
354
+ const qdrantUrl = qdrantHttpPort
355
+ ? `http://127.0.0.1:${qdrantHttpPort}`
356
+ : getQdrantUrlEnv() || config.memory.qdrant.url;
357
+ log.info({ qdrantUrl }, "Daemon startup: initializing Qdrant");
358
+ const manager = new QdrantManager({ url: qdrantUrl });
359
+ bgRefs.qdrantManager = manager;
360
+ try {
361
+ await manager.start();
362
+ const embeddingSelection = await selectEmbeddingBackend(config);
363
+ const embeddingModel = embeddingSelection.backend
364
+ ? `${embeddingSelection.backend.provider}:${embeddingSelection.backend.model}:sparse-v${SPARSE_EMBEDDING_VERSION}`
365
+ : undefined;
366
+ const qdrantClient = initQdrantClient({
367
+ url: qdrantUrl,
368
+ collection: config.memory.qdrant.collection,
369
+ vectorSize: config.memory.qdrant.vectorSize,
370
+ onDisk: config.memory.qdrant.onDisk,
371
+ quantization: config.memory.qdrant.quantization,
372
+ embeddingModel,
373
+ });
374
+
375
+ // Eagerly ensure the collection exists so we detect migrations
376
+ // (unnamed→named vectors, dimension/model changes) at startup.
377
+ // If a destructive migration occurred, enqueue a rebuild_index job
378
+ // to re-embed all memory items from the SQLite cache.
379
+ const { migrated } = await qdrantClient.ensureCollection();
380
+ if (migrated) {
381
+ enqueueMemoryJob("rebuild_index", {});
382
+ log.info(
383
+ "Qdrant collection was migrated — enqueued rebuild_index job",
384
+ );
385
+ }
356
386
 
357
- // Eagerly ensure the collection exists so we detect migrations
358
- // (unnamed→named vectors, dimension/model changes) at startup.
359
- // If a destructive migration occurred, enqueue a rebuild_index job
360
- // to re-embed all memory items from the SQLite cache.
361
- const { migrated } = await qdrantClient.ensureCollection();
362
- if (migrated) {
363
- enqueueMemoryJob("rebuild_index", {});
364
- log.info("Qdrant collection was migrated — enqueued rebuild_index job");
387
+ log.info("Qdrant vector store initialized");
388
+ } catch (err) {
389
+ log.warn(
390
+ { err },
391
+ "Qdrant failed to start memory features will be unavailable",
392
+ );
365
393
  }
366
394
 
367
- log.info("Qdrant vector store initialized");
368
- } catch (err) {
369
- log.warn(
370
- { err },
371
- "Qdrant failed to start memory features will be unavailable",
395
+ log.info("Daemon startup: starting memory worker");
396
+ bgRefs.memoryWorker = startMemoryJobsWorker();
397
+
398
+ // Seed capability memories for first-party catalog skills so the memory
399
+ // pipeline can surface relevant skills via semantic search.
400
+ void seedCatalogSkillMemories().catch((err) =>
401
+ log.warn({ err }, "Catalog skill memory seeding failed — continuing"),
372
402
  );
373
403
  }
374
404
 
375
- log.info("Daemon startup: starting memory worker");
376
- const memoryWorker = startMemoryJobsWorker();
377
-
378
- // Seed capability memories for first-party catalog skills so the memory
379
- // pipeline can surface relevant skills via semantic search.
380
- void seedCatalogSkillMemories().catch((err) =>
381
- log.warn({ err }, "Catalog skill memory seeding failed — continuing"),
405
+ // Fire-and-forget: Qdrant init runs concurrently with the rest of startup
406
+ void initializeQdrantAndMemory().catch((err) =>
407
+ log.warn({ err }, "Background Qdrant init failed"),
382
408
  );
383
409
 
384
410
  registerWatcherProviders();
@@ -459,7 +485,7 @@ export async function runDaemon(): Promise<void> {
459
485
  title: notification.title,
460
486
  body: notification.body,
461
487
  },
462
- dedupeKey: `watcher:notification:${Date.now()}`,
488
+ dedupeKey: `watcher:notification:${crypto.randomUUID()}`,
463
489
  });
464
490
  },
465
491
  (params) => {
@@ -477,7 +503,7 @@ export async function runDaemon(): Promise<void> {
477
503
  title: params.title,
478
504
  body: params.body,
479
505
  },
480
- dedupeKey: `watcher:escalation:${Date.now()}`,
506
+ dedupeKey: `watcher:escalation:${crypto.randomUUID()}`,
481
507
  });
482
508
  },
483
509
  (info) => {
@@ -532,13 +558,22 @@ export async function runDaemon(): Promise<void> {
532
558
  getOrCreateConversation: (conversationId) =>
533
559
  server.getConversationForMessages(conversationId),
534
560
  assistantEventHub,
535
- resolveAttachments: (attachmentIds) =>
536
- attachmentsStore.getAttachmentsByIds(attachmentIds).map((a) => ({
561
+ resolveAttachments: (attachmentIds) => {
562
+ const resolved = attachmentsStore.getAttachmentsByIds(attachmentIds, {
563
+ hydrateFileData: true,
564
+ });
565
+ const sourcePaths =
566
+ attachmentsStore.getSourcePathsForAttachments(attachmentIds);
567
+ return resolved.map((a) => ({
537
568
  id: a.id,
538
569
  filename: a.originalFilename,
539
570
  mimeType: a.mimeType,
540
571
  data: a.dataBase64,
541
- })),
572
+ ...(sourcePaths.has(a.id)
573
+ ? { filePath: sourcePaths.get(a.id) }
574
+ : {}),
575
+ }));
576
+ },
542
577
  },
543
578
  findConversation: (conversationId) =>
544
579
  server.findConversation(conversationId),
@@ -623,13 +658,20 @@ export async function runDaemon(): Promise<void> {
623
658
  setVoiceBridgeDeps({
624
659
  getOrCreateConversation: (conversationId, _transport) =>
625
660
  server.getConversationForMessages(conversationId),
626
- resolveAttachments: (attachmentIds) =>
627
- attachmentsStore.getAttachmentsByIds(attachmentIds).map((a) => ({
661
+ resolveAttachments: (attachmentIds) => {
662
+ const resolved = attachmentsStore.getAttachmentsByIds(attachmentIds, {
663
+ hydrateFileData: true,
664
+ });
665
+ const sourcePaths =
666
+ attachmentsStore.getSourcePathsForAttachments(attachmentIds);
667
+ return resolved.map((a) => ({
628
668
  id: a.id,
629
669
  filename: a.originalFilename,
630
670
  mimeType: a.mimeType,
631
671
  data: a.dataBase64,
632
- })),
672
+ ...(sourcePaths.has(a.id) ? { filePath: sourcePaths.get(a.id) } : {}),
673
+ }));
674
+ },
633
675
  deriveDefaultStrictSideEffects: (conversationId) => {
634
676
  const conversationType = getConversationType(conversationId);
635
677
  return conversationType === "private";
@@ -881,8 +923,8 @@ export async function runDaemon(): Promise<void> {
881
923
  hookManager,
882
924
  runtimeHttp,
883
925
  scheduler,
884
- memoryWorker,
885
- qdrantManager,
926
+ getMemoryWorker: () => bgRefs.memoryWorker,
927
+ getQdrantManager: () => bgRefs.qdrantManager,
886
928
  mcpManager,
887
929
  telemetryReporter,
888
930
  cleanupPidFile,
@@ -38,6 +38,7 @@ export * from "./message-types/skills.js";
38
38
  export * from "./message-types/subagents.js";
39
39
  export * from "./message-types/surfaces.js";
40
40
  export * from "./message-types/trust.js";
41
+ export * from "./message-types/upgrades.js";
41
42
  export * from "./message-types/work-items.js";
42
43
  export * from "./message-types/workspace.js";
43
44
 
@@ -123,6 +124,7 @@ import type {
123
124
  _TrustClientMessages,
124
125
  _TrustServerMessages,
125
126
  } from "./message-types/trust.js";
127
+ import type { _UpgradesServerMessages } from "./message-types/upgrades.js";
126
128
  import type {
127
129
  _WorkItemsClientMessages,
128
130
  _WorkItemsServerMessages,
@@ -194,6 +196,7 @@ export type ServerMessage =
194
196
  | _InboxServerMessages
195
197
  | _PairingServerMessages
196
198
  | _NotificationsServerMessages
199
+ | _UpgradesServerMessages
197
200
  | _AcpServerMessages
198
201
  | SubagentEvent;
199
202
 
@@ -80,6 +80,7 @@ export interface ModelGetRequest {
80
80
  export interface ModelSetRequest {
81
81
  type: "model_set";
82
82
  model: string;
83
+ provider?: string;
83
84
  }
84
85
 
85
86
  export interface ImageGenModelSetRequest {
@@ -177,6 +178,12 @@ export interface AssistantAttention {
177
178
  lastSeenSignalType?: string;
178
179
  }
179
180
 
181
+ export interface ConversationForkParent {
182
+ conversationId: string;
183
+ messageId: string;
184
+ title: string;
185
+ }
186
+
180
187
  export interface ConversationListResponse {
181
188
  type: "conversation_list_response";
182
189
  conversations: Array<{
@@ -193,6 +200,7 @@ export interface ConversationListResponse {
193
200
  assistantAttention?: AssistantAttention;
194
201
  displayOrder?: number;
195
202
  isPinned?: boolean;
203
+ forkParent?: ConversationForkParent;
196
204
  }>;
197
205
  /** Whether more conversations exist beyond the returned page. */
198
206
  hasMore?: boolean;
@@ -231,6 +239,7 @@ export interface GenerationHandoff {
231
239
  requestId?: string;
232
240
  queuedCount: number;
233
241
  attachments?: UserMessageAttachment[];
242
+ attachmentWarnings?: string[];
234
243
  /** Database ID of the persisted assistant message, if any. */
235
244
  messageId?: string;
236
245
  }
@@ -240,6 +249,16 @@ export interface ModelInfo {
240
249
  model: string;
241
250
  provider: string;
242
251
  configuredProviders?: string[];
252
+ availableModels?: Array<{ id: string; displayName: string }>;
253
+ allProviders?: Array<{
254
+ id: string;
255
+ displayName: string;
256
+ models: Array<{ id: string; displayName: string }>;
257
+ defaultModel: string;
258
+ apiKeyUrl?: string;
259
+ apiKeyPlaceholder?: string;
260
+ }>;
261
+ maskedKeys?: Record<string, string>;
243
262
  }
244
263
 
245
264
  export interface HistoryResponseToolCall {
@@ -183,6 +183,7 @@ export interface MessageComplete {
183
183
  type: "message_complete";
184
184
  conversationId?: string;
185
185
  attachments?: UserMessageAttachment[];
186
+ attachmentWarnings?: string[];
186
187
  /** Database ID of the persisted assistant message, if any. */
187
188
  messageId?: string;
188
189
  }
@@ -34,6 +34,8 @@ export interface UserMessageAttachment {
34
34
  filename: string;
35
35
  mimeType: string;
36
36
  data: string;
37
+ /** Origin of the attachment on the daemon side, when known. */
38
+ sourceType?: "sandbox_file" | "host_file" | "tool_block";
37
39
  extractedText?: string;
38
40
  /** Original file size in bytes. Present when data was omitted from history_response to reduce payload size. */
39
41
  sizeBytes?: number;