@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
@@ -15,11 +15,16 @@ import type {
15
15
  } from "../channels/types.js";
16
16
  import {
17
17
  addMessage,
18
+ getConversation,
18
19
  getMessageById,
19
20
  provenanceFromTrustContext,
20
21
  updateMessageContent,
21
22
  } from "../memory/conversation-crud.js";
22
- import { recordRequestLog } from "../memory/llm-request-log-store.js";
23
+ import { syncMessageToDisk } from "../memory/conversation-disk-view.js";
24
+ import {
25
+ backfillMessageIdOnLogs,
26
+ recordRequestLog,
27
+ } from "../memory/llm-request-log-store.js";
23
28
  import type { ContentBlock, ImageContent } from "../providers/types.js";
24
29
  import type { DirectiveRequest } from "./assistant-attachments.js";
25
30
  import {
@@ -48,6 +53,8 @@ export interface EventHandlerState {
48
53
  llmCallStartedEmitted: boolean;
49
54
  pendingDirectiveDisplayBuffer: string;
50
55
  firstAssistantText: string;
56
+ /** Most recent resolved provider for the current exchange's usage accounting. */
57
+ exchangeProviderName: string | undefined;
51
58
  exchangeInputTokens: number;
52
59
  exchangeCacheCreationInputTokens: number;
53
60
  exchangeCacheReadInputTokens: number;
@@ -114,6 +121,7 @@ export function createEventHandlerState(): EventHandlerState {
114
121
  llmCallStartedEmitted: false,
115
122
  pendingDirectiveDisplayBuffer: "",
116
123
  firstAssistantText: "",
124
+ exchangeProviderName: undefined,
117
125
  exchangeInputTokens: 0,
118
126
  exchangeCacheCreationInputTokens: 0,
119
127
  exchangeCacheReadInputTokens: 0,
@@ -167,6 +175,12 @@ export function emitLlmCallStartedIfNeeded(
167
175
  );
168
176
  }
169
177
 
178
+ // ── Client Payload Size Caps ─────────────────────────────────────────
179
+ // tool_input_delta streams accumulated JSON as tools run. For non-app
180
+ // tools the client discards it (extractCodePreview only handles app tools),
181
+ // so we skip forwarding entirely to avoid transport/decode overhead.
182
+ const APP_TOOL_NAMES = new Set(["app_create"]);
183
+
170
184
  // ── Friendly Tool Names ──────────────────────────────────────────────
171
185
 
172
186
  const TOOL_FRIENDLY_NAMES: Record<string, string> = {
@@ -183,11 +197,9 @@ const TOOL_FRIENDLY_NAMES: Record<string, string> = {
183
197
  browser_scroll: "browser",
184
198
  browser_wait: "browser",
185
199
  app_create: "app",
186
- app_update: "app",
200
+ app_refresh: "app refresh",
187
201
  skill_load: "skill",
188
202
  skill_execute: "skill",
189
- app_file_edit: "app file",
190
- app_file_write: "app file",
191
203
  };
192
204
 
193
205
  function friendlyToolName(name: string): string {
@@ -267,7 +279,12 @@ export function handleToolUse(
267
279
  state.toolCallTimestamps.set(event.id, { startedAt: Date.now() });
268
280
  state.currentToolUseId = event.id;
269
281
  state.currentTurnToolUseIds.push(event.id);
270
- const statusText = `Running ${friendlyToolName(event.name)}`;
282
+ const statusText =
283
+ event.name === "skill_execute" &&
284
+ typeof event.input.activity === "string" &&
285
+ event.input.activity.length > 0
286
+ ? event.input.activity
287
+ : `Running ${friendlyToolName(event.name)}`;
271
288
  deps.ctx.emitActivityState(
272
289
  "tool_running",
273
290
  "tool_use_start",
@@ -385,6 +402,10 @@ export function handleInputJsonDelta(
385
402
  deps: EventHandlerDeps,
386
403
  event: Extract<AgentEvent, { type: "input_json_delta" }>,
387
404
  ): void {
405
+ // Only forward input deltas for app tools — the client only uses this
406
+ // stream for app_create code previews. Non-app tools would send large
407
+ // cumulative JSON on every delta with no benefit.
408
+ if (!APP_TOOL_NAMES.has(event.toolName)) return;
388
409
  deps.onEvent({
389
410
  type: "tool_input_delta",
390
411
  toolName: event.toolName,
@@ -627,12 +648,21 @@ export async function handleMessageComplete(
627
648
  assistantMessageInterface:
628
649
  deps.turnInterfaceContext.assistantMessageInterface,
629
650
  };
630
- await addMessage(
651
+ const toolResultMsg = await addMessage(
631
652
  deps.ctx.conversationId,
632
653
  "user",
633
654
  JSON.stringify(toolResultBlocks),
634
655
  toolResultMetadata,
635
656
  );
657
+ // Sync tool-result user message to disk view
658
+ const convForToolResult = getConversation(deps.ctx.conversationId);
659
+ if (convForToolResult) {
660
+ syncMessageToDisk(
661
+ deps.ctx.conversationId,
662
+ toolResultMsg.id,
663
+ convForToolResult.createdAt,
664
+ );
665
+ }
636
666
  for (const id of state.pendingToolResults.keys()) {
637
667
  state.persistedToolUseIds.add(id);
638
668
  }
@@ -697,6 +727,18 @@ export async function handleMessageComplete(
697
727
  );
698
728
  state.lastAssistantMessageId = assistantMsg.id;
699
729
 
730
+ // Backfill message_id on all LLM request logs from this turn.
731
+ // The agent loop is single-threaded per conversation, so all rows with
732
+ // message_id IS NULL belong to the current turn.
733
+ try {
734
+ backfillMessageIdOnLogs(deps.ctx.conversationId, assistantMsg.id);
735
+ } catch (err) {
736
+ deps.rlog.warn(
737
+ { err },
738
+ "Failed to backfill message_id on LLM request logs (non-fatal)",
739
+ );
740
+ }
741
+
700
742
  deps.ctx.currentTurnSurfaces = [];
701
743
 
702
744
  // Emit trace event
@@ -724,6 +766,8 @@ export function handleUsage(
724
766
  deps: EventHandlerDeps,
725
767
  event: Extract<AgentEvent, { type: "usage" }>,
726
768
  ): void {
769
+ const providerName = event.actualProvider ?? deps.ctx.provider.name;
770
+ state.exchangeProviderName = providerName;
727
771
  state.exchangeInputTokens += event.inputTokens;
728
772
  state.exchangeCacheCreationInputTokens += event.cacheCreationInputTokens ?? 0;
729
773
  state.exchangeCacheReadInputTokens += event.cacheReadInputTokens ?? 0;
@@ -739,6 +783,8 @@ export function handleUsage(
739
783
  deps.ctx.conversationId,
740
784
  JSON.stringify(event.rawRequest),
741
785
  JSON.stringify(event.rawResponse),
786
+ undefined,
787
+ providerName,
742
788
  );
743
789
  } catch (err) {
744
790
  deps.rlog.warn({ err }, "Failed to persist LLM request log (non-fatal)");
@@ -749,12 +795,12 @@ export function handleUsage(
749
795
 
750
796
  deps.ctx.traceEmitter.emit(
751
797
  "llm_call_finished",
752
- `LLM call to ${deps.ctx.provider.name} finished`,
798
+ `LLM call to ${providerName} finished`,
753
799
  {
754
800
  requestId: deps.reqId,
755
801
  status: "success",
756
802
  attributes: {
757
- provider: deps.ctx.provider.name,
803
+ provider: providerName,
758
804
  model: event.model,
759
805
  inputTokens: event.inputTokens,
760
806
  outputTokens: event.outputTokens,
@@ -32,7 +32,8 @@ import {
32
32
  setSentryConversationContext,
33
33
  } from "../instrument.js";
34
34
  import { commitAppTurnChanges } from "../memory/app-git-service.js";
35
- import { getApp, listAppFiles } from "../memory/app-store.js";
35
+ import { getApp, listAppFiles, resolveAppDir } from "../memory/app-store.js";
36
+ import { insertCompactionEpisode } from "../memory/archive-store.js";
36
37
  import {
37
38
  addMessage,
38
39
  deleteMessageById,
@@ -43,6 +44,10 @@ import {
43
44
  updateConversationContextWindow,
44
45
  updateConversationTitle,
45
46
  } from "../memory/conversation-crud.js";
47
+ import {
48
+ rebuildConversationDiskViewFromDbState,
49
+ syncMessageToDisk,
50
+ } from "../memory/conversation-disk-view.js";
46
51
  import {
47
52
  isReplaceableTitle,
48
53
  queueGenerateConversationTitle,
@@ -77,7 +82,6 @@ import {
77
82
  } from "./conversation-agent-loop-handlers.js";
78
83
  import {
79
84
  approveHostAttachmentRead,
80
- formatAttachmentWarnings,
81
85
  resolveAssistantAttachments,
82
86
  } from "./conversation-attachments.js";
83
87
  import {
@@ -173,11 +177,9 @@ const TOOL_FRIENDLY_LABEL: Record<string, string> = {
173
177
  browser_scroll: "Browser",
174
178
  browser_wait: "Browser",
175
179
  app_create: "Create App",
176
- app_update: "Update App",
180
+ app_refresh: "Refresh App",
177
181
  skill_load: "Load Skill",
178
182
  skill_execute: "Run Skill Tool",
179
- app_file_edit: "Edit App File",
180
- app_file_write: "Write App File",
181
183
  };
182
184
 
183
185
  type GitServiceInitializer = {
@@ -207,7 +209,17 @@ export interface AgentLoopConversationContext {
207
209
  currentPage?: string;
208
210
  readonly surfaceState: Map<
209
211
  string,
210
- { surfaceType: SurfaceType; data: SurfaceData; title?: string }
212
+ {
213
+ surfaceType: SurfaceType;
214
+ data: SurfaceData;
215
+ title?: string;
216
+ actions?: Array<{
217
+ id: string;
218
+ label: string;
219
+ style?: string;
220
+ data?: Record<string, unknown>;
221
+ }>;
222
+ }
211
223
  >;
212
224
  pendingSurfaceActions: Map<string, { surfaceType: SurfaceType }>;
213
225
  surfaceActionRequestIds: Set<string>;
@@ -502,6 +514,12 @@ export async function runAgentLoopImpl(
502
514
  compacted.summaryText,
503
515
  ctx.contextCompactedMessageCount,
504
516
  );
517
+ dualWriteCompactionEpisode(
518
+ ctx.conversationId,
519
+ ctx.memoryPolicy.scopeId,
520
+ compacted.summaryText,
521
+ compacted.summaryOutputTokens,
522
+ );
505
523
  onEvent({
506
524
  type: "context_compacted",
507
525
  previousEstimatedInputTokens: compacted.previousEstimatedInputTokens,
@@ -604,6 +622,7 @@ export async function runAgentLoopImpl(
604
622
  if (app) {
605
623
  activeSurface.appId = app.id;
606
624
  activeSurface.appName = app.name;
625
+ activeSurface.appDirName = resolveAppDir(app.id).dirName;
607
626
  activeSurface.appSchemaJson = app.schemaJson;
608
627
  activeSurface.appFiles = listAppFiles(app.id);
609
628
  if (app.pages && Object.keys(app.pages).length > 0) {
@@ -768,6 +787,12 @@ export async function runAgentLoopImpl(
768
787
  step.compactionResult.summaryText,
769
788
  ctx.contextCompactedMessageCount,
770
789
  );
790
+ dualWriteCompactionEpisode(
791
+ ctx.conversationId,
792
+ ctx.memoryPolicy.scopeId,
793
+ step.compactionResult.summaryText,
794
+ step.compactionResult.summaryOutputTokens,
795
+ );
771
796
  onEvent({
772
797
  type: "context_compacted",
773
798
  previousEstimatedInputTokens:
@@ -801,7 +826,16 @@ export async function runAgentLoopImpl(
801
826
  mode: currentInjectionMode,
802
827
  });
803
828
 
804
- if (step.estimatedTokens <= preflightBudget) break;
829
+ // Re-estimate with injections included — step.estimatedTokens was
830
+ // computed on bare history (ctx.messages) and doesn't account for
831
+ // tokens added by runtime injections.
832
+ const postInjectionTokens = estimatePromptTokens(
833
+ runMessages,
834
+ ctx.systemPrompt,
835
+ { providerName: ctx.provider.name, toolTokenBudget },
836
+ );
837
+
838
+ if (postInjectionTokens <= preflightBudget) break;
805
839
  }
806
840
  }
807
841
 
@@ -943,6 +977,12 @@ export async function runAgentLoopImpl(
943
977
  midLoopCompact.summaryText,
944
978
  ctx.contextCompactedMessageCount,
945
979
  );
980
+ dualWriteCompactionEpisode(
981
+ ctx.conversationId,
982
+ ctx.memoryPolicy.scopeId,
983
+ midLoopCompact.summaryText,
984
+ midLoopCompact.summaryOutputTokens,
985
+ );
946
986
  onEvent({
947
987
  type: "context_compacted",
948
988
  previousEstimatedInputTokens:
@@ -1139,6 +1179,12 @@ export async function runAgentLoopImpl(
1139
1179
  step.compactionResult.summaryText,
1140
1180
  ctx.contextCompactedMessageCount,
1141
1181
  );
1182
+ dualWriteCompactionEpisode(
1183
+ ctx.conversationId,
1184
+ ctx.memoryPolicy.scopeId,
1185
+ step.compactionResult.summaryText,
1186
+ step.compactionResult.summaryOutputTokens,
1187
+ );
1142
1188
  onEvent({
1143
1189
  type: "context_compacted",
1144
1190
  previousEstimatedInputTokens:
@@ -1173,6 +1219,7 @@ export async function runAgentLoopImpl(
1173
1219
  preRepairMessages = runMessages;
1174
1220
  preRunHistoryLength = runMessages.length;
1175
1221
  state.contextTooLargeDetected = false;
1222
+ yieldedForBudget = false;
1176
1223
 
1177
1224
  updatedHistory = await ctx.agentLoop.run(
1178
1225
  runMessages,
@@ -1195,6 +1242,15 @@ export async function runAgentLoopImpl(
1195
1242
  "Post-convergence rerun still yielded at checkpoint — continuing reduction",
1196
1243
  );
1197
1244
  state.contextTooLargeDetected = true;
1245
+
1246
+ // Fold rerun progress into ctx.messages so the next reducer
1247
+ // tier operates on up-to-date history instead of stale
1248
+ // pre-rerun messages.
1249
+ if (updatedHistory.length > preRunHistoryLength) {
1250
+ ctx.messages = stripInjectedContext(updatedHistory);
1251
+ preRepairMessages = updatedHistory;
1252
+ preRunHistoryLength = updatedHistory.length;
1253
+ }
1198
1254
  }
1199
1255
  }
1200
1256
 
@@ -1236,6 +1292,12 @@ export async function runAgentLoopImpl(
1236
1292
  emergencyCompact.summaryText,
1237
1293
  ctx.contextCompactedMessageCount,
1238
1294
  );
1295
+ dualWriteCompactionEpisode(
1296
+ ctx.conversationId,
1297
+ ctx.memoryPolicy.scopeId,
1298
+ emergencyCompact.summaryText,
1299
+ emergencyCompact.summaryOutputTokens,
1300
+ );
1239
1301
  onEvent({
1240
1302
  type: "context_compacted",
1241
1303
  previousEstimatedInputTokens:
@@ -1340,6 +1402,12 @@ export async function runAgentLoopImpl(
1340
1402
  emergencyCompact.summaryText,
1341
1403
  ctx.contextCompactedMessageCount,
1342
1404
  );
1405
+ dualWriteCompactionEpisode(
1406
+ ctx.conversationId,
1407
+ ctx.memoryPolicy.scopeId,
1408
+ emergencyCompact.summaryText,
1409
+ emergencyCompact.summaryOutputTokens,
1410
+ );
1343
1411
  onEvent({
1344
1412
  type: "context_compacted",
1345
1413
  previousEstimatedInputTokens:
@@ -1514,16 +1582,29 @@ export async function runAgentLoopImpl(
1514
1582
  state.exchangeCacheCreationInputTokens,
1515
1583
  state.exchangeCacheReadInputTokens,
1516
1584
  collapseRawResponses(state.exchangeRawResponses),
1585
+ state.exchangeProviderName,
1517
1586
  );
1518
1587
 
1519
1588
  void getHookManager().trigger("post-message", {
1520
1589
  conversationId: ctx.conversationId,
1521
1590
  });
1522
1591
 
1592
+ const syncLastAssistantMessageToDisk = (): void => {
1593
+ if (!state.lastAssistantMessageId) return;
1594
+ const convForDisk = getConversation(ctx.conversationId);
1595
+ if (!convForDisk) return;
1596
+ syncMessageToDisk(
1597
+ ctx.conversationId,
1598
+ state.lastAssistantMessageId,
1599
+ convForDisk.createdAt,
1600
+ );
1601
+ };
1602
+
1523
1603
  // Fast-path: when the user cancelled, skip expensive post-loop work
1524
1604
  // (attachment resolution) and emit the cancellation event immediately
1525
1605
  // so the client can re-enable the UI without delay.
1526
1606
  if (abortController.signal.aborted) {
1607
+ syncLastAssistantMessageToDisk();
1527
1608
  ctx.emitActivityState("idle", "generation_cancelled", "global", reqId);
1528
1609
  ctx.traceEmitter.emit(
1529
1610
  "generation_cancelled",
@@ -1559,17 +1640,7 @@ export async function runAgentLoopImpl(
1559
1640
 
1560
1641
  ctx.lastAssistantAttachments = assistantAttachments;
1561
1642
  ctx.lastAttachmentWarnings = attachmentResult.directiveWarnings;
1562
-
1563
- const warningText = formatAttachmentWarnings(
1564
- attachmentResult.directiveWarnings,
1565
- );
1566
- if (warningText) {
1567
- onEvent({
1568
- type: "assistant_text_delta",
1569
- text: warningText,
1570
- conversationId: ctx.conversationId,
1571
- });
1572
- }
1643
+ syncLastAssistantMessageToDisk();
1573
1644
 
1574
1645
  // Re-check: the user may have cancelled during attachment resolution
1575
1646
  if (abortController.signal.aborted) {
@@ -1604,6 +1675,9 @@ export async function runAgentLoopImpl(
1604
1675
  ...(emittedAttachments.length > 0
1605
1676
  ? { attachments: emittedAttachments }
1606
1677
  : {}),
1678
+ ...(ctx.lastAttachmentWarnings.length > 0
1679
+ ? { attachmentWarnings: ctx.lastAttachmentWarnings }
1680
+ : {}),
1607
1681
  ...(state.lastAssistantMessageId
1608
1682
  ? { messageId: state.lastAssistantMessageId }
1609
1683
  : {}),
@@ -1624,6 +1698,9 @@ export async function runAgentLoopImpl(
1624
1698
  ...(emittedAttachments.length > 0
1625
1699
  ? { attachments: emittedAttachments }
1626
1700
  : {}),
1701
+ ...(ctx.lastAttachmentWarnings.length > 0
1702
+ ? { attachmentWarnings: ctx.lastAttachmentWarnings }
1703
+ : {}),
1627
1704
  ...(state.lastAssistantMessageId
1628
1705
  ? { messageId: state.lastAssistantMessageId }
1629
1706
  : {}),
@@ -1739,7 +1816,13 @@ export async function runAgentLoopImpl(
1739
1816
  ctx.commandIntent = undefined;
1740
1817
 
1741
1818
  if (userMessageId) {
1742
- consolidateAssistantMessages(ctx.conversationId, userMessageId);
1819
+ const didMutateHistory = consolidateAssistantMessages(
1820
+ ctx.conversationId,
1821
+ userMessageId,
1822
+ );
1823
+ if (didMutateHistory) {
1824
+ rebuildConversationDiskViewFromDbState(ctx.conversationId);
1825
+ }
1743
1826
  }
1744
1827
 
1745
1828
  ctx.drainQueue(yieldedForHandoff ? "checkpoint_handoff" : "loop_complete");
@@ -1766,11 +1849,12 @@ function emitUsage(
1766
1849
  cacheCreationInputTokens = 0,
1767
1850
  cacheReadInputTokens = 0,
1768
1851
  rawResponse?: unknown,
1852
+ providerName?: string,
1769
1853
  ): void {
1770
1854
  recordUsage(
1771
1855
  {
1772
1856
  conversationId: ctx.conversationId,
1773
- providerName: ctx.provider.name,
1857
+ providerName: providerName ?? ctx.provider.name,
1774
1858
  usageStats: ctx.usageStats,
1775
1859
  },
1776
1860
  inputTokens,
@@ -1789,3 +1873,26 @@ function collapseRawResponses(rawResponses?: unknown[]): unknown | undefined {
1789
1873
  if (!rawResponses || rawResponses.length === 0) return undefined;
1790
1874
  return rawResponses.length === 1 ? rawResponses[0] : rawResponses;
1791
1875
  }
1876
+
1877
+ /**
1878
+ * Dual-write a compaction summary as an archive episode so it becomes
1879
+ * searchable via vector recall. Called after each successful compaction
1880
+ * that produces a new summary.
1881
+ */
1882
+ function dualWriteCompactionEpisode(
1883
+ conversationId: string,
1884
+ scopeId: string,
1885
+ summaryText: string,
1886
+ summaryOutputTokens: number,
1887
+ ): void {
1888
+ const now = Date.now();
1889
+ insertCompactionEpisode({
1890
+ conversationId,
1891
+ scopeId,
1892
+ title: truncate(summaryText, 120, ""),
1893
+ summary: summaryText,
1894
+ tokenEstimate: summaryOutputTokens,
1895
+ startAt: now,
1896
+ endAt: now,
1897
+ });
1898
+ }
@@ -1,11 +1,8 @@
1
1
  import {
2
+ attachInlineAttachmentToMessage,
2
3
  AttachmentUploadError,
3
- FILE_BACKED_THRESHOLD_BYTES,
4
- linkAttachmentToMessage,
4
+ getFilePathForAttachment,
5
5
  setAttachmentThumbnail,
6
- uploadAttachment,
7
- uploadFileBackedAttachment,
8
- writeAttachmentToDisk,
9
6
  } from "../memory/attachments-store.js";
10
7
  import {
11
8
  check,
@@ -107,12 +104,6 @@ export async function approveHostAttachmentRead(
107
104
  return isAllowDecision(response.decision);
108
105
  }
109
106
 
110
- export function formatAttachmentWarnings(warnings: string[]): string | null {
111
- if (warnings.length === 0) return null;
112
- const lines = warnings.map((warning) => `Attachment warning: ${warning}`);
113
- return `\n\n${lines.join("\n")}`;
114
- }
115
-
116
107
  export interface AttachmentResolutionResult {
117
108
  assistantAttachments: AssistantAttachmentDraft[];
118
109
  emittedAttachments: UserMessageAttachment[];
@@ -211,28 +202,16 @@ export async function resolveAssistantAttachments(
211
202
  if (assistantAttachments.length > 0 && lastAssistantMessageId) {
212
203
  for (let i = 0; i < assistantAttachments.length; i++) {
213
204
  const draft = assistantAttachments[i];
214
- const isFileBacked = draft.sizeBytes > FILE_BACKED_THRESHOLD_BYTES;
215
205
  let stored;
216
- let diskFilePath: string | undefined;
217
206
  try {
218
- if (isFileBacked) {
219
- diskFilePath = writeAttachmentToDisk(
220
- draft.dataBase64,
221
- draft.filename,
222
- );
223
- stored = uploadFileBackedAttachment(
224
- draft.filename,
225
- draft.mimeType,
226
- diskFilePath,
227
- draft.sizeBytes,
228
- );
229
- } else {
230
- stored = uploadAttachment(
231
- draft.filename,
232
- draft.mimeType,
233
- draft.dataBase64,
234
- );
235
- }
207
+ stored = attachInlineAttachmentToMessage(
208
+ lastAssistantMessageId,
209
+ i,
210
+ draft.filename,
211
+ draft.mimeType,
212
+ draft.dataBase64,
213
+ { skipSizeLimit: true },
214
+ );
236
215
  } catch (err) {
237
216
  if (err instanceof AttachmentUploadError) {
238
217
  log.warn(
@@ -246,11 +225,11 @@ export async function resolveAssistantAttachments(
246
225
  }
247
226
  throw err;
248
227
  }
249
- linkAttachmentToMessage(lastAssistantMessageId, stored.id, i);
250
228
  const isVideo = draft.mimeType.startsWith("video/");
251
- const omitData =
252
- isFileBacked ||
253
- (isVideo && draft.dataBase64.length > MAX_INLINE_B64_SIZE);
229
+ // Only omit data for videos — they have an end-to-end lazy-load path
230
+ // via /v1/attachments/:id/content. Other types (images, PDFs) still need
231
+ // inline data for thumbnails, preview, and file-save in the client.
232
+ const omitData = isVideo && draft.dataBase64.length > MAX_INLINE_B64_SIZE;
254
233
 
255
234
  // Generate and persist a thumbnail for video attachments.
256
235
  let thumbnailData: string | undefined;
@@ -259,6 +238,7 @@ export async function resolveAssistantAttachments(
259
238
  if (existing) {
260
239
  thumbnailData = existing;
261
240
  } else {
241
+ const diskFilePath = getFilePathForAttachment(stored.id);
262
242
  const generated = diskFilePath
263
243
  ? await generateVideoThumbnailFromPath(diskFilePath)
264
244
  : await generateVideoThumbnail(draft.dataBase64);
@@ -274,8 +254,9 @@ export async function resolveAssistantAttachments(
274
254
  filename: draft.filename,
275
255
  mimeType: draft.mimeType,
276
256
  data: omitData ? "" : draft.dataBase64,
257
+ sourceType: draft.sourceType,
277
258
  ...(omitData ? { sizeBytes: draft.sizeBytes } : {}),
278
- ...(isFileBacked ? { fileBacked: true } : {}),
259
+ fileBacked: true,
279
260
  ...(thumbnailData ? { thumbnailData } : {}),
280
261
  });
281
262
  }
@@ -285,6 +266,7 @@ export async function resolveAssistantAttachments(
285
266
  filename: draft.filename,
286
267
  mimeType: draft.mimeType,
287
268
  data: draft.dataBase64,
269
+ sourceType: draft.sourceType,
288
270
  });
289
271
  }
290
272
  }
@@ -390,7 +390,8 @@ function classifyByMessage(
390
390
  if (isStreamingError(message)) {
391
391
  return {
392
392
  code: "PROVIDER_API",
393
- userMessage: "The AI provider's response was interrupted. Please try again.",
393
+ userMessage:
394
+ "The AI provider's response was interrupted. Please try again.",
394
395
  retryable: true,
395
396
  errorCategory: "stream_corruption",
396
397
  };
@@ -29,6 +29,16 @@ function isToolResultBlock(
29
29
  );
30
30
  }
31
31
 
32
+ function isSystemNoticeBlock(
33
+ block: ContentBlock | Record<string, unknown>,
34
+ ): boolean {
35
+ if (block.type !== "text") return false;
36
+ const text = (block as { text?: string }).text ?? "";
37
+ return (
38
+ text.startsWith("<system_notice>") && text.endsWith("</system_notice>")
39
+ );
40
+ }
41
+
32
42
  function isUndoableUserMessage(message: Message): boolean {
33
43
  if (message.role !== "user") return false;
34
44
  if (getSummaryFromContextMessage(message) != null) return false;
@@ -37,8 +47,9 @@ function isUndoableUserMessage(message: Message): boolean {
37
47
  // responses) are not undoable. Messages that have both tool_result and text blocks
38
48
  // (e.g. after repairHistory merges a tool_result turn with a user prompt) are still
39
49
  // undoable because they contain real user content.
50
+ // System notice text blocks (retry nudges, progress checks) are not user content.
40
51
  const hasNonToolResultContent = message.content.some(
41
- (block) => !isToolResultBlock(block),
52
+ (block) => !isToolResultBlock(block) && !isSystemNoticeBlock(block),
42
53
  );
43
54
  if (!hasNonToolResultContent) return false;
44
55
  return true;
@@ -131,10 +142,10 @@ export async function cleanupQdrantVectors(
131
142
  export function consolidateAssistantMessages(
132
143
  conversationId: string,
133
144
  userMessageId: string,
134
- ): void {
145
+ ): boolean {
135
146
  const allMessages = getMessages(conversationId);
136
147
  const userMsgIndex = allMessages.findIndex((m) => m.id === userMessageId);
137
- if (userMsgIndex === -1) return;
148
+ if (userMsgIndex === -1) return false;
138
149
 
139
150
  const messagesToConsolidate: typeof allMessages = [];
140
151
  const internalToolResultMessages: typeof allMessages = [];
@@ -171,12 +182,14 @@ export function consolidateAssistantMessages(
171
182
 
172
183
  // Only consolidate if there are multiple assistant messages
173
184
  if (messagesToConsolidate.length <= 1) {
185
+ let didMutate = false;
174
186
  // Still delete internal tool_result messages even if only one assistant message,
175
187
  // and collect IDs for vector cleanup
176
188
  const allSegmentIds: string[] = [];
177
189
  const allOrphanedItemIds: string[] = [];
178
190
  for (const id of messagesToDelete) {
179
191
  const deleted = deleteMessageById(id);
192
+ didMutate = true;
180
193
  allSegmentIds.push(...deleted.segmentIds);
181
194
  allOrphanedItemIds.push(...deleted.orphanedItemIds);
182
195
  }
@@ -194,7 +207,7 @@ export function consolidateAssistantMessages(
194
207
  );
195
208
  });
196
209
  }
197
- return;
210
+ return didMutate;
198
211
  }
199
212
 
200
213
  log.info(
@@ -339,6 +352,7 @@ export function consolidateAssistantMessages(
339
352
  },
340
353
  "Assistant messages consolidated",
341
354
  );
355
+ return true;
342
356
  }
343
357
 
344
358
  // ── Undo ─────────────────────────────────────────────────────────────