@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
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Shared formatting helpers for rendering capped markdown sections
3
+ * inside the `<memory_brief>` wrapper.
4
+ */
5
+
6
+ export interface BriefEntry {
7
+ /** One-line markdown bullet text (no leading `- `). */
8
+ text: string;
9
+ }
10
+
11
+ /**
12
+ * Render a titled markdown section with a capped number of bullet entries.
13
+ *
14
+ * Returns `null` when `entries` is empty so callers can easily omit absent
15
+ * sections. The output is a markdown string like:
16
+ *
17
+ * ```
18
+ * ### Time-Relevant Context
19
+ * - Meeting with Alice in 2 hours
20
+ * - Quarterly review deadline tomorrow
21
+ * ```
22
+ */
23
+ export function renderBriefSection(
24
+ title: string,
25
+ entries: BriefEntry[],
26
+ maxEntries: number,
27
+ ): string | null {
28
+ if (entries.length === 0) return null;
29
+
30
+ const capped = entries.slice(0, maxEntries);
31
+ const bullets = capped.map((e) => `- ${e.text}`).join("\n");
32
+ return `### ${title}\n${bullets}`;
33
+ }
@@ -0,0 +1,266 @@
1
+ /**
2
+ * Open-loop brief compiler.
3
+ *
4
+ * Merges reducer-created open_loops rows with live task queue (work items)
5
+ * and follow-up state into a ranked, deduplicated list of bullets for the
6
+ * memory brief.
7
+ *
8
+ * Ranking tiers (highest first):
9
+ * 1. Overdue — dueAt in the past
10
+ * 2. Due ≤ 24 h — dueAt within the next 24 hours
11
+ * 3. Due ≤ 7 d — dueAt within the next 7 days
12
+ * 4. High-priority / blocked — work items at priority tier 0 or
13
+ * follow-ups with status "nudged"
14
+ * 5. Recently touched — updatedAt within the last 48 hours
15
+ *
16
+ * After ranked items, at most ONE low-salience loop is resurfaced via
17
+ * deterministic pseudo-random sampling seeded by `scopeId + userMessageId`.
18
+ * The resurfaced loop's `surfacedAt` is updated so it won't repeat
19
+ * immediately.
20
+ */
21
+
22
+ import { and, eq } from "drizzle-orm";
23
+
24
+ import {
25
+ type BriefFollowUp,
26
+ getPendingAndOverdueFollowUps,
27
+ } from "../followups/followup-store.js";
28
+ import {
29
+ type ActionableWorkItem,
30
+ getActionableWorkItems,
31
+ } from "../tasks/task-store.js";
32
+ import { getDb } from "./db.js";
33
+ import { updateLastSurfacedAt } from "./reducer-store.js";
34
+ import { openLoops } from "./schema.js";
35
+
36
+ // ── Constants ─────────────────────────────────────────────────────────
37
+
38
+ const MS_24H = 24 * 60 * 60 * 1000;
39
+ const MS_7D = 7 * 24 * 60 * 60 * 1000;
40
+ const MS_48H = 48 * 60 * 60 * 1000;
41
+
42
+ // ── Types ─────────────────────────────────────────────────────────────
43
+
44
+ export interface OpenLoopBullet {
45
+ /** Dedupe key — one of `loop:<id>`, `work:<id>`, or `followup:<id>`. */
46
+ key: string;
47
+ summary: string;
48
+ tier: number; // 1–5, lower = higher priority
49
+ source: "loop" | "work_item" | "followup";
50
+ sourceId: string;
51
+ }
52
+
53
+ export interface OpenLoopBriefResult {
54
+ /** Bullets ordered by tier then recency. */
55
+ bullets: OpenLoopBullet[];
56
+ /** ID of the resurfaced low-salience loop, if any. */
57
+ resurfacedLoopId: string | null;
58
+ }
59
+
60
+ interface OpenLoopRow {
61
+ id: string;
62
+ scopeId: string;
63
+ summary: string;
64
+ status: string;
65
+ source: string;
66
+ dueAt: number | null;
67
+ surfacedAt: number | null;
68
+ createdAt: number;
69
+ updatedAt: number;
70
+ }
71
+
72
+ // ── Deterministic hash ────────────────────────────────────────────────
73
+
74
+ /**
75
+ * Simple 32-bit FNV-1a hash for deterministic pseudo-random selection.
76
+ * Not cryptographic — only needs to be well-distributed for sampling.
77
+ */
78
+ function fnv1a(input: string): number {
79
+ let h = 0x811c9dc5;
80
+ for (let i = 0; i < input.length; i++) {
81
+ h ^= input.charCodeAt(i);
82
+ h = Math.imul(h, 0x01000193);
83
+ }
84
+ return h >>> 0; // unsigned 32-bit
85
+ }
86
+
87
+ // ── Tier assignment ───────────────────────────────────────────────────
88
+
89
+ function assignLoopTier(loop: OpenLoopRow, now: number): number {
90
+ if (loop.dueAt != null) {
91
+ if (loop.dueAt <= now) return 1; // overdue
92
+ if (loop.dueAt <= now + MS_24H) return 2; // due within 24h
93
+ if (loop.dueAt <= now + MS_7D) return 3; // due within 7d
94
+ }
95
+ if (now - loop.updatedAt <= MS_48H) return 5; // recently touched
96
+ return 6; // low salience — candidate for resurfacing
97
+ }
98
+
99
+ function assignWorkItemTier(item: ActionableWorkItem, now: number): number {
100
+ if (item.priorityTier === 0) return 4; // high priority
101
+ if (item.status === "awaiting_review") return 4;
102
+ if (now - item.updatedAt <= MS_48H) return 5;
103
+ return 6;
104
+ }
105
+
106
+ function assignFollowUpTier(fu: BriefFollowUp, now: number): number {
107
+ if (fu.expectedResponseBy != null && fu.expectedResponseBy <= now) return 1;
108
+ if (fu.expectedResponseBy != null && fu.expectedResponseBy <= now + MS_24H)
109
+ return 2;
110
+ if (fu.expectedResponseBy != null && fu.expectedResponseBy <= now + MS_7D)
111
+ return 3;
112
+ if (fu.status === "nudged") return 4;
113
+ if (now - fu.updatedAt <= MS_48H) return 5;
114
+ return 6;
115
+ }
116
+
117
+ // ── Compiler ──────────────────────────────────────────────────────────
118
+
119
+ /**
120
+ * Compile the open-loop section of the memory brief.
121
+ *
122
+ * @param scopeId Memory scope (e.g. assistant instance ID)
123
+ * @param userMessageId Current user message ID — used as part of the
124
+ * resurfacing seed so the selection is deterministic
125
+ * per turn but varies across turns.
126
+ * @param now Current epoch-ms timestamp (injectable for testing).
127
+ */
128
+ export function compileOpenLoopBrief(
129
+ scopeId: string,
130
+ userMessageId: string,
131
+ now: number = Date.now(),
132
+ ): OpenLoopBriefResult {
133
+ // 1. Gather data from all three sources
134
+ const loops = getOpenLoopsForScope(scopeId);
135
+ const workItems = getActionableWorkItems();
136
+ const followUps = getPendingAndOverdueFollowUps();
137
+
138
+ // 2. Convert to bullets with tier assignment
139
+ const bullets: OpenLoopBullet[] = [];
140
+ const seenKeys = new Set<string>();
141
+
142
+ // Loops first (they are the authoritative open-loop source)
143
+ for (const loop of loops) {
144
+ const key = `loop:${loop.id}`;
145
+ if (seenKeys.has(key)) continue;
146
+ seenKeys.add(key);
147
+ bullets.push({
148
+ key,
149
+ summary: loop.summary,
150
+ tier: assignLoopTier(loop, now),
151
+ source: "loop",
152
+ sourceId: loop.id,
153
+ });
154
+ }
155
+
156
+ // Work items — skip if already represented by a loop with matching summary
157
+ const loopSummaries = new Set(loops.map((l) => l.summary.toLowerCase()));
158
+ for (const item of workItems) {
159
+ const key = `work:${item.id}`;
160
+ if (seenKeys.has(key)) continue;
161
+ // Deduplicate against loop summaries
162
+ if (loopSummaries.has(item.title.toLowerCase())) continue;
163
+ seenKeys.add(key);
164
+ bullets.push({
165
+ key,
166
+ summary: item.title,
167
+ tier: assignWorkItemTier(item, now),
168
+ source: "work_item",
169
+ sourceId: item.id,
170
+ });
171
+ }
172
+
173
+ // Follow-ups — skip if already represented by a loop
174
+ for (const fu of followUps) {
175
+ const key = `followup:${fu.id}`;
176
+ if (seenKeys.has(key)) continue;
177
+ const fuSummary =
178
+ `Awaiting reply on ${fu.channel} (${fu.conversationId})`.toLowerCase();
179
+ if (loopSummaries.has(fuSummary)) continue;
180
+ seenKeys.add(key);
181
+ bullets.push({
182
+ key,
183
+ summary: `Awaiting reply on ${fu.channel} (${fu.conversationId})`,
184
+ tier: assignFollowUpTier(fu, now),
185
+ source: "followup",
186
+ sourceId: fu.id,
187
+ });
188
+ }
189
+
190
+ // 3. Split into ranked (tiers 1–5) and low-salience (tier 6)
191
+ const ranked: OpenLoopBullet[] = [];
192
+ const lowSalience: OpenLoopBullet[] = [];
193
+
194
+ for (const b of bullets) {
195
+ if (b.tier <= 5) {
196
+ ranked.push(b);
197
+ } else {
198
+ lowSalience.push(b);
199
+ }
200
+ }
201
+
202
+ // Sort ranked: by tier ascending, then by source priority (loop > work > followup)
203
+ ranked.sort((a, b) => {
204
+ if (a.tier !== b.tier) return a.tier - b.tier;
205
+ return sourcePriority(a.source) - sourcePriority(b.source);
206
+ });
207
+
208
+ // 4. Deterministic resurfacing of ONE low-salience loop
209
+ let resurfacedLoopId: string | null = null;
210
+
211
+ if (lowSalience.length > 0) {
212
+ // Only consider loops from the open_loops table for resurfacing
213
+ // (work items and follow-ups have their own lifecycle)
214
+ const resurfaceCandidates = lowSalience.filter((b) => b.source === "loop");
215
+
216
+ if (resurfaceCandidates.length > 0) {
217
+ // Sort candidates deterministically by key for stable ordering
218
+ resurfaceCandidates.sort((a, b) => a.key.localeCompare(b.key));
219
+
220
+ const seed = `${scopeId}:${userMessageId}`;
221
+ const hash = fnv1a(seed);
222
+ const idx = hash % resurfaceCandidates.length;
223
+ const picked = resurfaceCandidates[idx];
224
+
225
+ // Promote picked to tier 5 and add to ranked output
226
+ ranked.push({ ...picked, tier: 5 });
227
+ resurfacedLoopId = picked.sourceId;
228
+
229
+ // Update surfacedAt so it is deprioritised on subsequent turns
230
+ updateLastSurfacedAt(picked.sourceId, now);
231
+ }
232
+ }
233
+
234
+ // Re-sort after potential resurfaced bullet insertion
235
+ ranked.sort((a, b) => {
236
+ if (a.tier !== b.tier) return a.tier - b.tier;
237
+ return sourcePriority(a.source) - sourcePriority(b.source);
238
+ });
239
+
240
+ return {
241
+ bullets: ranked,
242
+ resurfacedLoopId,
243
+ };
244
+ }
245
+
246
+ // ── Internals ─────────────────────────────────────────────────────────
247
+
248
+ function sourcePriority(source: "loop" | "work_item" | "followup"): number {
249
+ switch (source) {
250
+ case "loop":
251
+ return 0;
252
+ case "work_item":
253
+ return 1;
254
+ case "followup":
255
+ return 2;
256
+ }
257
+ }
258
+
259
+ function getOpenLoopsForScope(scopeId: string): OpenLoopRow[] {
260
+ const db = getDb();
261
+ return db
262
+ .select()
263
+ .from(openLoops)
264
+ .where(and(eq(openLoops.scopeId, scopeId), eq(openLoops.status, "open")))
265
+ .all() as OpenLoopRow[];
266
+ }
@@ -0,0 +1,161 @@
1
+ /**
2
+ * Deterministic compiler for the "Time-Relevant Context" section of the
3
+ * memory brief. Reads active `time_contexts` rows plus due-soon live
4
+ * schedule jobs, sorts them by urgency bucket, and caps the output.
5
+ */
6
+
7
+ import { and, gte, lte } from "drizzle-orm";
8
+
9
+ import { getDueSoonSchedules } from "../schedule/schedule-store.js";
10
+ import type { BriefEntry } from "./brief-formatting.js";
11
+ import { renderBriefSection } from "./brief-formatting.js";
12
+ import type { DrizzleDb } from "./db-connection.js";
13
+ import { timeContexts } from "./schema/memory-brief.js";
14
+
15
+ const MAX_ENTRIES = 3;
16
+ const SEVEN_DAYS_MS = 7 * 24 * 60 * 60 * 1000;
17
+ const ONE_DAY_MS = 24 * 60 * 60 * 1000;
18
+
19
+ /** Urgency buckets — lower number = higher priority. */
20
+ const enum Bucket {
21
+ HappeningNow = 0,
22
+ Overdue = 1,
23
+ Within24h = 2,
24
+ Within7d = 3,
25
+ }
26
+
27
+ interface Candidate {
28
+ bucket: Bucket;
29
+ /** Epoch ms timestamp used for secondary sort within a bucket. */
30
+ sortKey: number;
31
+ entry: BriefEntry;
32
+ }
33
+
34
+ // ────────────────────────────────────────────────────────────────────
35
+ // Public API
36
+ // ────────────────────────────────────────────────────────────────────
37
+
38
+ /**
39
+ * Compile the time-relevant brief section.
40
+ *
41
+ * @param db Drizzle database instance
42
+ * @param now Current epoch-ms timestamp (injectable for deterministic tests)
43
+ * @returns Markdown string for the section, or `null` if nothing qualifies
44
+ */
45
+ export function compileTimeBrief(
46
+ db: DrizzleDb,
47
+ scopeId: string,
48
+ now: number,
49
+ ): string | null {
50
+ const candidates: Candidate[] = [];
51
+
52
+ collectTimeContexts(db, scopeId, now, candidates);
53
+ collectDueSoonSchedules(now, candidates);
54
+
55
+ // Sort: primary = bucket ascending, secondary = sortKey ascending (sooner first)
56
+ candidates.sort((a, b) => a.bucket - b.bucket || a.sortKey - b.sortKey);
57
+
58
+ const entries = candidates.slice(0, MAX_ENTRIES).map((c) => c.entry);
59
+ return renderBriefSection("Time-Relevant Context", entries, MAX_ENTRIES);
60
+ }
61
+
62
+ // ────────────────────────────────────────────────────────────────────
63
+ // Internal collectors
64
+ // ────────────────────────────────────────────────────────────────────
65
+
66
+ function collectTimeContexts(
67
+ db: DrizzleDb,
68
+ scopeId: string,
69
+ now: number,
70
+ out: Candidate[],
71
+ ): void {
72
+ // Active time contexts: activeFrom <= now AND activeUntil >= now
73
+ const rows = db
74
+ .select()
75
+ .from(timeContexts)
76
+ .where(
77
+ and(
78
+ lte(timeContexts.activeFrom, now),
79
+ gte(timeContexts.activeUntil, now),
80
+ ),
81
+ )
82
+ .all()
83
+ .filter((r) => r.scopeId === scopeId);
84
+
85
+ for (const row of rows) {
86
+ const remaining = row.activeUntil - now;
87
+ let bucket: Bucket;
88
+
89
+ if (row.activeFrom <= now && row.activeUntil >= now) {
90
+ // Currently active — classify by how much time remains
91
+ if (remaining <= ONE_DAY_MS) {
92
+ bucket = Bucket.HappeningNow;
93
+ } else if (remaining <= SEVEN_DAYS_MS) {
94
+ bucket = Bucket.Within24h;
95
+ } else {
96
+ bucket = Bucket.Within7d;
97
+ }
98
+ } else {
99
+ bucket = Bucket.Within7d;
100
+ }
101
+
102
+ out.push({
103
+ bucket,
104
+ sortKey: row.activeUntil,
105
+ entry: { text: row.summary },
106
+ });
107
+ }
108
+ }
109
+
110
+ function collectDueSoonSchedules(now: number, out: Candidate[]): void {
111
+ const jobs = getDueSoonSchedules(now, SEVEN_DAYS_MS);
112
+
113
+ for (const job of jobs) {
114
+ const delta = job.nextRunAt - now;
115
+ let bucket: Bucket;
116
+
117
+ if (delta <= 0) {
118
+ bucket = Bucket.Overdue;
119
+ } else if (delta <= ONE_DAY_MS) {
120
+ bucket = Bucket.Within24h;
121
+ } else {
122
+ bucket = Bucket.Within7d;
123
+ }
124
+
125
+ const label = formatScheduleLabel(job.name, job.nextRunAt, now);
126
+ out.push({
127
+ bucket,
128
+ sortKey: job.nextRunAt,
129
+ entry: { text: label },
130
+ });
131
+ }
132
+ }
133
+
134
+ // ────────────────────────────────────────────────────────────────────
135
+ // Formatting
136
+ // ────────────────────────────────────────────────────────────────────
137
+
138
+ function formatScheduleLabel(
139
+ name: string,
140
+ nextRunAt: number,
141
+ now: number,
142
+ ): string {
143
+ const delta = nextRunAt - now;
144
+
145
+ if (delta <= 0) {
146
+ return `Scheduled: "${name}" — overdue`;
147
+ }
148
+
149
+ const minutes = Math.round(delta / 60_000);
150
+ if (minutes < 60) {
151
+ return `Scheduled: "${name}" — in ${minutes} minute${minutes === 1 ? "" : "s"}`;
152
+ }
153
+
154
+ const hours = Math.round(delta / 3_600_000);
155
+ if (hours < 24) {
156
+ return `Scheduled: "${name}" — in ${hours} hour${hours === 1 ? "" : "s"}`;
157
+ }
158
+
159
+ const days = Math.round(delta / 86_400_000);
160
+ return `Scheduled: "${name}" — in ${days} day${days === 1 ? "" : "s"}`;
161
+ }
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Top-level memory brief composer.
3
+ *
4
+ * Composes the "Time-Relevant Context" and "Open Loops" sections into a
5
+ * single `<memory_brief>` XML-wrapped block. Omits empty sections and
6
+ * returns an empty string when neither section has content.
7
+ */
8
+
9
+ import { renderBriefSection } from "./brief-formatting.js";
10
+ import type { OpenLoopBriefResult } from "./brief-open-loops.js";
11
+ import { compileOpenLoopBrief } from "./brief-open-loops.js";
12
+ import { compileTimeBrief } from "./brief-time.js";
13
+ import type { DrizzleDb } from "./db-connection.js";
14
+
15
+ /** Maximum number of open-loop bullets to include in the brief. */
16
+ const MAX_OPEN_LOOP_ENTRIES = 5;
17
+
18
+ export interface MemoryBriefResult {
19
+ /** Rendered `<memory_brief>` block, or empty string if nothing to show. */
20
+ text: string;
21
+ /** Forwarded from `compileOpenLoopBrief` for downstream tracking. */
22
+ resurfacedLoopId: string | null;
23
+ }
24
+
25
+ /**
26
+ * Compile the full memory brief block.
27
+ *
28
+ * @param db Drizzle database instance
29
+ * @param scopeId Memory scope (e.g. assistant instance ID)
30
+ * @param userMessageId Current user message ID — used for deterministic
31
+ * open-loop resurfacing
32
+ * @param now Current epoch-ms timestamp (injectable for tests)
33
+ * @returns `{ text, resurfacedLoopId }` — `text` is the
34
+ * rendered `<memory_brief>` block or empty string
35
+ */
36
+ export function compileMemoryBrief(
37
+ db: DrizzleDb,
38
+ scopeId: string,
39
+ userMessageId: string,
40
+ now: number = Date.now(),
41
+ ): MemoryBriefResult {
42
+ // Compile individual sections
43
+ const timeSection = compileTimeBrief(db, scopeId, now);
44
+
45
+ const openLoopResult: OpenLoopBriefResult = compileOpenLoopBrief(
46
+ scopeId,
47
+ userMessageId,
48
+ now,
49
+ );
50
+
51
+ // Convert open-loop bullets to a rendered section via the shared helper
52
+ const openLoopEntries = openLoopResult.bullets.map((b) => ({
53
+ text: b.summary,
54
+ }));
55
+ const openLoopSection = renderBriefSection(
56
+ "Open Loops",
57
+ openLoopEntries,
58
+ MAX_OPEN_LOOP_ENTRIES,
59
+ );
60
+
61
+ // Collect non-empty sections
62
+ const sections: string[] = [];
63
+ if (timeSection) sections.push(timeSection);
64
+ if (openLoopSection) sections.push(openLoopSection);
65
+
66
+ // If no sections have content, return empty
67
+ if (sections.length === 0) {
68
+ return { text: "", resurfacedLoopId: openLoopResult.resurfacedLoopId };
69
+ }
70
+
71
+ const body = sections.join("\n\n");
72
+ const text = `<memory_brief>\n${body}\n</memory_brief>`;
73
+
74
+ return { text, resurfacedLoopId: openLoopResult.resurfacedLoopId };
75
+ }
@@ -163,6 +163,76 @@ export function projectAssistantMessage(params: {
163
163
  .run();
164
164
  }
165
165
 
166
+ /**
167
+ * Seed a forked conversation's assistant-attention projection so copied
168
+ * assistant history is treated as already seen from the outset.
169
+ */
170
+ export function seedForkedConversationAttention(params: {
171
+ conversationId: string;
172
+ latestAssistantMessageId: string | null;
173
+ latestAssistantMessageAt: number | null;
174
+ }): void {
175
+ const {
176
+ conversationId,
177
+ latestAssistantMessageId,
178
+ latestAssistantMessageAt,
179
+ } = params;
180
+
181
+ if (!latestAssistantMessageId || latestAssistantMessageAt == null) {
182
+ return;
183
+ }
184
+
185
+ const db = getDb();
186
+ const now = Date.now();
187
+ const existing = db
188
+ .select()
189
+ .from(conversationAssistantAttentionState)
190
+ .where(
191
+ eq(conversationAssistantAttentionState.conversationId, conversationId),
192
+ )
193
+ .get();
194
+
195
+ if (!existing) {
196
+ db.insert(conversationAssistantAttentionState)
197
+ .values({
198
+ conversationId,
199
+ latestAssistantMessageId,
200
+ latestAssistantMessageAt,
201
+ lastSeenAssistantMessageId: latestAssistantMessageId,
202
+ lastSeenAssistantMessageAt: latestAssistantMessageAt,
203
+ lastSeenEventAt: null,
204
+ lastSeenConfidence: null,
205
+ lastSeenSignalType: null,
206
+ lastSeenSourceChannel: null,
207
+ lastSeenSource: null,
208
+ lastSeenEvidenceText: null,
209
+ createdAt: now,
210
+ updatedAt: now,
211
+ })
212
+ .run();
213
+ return;
214
+ }
215
+
216
+ db.update(conversationAssistantAttentionState)
217
+ .set({
218
+ latestAssistantMessageId,
219
+ latestAssistantMessageAt,
220
+ lastSeenAssistantMessageId: latestAssistantMessageId,
221
+ lastSeenAssistantMessageAt: latestAssistantMessageAt,
222
+ lastSeenEventAt: null,
223
+ lastSeenConfidence: null,
224
+ lastSeenSignalType: null,
225
+ lastSeenSourceChannel: null,
226
+ lastSeenSource: null,
227
+ lastSeenEvidenceText: null,
228
+ updatedAt: now,
229
+ })
230
+ .where(
231
+ eq(conversationAssistantAttentionState.conversationId, conversationId),
232
+ )
233
+ .run();
234
+ }
235
+
166
236
  // ── recordConversationSeenSignal ─────────────────────────────────────
167
237
 
168
238
  /**