@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
@@ -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();
@@ -196,31 +200,43 @@ export async function runDaemon(): Promise<void> {
196
200
  targetId: itemId,
197
201
  });
198
202
  }
203
+ for (const summaryId of deletedMemory.deletedSummaryIds) {
204
+ enqueueMemoryJob("delete_qdrant_vectors", {
205
+ targetType: "summary",
206
+ targetId: summaryId,
207
+ });
208
+ }
199
209
  if (
200
210
  deletedMemory.segmentIds.length > 0 ||
201
- deletedMemory.orphanedItemIds.length > 0
211
+ deletedMemory.orphanedItemIds.length > 0 ||
212
+ deletedMemory.deletedSummaryIds.length > 0
202
213
  ) {
203
214
  log.info(
204
215
  {
205
216
  segments: deletedMemory.segmentIds.length,
206
217
  orphanedItems: deletedMemory.orphanedItemIds.length,
218
+ deletedSummaries: deletedMemory.deletedSummaryIds.length,
207
219
  },
208
220
  "Enqueued Qdrant vector cleanup jobs for purged private conversations",
209
221
  );
210
222
  }
211
223
  }
212
224
 
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.
225
+ // Expire stale pending canonical guardian requests left over from before
226
+ // this process started. Two categories are cleaned up:
227
+ //
228
+ // 1. Interaction-bound kinds (tool_approval, pending_question) their
229
+ // in-memory pending-interaction session references are gone, so they
230
+ // can never be completed.
231
+ // 2. Any pending request whose expiresAt has already passed — persistent
232
+ // kinds (access_request, tool_grant_request) that expired while the
233
+ // daemon was stopped are transitioned so dedup logic doesn't return
234
+ // stale rows.
219
235
  const expiredCount = expireAllPendingCanonicalRequests();
220
236
  if (expiredCount > 0) {
221
237
  log.info(
222
238
  { event: "startup_expired_stale_requests", expiredCount },
223
- `Expired ${expiredCount} stale interaction-bound canonical request(s) from previous process`,
239
+ `Expired ${expiredCount} stale canonical request(s) from previous process`,
224
240
  );
225
241
  }
226
242
 
@@ -329,56 +345,74 @@ export async function runDaemon(): Promise<void> {
329
345
  await server.start();
330
346
  log.info("Daemon startup: DaemonServer started");
331
347
 
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
- });
348
+ // Mutable refs for Qdrant and memory worker so background init can assign
349
+ // them and the shutdown handler always sees the latest value.
350
+ const bgRefs: {
351
+ qdrantManager: QdrantManager | null;
352
+ memoryWorker: { stop(): void } | null;
353
+ } = { qdrantManager: null, memoryWorker: null };
354
+
355
+ // Initialize Qdrant vector store and memory worker in the background so the
356
+ // RuntimeHttpServer can start accepting requests without waiting for Qdrant.
357
+ async function initializeQdrantAndMemory(): Promise<void> {
358
+ // Prefer QDRANT_HTTP_PORT (locally-spawned Qdrant on a specific port) over
359
+ // QDRANT_URL (external Qdrant instance) so the CLI can set the port without
360
+ // triggering QdrantManager's external mode which skips local process spawn.
361
+ const qdrantHttpPort = getQdrantHttpPortEnv();
362
+ const qdrantUrl = qdrantHttpPort
363
+ ? `http://127.0.0.1:${qdrantHttpPort}`
364
+ : getQdrantUrlEnv() || config.memory.qdrant.url;
365
+ log.info({ qdrantUrl }, "Daemon startup: initializing Qdrant");
366
+ const manager = new QdrantManager({ url: qdrantUrl });
367
+ bgRefs.qdrantManager = manager;
368
+ try {
369
+ await manager.start();
370
+ const embeddingSelection = await selectEmbeddingBackend(config);
371
+ const embeddingModel = embeddingSelection.backend
372
+ ? `${embeddingSelection.backend.provider}:${embeddingSelection.backend.model}:sparse-v${SPARSE_EMBEDDING_VERSION}`
373
+ : undefined;
374
+ const qdrantClient = initQdrantClient({
375
+ url: qdrantUrl,
376
+ collection: config.memory.qdrant.collection,
377
+ vectorSize: config.memory.qdrant.vectorSize,
378
+ onDisk: config.memory.qdrant.onDisk,
379
+ quantization: config.memory.qdrant.quantization,
380
+ embeddingModel,
381
+ });
382
+
383
+ // Eagerly ensure the collection exists so we detect migrations
384
+ // (unnamed→named vectors, dimension/model changes) at startup.
385
+ // If a destructive migration occurred, enqueue a rebuild_index job
386
+ // to re-embed all memory items from the SQLite cache.
387
+ const { migrated } = await qdrantClient.ensureCollection();
388
+ if (migrated) {
389
+ enqueueMemoryJob("rebuild_index", {});
390
+ log.info(
391
+ "Qdrant collection was migrated — enqueued rebuild_index job",
392
+ );
393
+ }
356
394
 
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");
395
+ log.info("Qdrant vector store initialized");
396
+ } catch (err) {
397
+ log.warn(
398
+ { err },
399
+ "Qdrant failed to start memory features will be unavailable",
400
+ );
365
401
  }
366
402
 
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",
403
+ log.info("Daemon startup: starting memory worker");
404
+ bgRefs.memoryWorker = startMemoryJobsWorker();
405
+
406
+ // Seed capability memories for first-party catalog skills so the memory
407
+ // pipeline can surface relevant skills via semantic search.
408
+ void seedCatalogSkillMemories().catch((err) =>
409
+ log.warn({ err }, "Catalog skill memory seeding failed — continuing"),
372
410
  );
373
411
  }
374
412
 
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"),
413
+ // Fire-and-forget: Qdrant init runs concurrently with the rest of startup
414
+ void initializeQdrantAndMemory().catch((err) =>
415
+ log.warn({ err }, "Background Qdrant init failed"),
382
416
  );
383
417
 
384
418
  registerWatcherProviders();
@@ -459,7 +493,7 @@ export async function runDaemon(): Promise<void> {
459
493
  title: notification.title,
460
494
  body: notification.body,
461
495
  },
462
- dedupeKey: `watcher:notification:${Date.now()}`,
496
+ dedupeKey: `watcher:notification:${crypto.randomUUID()}`,
463
497
  });
464
498
  },
465
499
  (params) => {
@@ -477,7 +511,7 @@ export async function runDaemon(): Promise<void> {
477
511
  title: params.title,
478
512
  body: params.body,
479
513
  },
480
- dedupeKey: `watcher:escalation:${Date.now()}`,
514
+ dedupeKey: `watcher:escalation:${crypto.randomUUID()}`,
481
515
  });
482
516
  },
483
517
  (info) => {
@@ -532,13 +566,22 @@ export async function runDaemon(): Promise<void> {
532
566
  getOrCreateConversation: (conversationId) =>
533
567
  server.getConversationForMessages(conversationId),
534
568
  assistantEventHub,
535
- resolveAttachments: (attachmentIds) =>
536
- attachmentsStore.getAttachmentsByIds(attachmentIds).map((a) => ({
569
+ resolveAttachments: (attachmentIds) => {
570
+ const resolved = attachmentsStore.getAttachmentsByIds(attachmentIds, {
571
+ hydrateFileData: true,
572
+ });
573
+ const sourcePaths =
574
+ attachmentsStore.getSourcePathsForAttachments(attachmentIds);
575
+ return resolved.map((a) => ({
537
576
  id: a.id,
538
577
  filename: a.originalFilename,
539
578
  mimeType: a.mimeType,
540
579
  data: a.dataBase64,
541
- })),
580
+ ...(sourcePaths.has(a.id)
581
+ ? { filePath: sourcePaths.get(a.id) }
582
+ : {}),
583
+ }));
584
+ },
542
585
  },
543
586
  findConversation: (conversationId) =>
544
587
  server.findConversation(conversationId),
@@ -623,13 +666,20 @@ export async function runDaemon(): Promise<void> {
623
666
  setVoiceBridgeDeps({
624
667
  getOrCreateConversation: (conversationId, _transport) =>
625
668
  server.getConversationForMessages(conversationId),
626
- resolveAttachments: (attachmentIds) =>
627
- attachmentsStore.getAttachmentsByIds(attachmentIds).map((a) => ({
669
+ resolveAttachments: (attachmentIds) => {
670
+ const resolved = attachmentsStore.getAttachmentsByIds(attachmentIds, {
671
+ hydrateFileData: true,
672
+ });
673
+ const sourcePaths =
674
+ attachmentsStore.getSourcePathsForAttachments(attachmentIds);
675
+ return resolved.map((a) => ({
628
676
  id: a.id,
629
677
  filename: a.originalFilename,
630
678
  mimeType: a.mimeType,
631
679
  data: a.dataBase64,
632
- })),
680
+ ...(sourcePaths.has(a.id) ? { filePath: sourcePaths.get(a.id) } : {}),
681
+ }));
682
+ },
633
683
  deriveDefaultStrictSideEffects: (conversationId) => {
634
684
  const conversationType = getConversationType(conversationId);
635
685
  return conversationType === "private";
@@ -881,8 +931,8 @@ export async function runDaemon(): Promise<void> {
881
931
  hookManager,
882
932
  runtimeHttp,
883
933
  scheduler,
884
- memoryWorker,
885
- qdrantManager,
934
+ getMemoryWorker: () => bgRefs.memoryWorker,
935
+ getQdrantManager: () => bgRefs.qdrantManager,
886
936
  mcpManager,
887
937
  telemetryReporter,
888
938
  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,15 @@ 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
+ }>;
243
261
  }
244
262
 
245
263
  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;
@@ -100,6 +100,8 @@ export interface DynamicPageSurfaceData {
100
100
  width?: number;
101
101
  height?: number;
102
102
  appId?: string;
103
+ /** Filesystem directory name for this app (may differ from `appId`). */
104
+ dirName?: string;
103
105
  reloadGeneration?: number;
104
106
  status?: string;
105
107
  preview?: DynamicPagePreview;
@@ -0,0 +1,23 @@
1
+ /** Broadcast to connected clients when a service group update is about to begin. */
2
+ export interface ServiceGroupUpdateStarting {
3
+ type: "service_group_update_starting";
4
+ /** The version being upgraded to. */
5
+ targetVersion: string;
6
+ /** Estimated seconds of downtime. */
7
+ expectedDowntimeSeconds: number;
8
+ }
9
+
10
+ /** Broadcast to connected clients when a service group update has completed. */
11
+ export interface ServiceGroupUpdateComplete {
12
+ type: "service_group_update_complete";
13
+ /** The version that was installed (may differ from target if rolled back). */
14
+ installedVersion: string;
15
+ /** Whether the update succeeded or rolled back. */
16
+ success: boolean;
17
+ /** If rolled back, the version reverted to. */
18
+ rolledBackToVersion?: string;
19
+ }
20
+
21
+ export type _UpgradesServerMessages =
22
+ | ServiceGroupUpdateStarting
23
+ | ServiceGroupUpdateComplete;
@@ -6,6 +6,7 @@ import {
6
6
  getAcpSessionManager,
7
7
  setBroadcastToAllClients,
8
8
  } from "../acp/index.js";
9
+ import { enrichMessageWithSourcePaths } from "../agent/attachments.js";
9
10
  import {
10
11
  createAssistantMessage,
11
12
  createUserMessage,
@@ -34,12 +35,14 @@ import {
34
35
  } from "../memory/canonical-guardian-store.js";
35
36
  import {
36
37
  addMessage,
38
+ getConversation,
37
39
  getConversationMemoryScopeId,
38
40
  getConversationType,
39
41
  provenanceFromTrustContext,
40
42
  setConversationOriginChannelIfUnset,
41
43
  setConversationOriginInterfaceIfUnset,
42
44
  } from "../memory/conversation-crud.js";
45
+ import { updateMetaFile } from "../memory/conversation-disk-view.js";
43
46
  import { getOrCreateConversation } from "../memory/conversation-key-store.js";
44
47
  import { buildSystemPrompt } from "../prompts/system-prompt.js";
45
48
  import { resolveManagedProxyContext } from "../providers/managed-proxy/context.js";
@@ -75,7 +78,7 @@ import {
75
78
  } from "./conversation.js";
76
79
  import { ConversationEvictor } from "./conversation-evictor.js";
77
80
  import { resolveChannelCapabilities } from "./conversation-runtime-assembly.js";
78
- import { resolveSlash } from "./conversation-slash.js";
81
+ import { resolveSlash, type SlashContext } from "./conversation-slash.js";
79
82
  import { undoLastMessage } from "./handlers/conversations.js";
80
83
  import { parseIdentityFields } from "./handlers/identity.js";
81
84
  import type {
@@ -878,6 +881,7 @@ export class DaemonServer {
878
881
  filename: string;
879
882
  mimeType: string;
880
883
  data: string;
884
+ filePath?: string;
881
885
  }[];
882
886
  }> {
883
887
  const ingressCheck = checkIngressForSecrets(content);
@@ -960,12 +964,22 @@ export class DaemonServer {
960
964
  });
961
965
 
962
966
  const attachments = attachmentIds
963
- ? attachmentsStore.getAttachmentsByIds(attachmentIds).map((a) => ({
964
- id: a.id,
965
- filename: a.originalFilename,
966
- mimeType: a.mimeType,
967
- data: a.dataBase64,
968
- }))
967
+ ? (() => {
968
+ const resolved = attachmentsStore.getAttachmentsByIds(attachmentIds, {
969
+ hydrateFileData: true,
970
+ });
971
+ const sourcePaths =
972
+ attachmentsStore.getSourcePathsForAttachments(attachmentIds);
973
+ return resolved.map((a) => ({
974
+ id: a.id,
975
+ filename: a.originalFilename,
976
+ mimeType: a.mimeType,
977
+ data: a.dataBase64,
978
+ ...(sourcePaths.has(a.id)
979
+ ? { filePath: sourcePaths.get(a.id) }
980
+ : {}),
981
+ }));
982
+ })()
969
983
  : [];
970
984
 
971
985
  return { conversation, attachments };
@@ -1057,14 +1071,32 @@ export class DaemonServer {
1057
1071
  sourceInterface,
1058
1072
  );
1059
1073
 
1060
- const slashResult = await resolveSlash(content);
1074
+ const config = getConfig();
1075
+ const serverInterfaceCtx = conversation.getTurnInterfaceContext();
1076
+ const slashContext: SlashContext = {
1077
+ messageCount: conversation.getMessages().length,
1078
+ inputTokens: conversation.usageStats.inputTokens,
1079
+ outputTokens: conversation.usageStats.outputTokens,
1080
+ maxInputTokens: config.contextWindow.maxInputTokens,
1081
+ model: config.services.inference.model,
1082
+ provider: config.services.inference.provider,
1083
+ estimatedCost: conversation.usageStats.estimatedCost,
1084
+ userMessageInterface: serverInterfaceCtx?.userMessageInterface,
1085
+ };
1086
+ const slashResult = await resolveSlash(content, slashContext);
1061
1087
 
1062
1088
  if (slashResult.kind === "unknown") {
1063
1089
  const serverTurnCtx = conversation.getTurnChannelContext();
1064
- const serverInterfaceCtx = conversation.getTurnInterfaceContext();
1065
1090
  const serverProvenance = provenanceFromTrustContext(
1066
1091
  conversation.trustContext,
1067
1092
  );
1093
+ const imageSourcePaths: Record<string, string> = {};
1094
+ for (let i = 0; i < attachments.length; i++) {
1095
+ const a = attachments[i];
1096
+ if (a.filePath && a.mimeType.toLowerCase().startsWith("image/")) {
1097
+ imageSourcePaths[`${i}:${a.filename}`] = a.filePath;
1098
+ }
1099
+ }
1068
1100
  const serverChannelMeta = {
1069
1101
  ...serverProvenance,
1070
1102
  ...(serverTurnCtx
@@ -1080,15 +1112,19 @@ export class DaemonServer {
1080
1112
  serverInterfaceCtx.assistantMessageInterface,
1081
1113
  }
1082
1114
  : {}),
1115
+ ...(Object.keys(imageSourcePaths).length > 0
1116
+ ? { imageSourcePaths }
1117
+ : {}),
1083
1118
  };
1084
- const userMsg = createUserMessage(content, attachments);
1119
+ const cleanMsg = createUserMessage(content, attachments);
1120
+ const llmMsg = enrichMessageWithSourcePaths(cleanMsg, attachments);
1085
1121
  const persisted = await addMessage(
1086
1122
  conversationId,
1087
1123
  "user",
1088
- JSON.stringify(userMsg.content),
1124
+ JSON.stringify(cleanMsg.content),
1089
1125
  serverChannelMeta,
1090
1126
  );
1091
- conversation.getMessages().push(userMsg);
1127
+ conversation.getMessages().push(llmMsg);
1092
1128
 
1093
1129
  if (serverTurnCtx) {
1094
1130
  try {
@@ -1117,6 +1153,21 @@ export class DaemonServer {
1117
1153
  }
1118
1154
  }
1119
1155
 
1156
+ // Rewrite meta.json so the on-disk metadata reflects the origin channel
1157
+ if (serverTurnCtx || serverInterfaceCtx) {
1158
+ try {
1159
+ const convForMeta = getConversation(conversationId);
1160
+ if (convForMeta) {
1161
+ updateMetaFile(convForMeta);
1162
+ }
1163
+ } catch (err) {
1164
+ log.warn(
1165
+ { err, conversationId },
1166
+ "Failed to update disk meta (best-effort)",
1167
+ );
1168
+ }
1169
+ }
1170
+
1120
1171
  const assistantMsg = createAssistantMessage(slashResult.message);
1121
1172
  await addMessage(
1122
1173
  conversationId,
@@ -1198,9 +1249,29 @@ export class DaemonServer {
1198
1249
  * Look up an active conversation that owns a given surfaceId.
1199
1250
  */
1200
1251
  findConversationBySurfaceId(surfaceId: string): Conversation | undefined {
1252
+ // Fast path: exact surfaceId match in surfaceState
1201
1253
  for (const c of this.conversations.values()) {
1202
1254
  if (c.surfaceState.has(surfaceId)) return c;
1203
1255
  }
1256
+
1257
+ // Fallback: standalone app surfaces use "app-open-{appId}" IDs that
1258
+ // were never part of any conversation. Extract the appId and find
1259
+ // a conversation whose surfaceState has a surface for that app.
1260
+ const appOpenPrefix = "app-open-";
1261
+ if (surfaceId.startsWith(appOpenPrefix)) {
1262
+ const appId = surfaceId.slice(appOpenPrefix.length);
1263
+ for (const c of this.conversations.values()) {
1264
+ for (const [, state] of c.surfaceState.entries()) {
1265
+ const data = state.data as unknown as Record<string, unknown>;
1266
+ if (data?.appId === appId) {
1267
+ // Register this surfaceId so subsequent lookups are O(1)
1268
+ c.surfaceState.set(surfaceId, state);
1269
+ return c;
1270
+ }
1271
+ }
1272
+ }
1273
+ }
1274
+
1204
1275
  return undefined;
1205
1276
  }
1206
1277
 
@@ -21,8 +21,8 @@ export interface ShutdownDeps {
21
21
  hookManager: HookManager;
22
22
  runtimeHttp: RuntimeHttpServer | null;
23
23
  scheduler: { stop(): void };
24
- memoryWorker: { stop(): void };
25
- qdrantManager: QdrantManager;
24
+ getMemoryWorker: () => { stop(): void } | null;
25
+ getQdrantManager: () => QdrantManager | null;
26
26
  mcpManager: McpServerManager | null;
27
27
  telemetryReporter: { stop(): Promise<void> } | null;
28
28
  cleanupPidFile: () => void;
@@ -106,7 +106,7 @@ export function installShutdownHandlers(deps: ShutdownDeps): void {
106
106
  if (deps.runtimeHttp) await deps.runtimeHttp.stop();
107
107
  await browserManager.closeAllPages();
108
108
  deps.scheduler.stop();
109
- deps.memoryWorker.stop();
109
+ deps.getMemoryWorker()?.stop();
110
110
 
111
111
  if (deps.mcpManager) {
112
112
  try {
@@ -116,7 +116,7 @@ export function installShutdownHandlers(deps: ShutdownDeps): void {
116
116
  }
117
117
  }
118
118
 
119
- await deps.qdrantManager.stop();
119
+ await deps.getQdrantManager()?.stop();
120
120
 
121
121
  // Checkpoint WAL and close SQLite so no writes are lost on exit.
122
122
  // Checkpoint and close are in separate try blocks so that close()
@@ -143,7 +143,10 @@ export function installShutdownHandlers(deps: ShutdownDeps): void {
143
143
  process.on("SIGHUP", shutdown);
144
144
 
145
145
  process.on("unhandledRejection", (reason) => {
146
- log.error({ err: reason }, "Unhandled promise rejection — initiating shutdown");
146
+ log.error(
147
+ { err: reason },
148
+ "Unhandled promise rejection — initiating shutdown",
149
+ );
147
150
  Sentry.captureException(reason);
148
151
  exitCode = 1;
149
152
  void shutdown();
@@ -27,6 +27,14 @@ const DAEMON_ERROR_PREFIX = "DAEMON_ERROR:";
27
27
  * Inspect an error and return a categorized {@link DaemonStartupError}.
28
28
  */
29
29
  export function categorizeDaemonError(err: unknown): DaemonStartupError {
30
+ if (err == null) {
31
+ return {
32
+ error: "UNKNOWN",
33
+ message: String(err),
34
+ detail: "An unexpected error occurred during startup.",
35
+ };
36
+ }
37
+
30
38
  const code = (err as { code?: string }).code ?? "";
31
39
  const name = (err as { name?: string }).name ?? "";
32
40
  const message =
@@ -96,6 +104,7 @@ export function categorizeDaemonError(err: unknown): DaemonStartupError {
96
104
  message.startsWith("Invalid RUNTIME_HTTP_PORT") ||
97
105
  message.startsWith("Invalid integer for GATEWAY_PORT") ||
98
106
  message.startsWith("Invalid integer for RUNTIME_HTTP_PORT") ||
107
+ message.startsWith("Invalid integer for QDRANT_HTTP_PORT") ||
99
108
  /\benv\b.*\bvalidat/i.test(message) ||
100
109
  /\bvalidat.*\benv\b/i.test(message)
101
110
  ) {