@vellumai/assistant 0.5.5 → 0.5.7

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 (382) hide show
  1. package/.env.example +16 -2
  2. package/ARCHITECTURE.md +6 -75
  3. package/Dockerfile +4 -5
  4. package/README.md +0 -2
  5. package/bun.lock +0 -414
  6. package/docs/architecture/keychain-broker.md +45 -240
  7. package/docs/architecture/security.md +0 -17
  8. package/docs/credential-execution-service.md +2 -2
  9. package/node_modules/@vellumai/ces-contracts/package.json +1 -0
  10. package/node_modules/@vellumai/ces-contracts/src/rpc.ts +119 -0
  11. package/node_modules/@vellumai/credential-storage/package.json +1 -0
  12. package/node_modules/@vellumai/egress-proxy/package.json +1 -0
  13. package/package.json +2 -3
  14. package/src/__tests__/actor-token-service.test.ts +1 -2
  15. package/src/__tests__/assistant-feature-flags-integration.test.ts +30 -29
  16. package/src/__tests__/browser-skill-endstate.test.ts +6 -5
  17. package/src/__tests__/btw-routes.test.ts +0 -39
  18. package/src/__tests__/call-domain.test.ts +0 -128
  19. package/src/__tests__/ces-rpc-credential-backend.test.ts +199 -0
  20. package/src/__tests__/channel-approval-routes.test.ts +0 -5
  21. package/src/__tests__/channel-readiness-service.test.ts +1 -60
  22. package/src/__tests__/checker.test.ts +4 -2
  23. package/src/__tests__/cli-command-risk-guard.test.ts +112 -0
  24. package/src/__tests__/config-schema-cmd.test.ts +0 -1
  25. package/src/__tests__/config-schema.test.ts +3 -3
  26. package/src/__tests__/context-window-manager.test.ts +78 -0
  27. package/src/__tests__/conversation-attention-telegram.test.ts +0 -5
  28. package/src/__tests__/conversation-init.benchmark.test.ts +0 -2
  29. package/src/__tests__/conversation-skill-tools.test.ts +0 -54
  30. package/src/__tests__/conversation-title-service.test.ts +117 -1
  31. package/src/__tests__/credential-execution-feature-gates.test.ts +28 -14
  32. package/src/__tests__/credential-execution-managed-contract.test.ts +33 -18
  33. package/src/__tests__/credential-security-e2e.test.ts +0 -66
  34. package/src/__tests__/credential-security-invariants.test.ts +4 -45
  35. package/src/__tests__/credentials-cli.test.ts +78 -0
  36. package/src/__tests__/db-migration-rollback.test.ts +2015 -1
  37. package/src/__tests__/docker-signing-key-bootstrap.test.ts +98 -0
  38. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +6 -4
  39. package/src/__tests__/guardian-routing-state.test.ts +0 -5
  40. package/src/__tests__/host-shell-tool.test.ts +6 -7
  41. package/src/__tests__/http-user-message-parity.test.ts +3 -103
  42. package/src/__tests__/inbound-invite-redemption.test.ts +0 -4
  43. package/src/__tests__/inline-skill-load-permissions.test.ts +6 -8
  44. package/src/__tests__/intent-routing.test.ts +0 -13
  45. package/src/__tests__/jobs-store-qdrant-breaker.test.ts +178 -0
  46. package/src/__tests__/keychain-broker-client.test.ts +161 -22
  47. package/src/__tests__/memory-jobs-worker-backoff.test.ts +150 -0
  48. package/src/__tests__/memory-regressions.test.ts +8 -30
  49. package/src/__tests__/migration-export-http.test.ts +2 -2
  50. package/src/__tests__/migration-import-commit-http.test.ts +2 -2
  51. package/src/__tests__/migration-import-preflight-http.test.ts +2 -2
  52. package/src/__tests__/migration-validate-http.test.ts +2 -2
  53. package/src/__tests__/non-member-access-request.test.ts +0 -5
  54. package/src/__tests__/notification-decision-fallback.test.ts +4 -0
  55. package/src/__tests__/notification-decision-identity.test.ts +4 -0
  56. package/src/__tests__/permission-types.test.ts +1 -0
  57. package/src/__tests__/provider-managed-proxy-integration.test.ts +5 -6
  58. package/src/__tests__/qdrant-manager.test.ts +28 -2
  59. package/src/__tests__/registry.test.ts +0 -6
  60. package/src/__tests__/require-fresh-approval.test.ts +4 -0
  61. package/src/__tests__/runtime-attachment-metadata.test.ts +0 -4
  62. package/src/__tests__/secret-routes-managed-proxy.test.ts +0 -4
  63. package/src/__tests__/secure-keys.test.ts +83 -263
  64. package/src/__tests__/shell-identity.test.ts +96 -6
  65. package/src/__tests__/skill-feature-flags-integration.test.ts +22 -14
  66. package/src/__tests__/skill-feature-flags.test.ts +46 -45
  67. package/src/__tests__/skill-load-feature-flag.test.ts +7 -10
  68. package/src/__tests__/skill-load-inline-command.test.ts +8 -12
  69. package/src/__tests__/skill-load-inline-includes.test.ts +6 -10
  70. package/src/__tests__/skill-load-tool.test.ts +0 -2
  71. package/src/__tests__/skill-projection-feature-flag.test.ts +33 -29
  72. package/src/__tests__/skills.test.ts +0 -2
  73. package/src/__tests__/slack-inbound-verification.test.ts +0 -4
  74. package/src/__tests__/suggestion-routes.test.ts +1 -32
  75. package/src/__tests__/system-prompt.test.ts +0 -1
  76. package/src/__tests__/tool-executor-lifecycle-events.test.ts +4 -0
  77. package/src/__tests__/tool-executor-shell-integration.test.ts +5 -3
  78. package/src/__tests__/tool-executor.test.ts +4 -0
  79. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +0 -5
  80. package/src/__tests__/trusted-contact-multichannel.test.ts +0 -4
  81. package/src/__tests__/update-bulletin.test.ts +0 -2
  82. package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +6 -9
  83. package/src/__tests__/voice-scoped-grant-consumer.test.ts +0 -6
  84. package/src/__tests__/workspace-migration-015-migrate-credentials-to-keychain.test.ts +252 -0
  85. package/src/__tests__/workspace-migration-016-migrate-credentials-from-keychain.test.ts +218 -0
  86. package/src/__tests__/workspace-migration-down-functions.test.ts +1009 -0
  87. package/src/__tests__/workspace-migrations-runner.test.ts +114 -0
  88. package/src/calls/audio-store.test.ts +97 -0
  89. package/src/calls/audio-store.ts +205 -0
  90. package/src/calls/call-controller.ts +85 -7
  91. package/src/calls/call-domain.ts +3 -0
  92. package/src/calls/call-store.ts +10 -3
  93. package/src/calls/fish-audio-client.ts +117 -0
  94. package/src/calls/relay-server.ts +27 -0
  95. package/src/calls/twilio-routes.ts +2 -1
  96. package/src/calls/types.ts +1 -0
  97. package/src/calls/voice-ingress-preflight.ts +0 -42
  98. package/src/calls/voice-quality.ts +26 -5
  99. package/src/calls/voice-session-bridge.ts +6 -12
  100. package/src/cli/commands/config.ts +1 -4
  101. package/src/cli/commands/conversations.ts +0 -18
  102. package/src/cli/commands/credentials.ts +34 -4
  103. package/src/cli/commands/oauth/index.ts +7 -0
  104. package/src/cli/commands/oauth/platform.ts +179 -0
  105. package/src/cli/commands/platform.ts +3 -3
  106. package/src/config/assistant-feature-flags.ts +186 -5
  107. package/src/config/bundled-skills/messaging/SKILL.md +5 -5
  108. package/src/config/bundled-skills/phone-calls/TOOLS.json +4 -0
  109. package/src/config/bundled-skills/settings/TOOLS.json +2 -2
  110. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +42 -0
  111. package/src/config/bundled-tool-registry.ts +1 -11
  112. package/src/config/env-registry.ts +1 -1
  113. package/src/config/env.ts +16 -16
  114. package/src/config/feature-flag-registry.json +48 -16
  115. package/src/config/loader.ts +98 -31
  116. package/src/config/schema.ts +4 -25
  117. package/src/config/schemas/calls.ts +13 -0
  118. package/src/config/schemas/fish-audio.ts +39 -0
  119. package/src/config/schemas/memory.ts +0 -4
  120. package/src/config/schemas/platform.ts +1 -1
  121. package/src/config/schemas/security.ts +4 -4
  122. package/src/config/types.ts +0 -1
  123. package/src/contacts/contact-store.ts +39 -0
  124. package/src/contacts/types.ts +2 -0
  125. package/src/context/window-manager.ts +53 -2
  126. package/src/credential-execution/approval-bridge.ts +1 -0
  127. package/src/credential-execution/executable-discovery.ts +28 -4
  128. package/src/credential-execution/feature-gates.ts +16 -0
  129. package/src/credential-execution/process-manager.ts +38 -0
  130. package/src/daemon/assistant-attachments.ts +9 -0
  131. package/src/daemon/config-watcher.ts +6 -4
  132. package/src/daemon/conversation-agent-loop.ts +0 -60
  133. package/src/daemon/conversation-memory.ts +0 -117
  134. package/src/daemon/conversation-runtime-assembly.ts +0 -2
  135. package/src/daemon/conversation-tool-setup.ts +0 -105
  136. package/src/daemon/conversation.ts +10 -1
  137. package/src/daemon/handlers/config-vercel.ts +92 -0
  138. package/src/daemon/handlers/conversations.ts +0 -11
  139. package/src/daemon/handlers/skills.ts +2 -15
  140. package/src/daemon/install-symlink.ts +195 -0
  141. package/src/daemon/lifecycle.ts +229 -96
  142. package/src/daemon/message-types/conversations.ts +3 -4
  143. package/src/daemon/message-types/diagnostics.ts +3 -22
  144. package/src/daemon/message-types/messages.ts +0 -2
  145. package/src/daemon/message-types/upgrades.ts +8 -0
  146. package/src/daemon/server.ts +30 -92
  147. package/src/events/domain-events.ts +2 -1
  148. package/src/followups/followup-store.ts +5 -2
  149. package/src/inbound/platform-callback-registration.ts +3 -3
  150. package/src/instrument.ts +8 -5
  151. package/src/memory/conversation-crud.ts +0 -236
  152. package/src/memory/conversation-title-service.ts +76 -11
  153. package/src/memory/db-init.ts +15 -11
  154. package/src/memory/indexer.ts +15 -106
  155. package/src/memory/items-extractor.ts +15 -1
  156. package/src/memory/job-handlers/conversation-starters.ts +4 -1
  157. package/src/memory/job-handlers/embedding.ts +0 -79
  158. package/src/memory/job-utils.ts +1 -1
  159. package/src/memory/jobs-store.ts +30 -13
  160. package/src/memory/jobs-worker.ts +31 -27
  161. package/src/memory/migrations/001-job-deferrals.ts +19 -0
  162. package/src/memory/migrations/004-entity-relation-dedup.ts +10 -0
  163. package/src/memory/migrations/005-fingerprint-scope-unique.ts +76 -0
  164. package/src/memory/migrations/006-scope-salted-fingerprints.ts +50 -0
  165. package/src/memory/migrations/007-assistant-id-to-self.ts +10 -0
  166. package/src/memory/migrations/008-remove-assistant-id-columns.ts +34 -0
  167. package/src/memory/migrations/009-llm-usage-events-drop-assistant-id.ts +26 -0
  168. package/src/memory/migrations/014-backfill-inbox-thread-state.ts +10 -0
  169. package/src/memory/migrations/015-drop-active-search-index.ts +17 -0
  170. package/src/memory/migrations/019-notification-tables-schema-migration.ts +12 -0
  171. package/src/memory/migrations/020-rename-macos-ios-channel-to-vellum.ts +121 -0
  172. package/src/memory/migrations/024-embedding-vector-blob.ts +74 -0
  173. package/src/memory/migrations/026a-embeddings-nullable-vector-json.ts +82 -0
  174. package/src/memory/migrations/036-normalize-phone-identities.ts +11 -0
  175. package/src/memory/migrations/116-messages-fts.ts +106 -1
  176. package/src/memory/migrations/126-backfill-guardian-principal-id.ts +52 -0
  177. package/src/memory/migrations/127-guardian-principal-id-not-null.ts +77 -0
  178. package/src/memory/migrations/134-contacts-notes-column.ts +13 -0
  179. package/src/memory/migrations/135-backfill-contact-interaction-stats.ts +20 -0
  180. package/src/memory/migrations/136-drop-assistant-id-columns.ts +52 -0
  181. package/src/memory/migrations/140-backfill-usage-cache-accounting.ts +13 -0
  182. package/src/memory/migrations/141-rename-verification-table.ts +54 -0
  183. package/src/memory/migrations/142-rename-verification-session-id-column.ts +25 -0
  184. package/src/memory/migrations/143-rename-guardian-verification-values.ts +35 -0
  185. package/src/memory/migrations/144-rename-voice-to-phone.ts +136 -0
  186. package/src/memory/migrations/145-drop-accounts-table.ts +32 -0
  187. package/src/memory/migrations/147-migrate-reminders-to-schedules.ts +14 -1
  188. package/src/memory/migrations/148-drop-reminders-table.ts +35 -1
  189. package/src/memory/migrations/150-oauth-apps-client-secret-path.ts +69 -1
  190. package/src/memory/migrations/162-guardian-timestamps-epoch-ms.ts +290 -0
  191. package/src/memory/migrations/169-rename-gmail-provider-key-to-google.ts +51 -1
  192. package/src/memory/migrations/174-rename-thread-starters-table.ts +47 -1
  193. package/src/memory/migrations/176-drop-capability-card-state.ts +13 -0
  194. package/src/memory/migrations/180-backfill-inline-attachments-to-disk.ts +16 -0
  195. package/src/memory/migrations/181-rename-thread-starters-checkpoints.ts +28 -1
  196. package/src/memory/migrations/189-drop-simplified-memory.ts +42 -0
  197. package/src/memory/migrations/190-call-session-skip-disclosure.ts +15 -0
  198. package/src/memory/migrations/191-backfill-audio-attachment-mime-types.ts +64 -0
  199. package/src/memory/migrations/192-contacts-user-file-column.ts +15 -0
  200. package/src/memory/migrations/index.ts +5 -3
  201. package/src/memory/migrations/registry.ts +90 -0
  202. package/src/memory/migrations/validate-migration-state.ts +137 -11
  203. package/src/memory/qdrant-circuit-breaker.ts +9 -0
  204. package/src/memory/qdrant-client.ts +4 -6
  205. package/src/memory/qdrant-manager.ts +64 -7
  206. package/src/memory/schema/calls.ts +1 -0
  207. package/src/memory/schema/contacts.ts +1 -0
  208. package/src/memory/schema/conversations.ts +0 -3
  209. package/src/memory/schema/index.ts +0 -2
  210. package/src/messaging/draft-store.ts +2 -2
  211. package/src/notifications/decision-engine.ts +4 -1
  212. package/src/oauth/connection-resolver.ts +6 -4
  213. package/src/permissions/checker.ts +0 -38
  214. package/src/permissions/defaults.ts +3 -3
  215. package/src/permissions/shell-identity.ts +76 -22
  216. package/src/permissions/trust-client.ts +2 -13
  217. package/src/permissions/trust-store.ts +8 -3
  218. package/src/permissions/types.ts +4 -2
  219. package/src/platform/client.ts +35 -7
  220. package/src/prompts/persona-resolver.ts +138 -0
  221. package/src/prompts/system-prompt.ts +36 -4
  222. package/src/prompts/templates/users/default.md +1 -0
  223. package/src/providers/registry.ts +27 -40
  224. package/src/runtime/auth/__tests__/credential-service.test.ts +0 -1
  225. package/src/runtime/auth/__tests__/external-assistant-id.test.ts +13 -68
  226. package/src/runtime/auth/external-assistant-id.ts +13 -59
  227. package/src/runtime/auth/route-policy.ts +29 -1
  228. package/src/runtime/auth/token-service.ts +53 -15
  229. package/src/runtime/channel-readiness-service.ts +1 -16
  230. package/src/runtime/http-server.ts +29 -2
  231. package/src/runtime/middleware/error-handler.ts +1 -9
  232. package/src/runtime/routes/audio-routes.ts +40 -0
  233. package/src/runtime/routes/btw-routes.ts +0 -17
  234. package/src/runtime/routes/conversation-management-routes.ts +0 -36
  235. package/src/runtime/routes/conversation-query-routes.ts +106 -2
  236. package/src/runtime/routes/conversation-routes.ts +4 -43
  237. package/src/runtime/routes/diagnostics-routes.ts +1 -477
  238. package/src/runtime/routes/identity-routes.ts +18 -29
  239. package/src/runtime/routes/inbound-stages/secret-ingress-check.ts +4 -33
  240. package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +1 -1
  241. package/src/runtime/routes/integrations/vercel.ts +89 -0
  242. package/src/runtime/routes/log-export-routes.ts +5 -0
  243. package/src/runtime/routes/memory-item-routes.test.ts +221 -3
  244. package/src/runtime/routes/memory-item-routes.ts +144 -4
  245. package/src/runtime/routes/migration-rollback-routes.ts +209 -0
  246. package/src/runtime/routes/migration-routes.ts +17 -1
  247. package/src/runtime/routes/notification-routes.ts +58 -0
  248. package/src/runtime/routes/schedule-routes.ts +65 -0
  249. package/src/runtime/routes/settings-routes.ts +41 -1
  250. package/src/runtime/routes/tts-routes.ts +86 -0
  251. package/src/runtime/routes/upgrade-broadcast-routes.ts +175 -0
  252. package/src/runtime/routes/workspace-commit-routes.ts +62 -0
  253. package/src/runtime/routes/workspace-routes.test.ts +22 -1
  254. package/src/runtime/routes/workspace-routes.ts +1 -1
  255. package/src/runtime/routes/workspace-utils.ts +86 -2
  256. package/src/schedule/schedule-store.ts +0 -21
  257. package/src/security/ces-credential-client.ts +59 -22
  258. package/src/security/ces-rpc-credential-backend.ts +85 -0
  259. package/src/security/credential-backend.ts +12 -88
  260. package/src/security/keychain-broker-client.ts +10 -2
  261. package/src/security/secure-keys.ts +94 -113
  262. package/src/skills/catalog-install.ts +13 -7
  263. package/src/skills/inline-command-render.ts +5 -1
  264. package/src/skills/inline-command-runner.ts +30 -2
  265. package/src/telemetry/usage-telemetry-reporter.ts +4 -2
  266. package/src/tools/calls/call-start.ts +1 -0
  267. package/src/tools/executor.ts +0 -4
  268. package/src/tools/memory/handlers.ts +1 -129
  269. package/src/tools/network/script-proxy/session-manager.ts +19 -4
  270. package/src/tools/network/web-fetch.ts +3 -1
  271. package/src/tools/permission-checker.ts +18 -0
  272. package/src/tools/skills/execute.ts +1 -1
  273. package/src/tools/skills/load.ts +9 -2
  274. package/src/tools/types.ts +0 -8
  275. package/src/util/errors.ts +0 -12
  276. package/src/util/platform.ts +8 -55
  277. package/src/util/xml.ts +8 -0
  278. package/src/workspace/git-service.ts +5 -2
  279. package/src/workspace/heartbeat-service.ts +5 -24
  280. package/src/workspace/migrations/001-avatar-rename.ts +15 -0
  281. package/src/workspace/migrations/003-seed-device-id.ts +17 -1
  282. package/src/workspace/migrations/004-extract-collect-usage-data.ts +33 -0
  283. package/src/workspace/migrations/005-add-send-diagnostics.ts +3 -0
  284. package/src/workspace/migrations/006-services-config.ts +49 -0
  285. package/src/workspace/migrations/007-web-search-provider-rename.ts +27 -0
  286. package/src/workspace/migrations/008-voice-timeout-and-max-steps.ts +3 -0
  287. package/src/workspace/migrations/009-backfill-conversation-disk-view.ts +4 -0
  288. package/src/workspace/migrations/010-app-dir-rename.ts +78 -0
  289. package/src/workspace/migrations/011-backfill-installation-id.ts +11 -0
  290. package/src/workspace/migrations/012-rename-conversation-disk-view-dirs.ts +44 -0
  291. package/src/workspace/migrations/013-repair-conversation-disk-view.ts +5 -0
  292. package/src/workspace/migrations/015-migrate-credentials-to-keychain.ts +153 -0
  293. package/src/workspace/migrations/016-extract-feature-flags-to-protected.ts +156 -0
  294. package/src/workspace/migrations/016-migrate-credentials-from-keychain.ts +150 -0
  295. package/src/workspace/migrations/017-seed-persona-dirs.ts +95 -0
  296. package/src/workspace/migrations/migrate-to-workspace-volume.ts +23 -1
  297. package/src/workspace/migrations/registry.ts +8 -0
  298. package/src/workspace/migrations/runner.ts +106 -2
  299. package/src/workspace/migrations/types.ts +4 -0
  300. package/src/__tests__/archive-recall.test.ts +0 -560
  301. package/src/__tests__/claude-code-skill-regression.test.ts +0 -206
  302. package/src/__tests__/claude-code-tool-profiles.test.ts +0 -99
  303. package/src/__tests__/conversation-memory-dirty-tail.test.ts +0 -150
  304. package/src/__tests__/conversation-switch-memory-reduction.test.ts +0 -474
  305. package/src/__tests__/db-memory-archive-migration.test.ts +0 -372
  306. package/src/__tests__/db-memory-brief-state-migration.test.ts +0 -213
  307. package/src/__tests__/db-memory-reducer-checkpoints.test.ts +0 -273
  308. package/src/__tests__/diagnostics-export.test.ts +0 -288
  309. package/src/__tests__/local-gateway-health.test.ts +0 -209
  310. package/src/__tests__/memory-brief-open-loops.test.ts +0 -530
  311. package/src/__tests__/memory-brief-time.test.ts +0 -285
  312. package/src/__tests__/memory-brief-wrapper.test.ts +0 -311
  313. package/src/__tests__/memory-chunk-archive.test.ts +0 -400
  314. package/src/__tests__/memory-chunk-dual-write.test.ts +0 -453
  315. package/src/__tests__/memory-episode-archive.test.ts +0 -370
  316. package/src/__tests__/memory-episode-dual-write.test.ts +0 -626
  317. package/src/__tests__/memory-observation-archive.test.ts +0 -375
  318. package/src/__tests__/memory-observation-dual-write.test.ts +0 -318
  319. package/src/__tests__/memory-reducer-job.test.ts +0 -538
  320. package/src/__tests__/memory-reducer-scheduling.test.ts +0 -473
  321. package/src/__tests__/memory-reducer-store.test.ts +0 -728
  322. package/src/__tests__/memory-reducer-types.test.ts +0 -707
  323. package/src/__tests__/memory-reducer.test.ts +0 -704
  324. package/src/__tests__/memory-simplified-config.test.ts +0 -281
  325. package/src/__tests__/secret-ingress-handler.test.ts +0 -120
  326. package/src/__tests__/simplified-memory-e2e.test.ts +0 -666
  327. package/src/__tests__/simplified-memory-runtime.test.ts +0 -616
  328. package/src/__tests__/swarm-conversation-integration.test.ts +0 -358
  329. package/src/__tests__/swarm-dag-pathological.test.ts +0 -547
  330. package/src/__tests__/swarm-orchestrator.test.ts +0 -463
  331. package/src/__tests__/swarm-plan-validator.test.ts +0 -384
  332. package/src/__tests__/swarm-recursion.test.ts +0 -197
  333. package/src/__tests__/swarm-router-planner.test.ts +0 -234
  334. package/src/__tests__/swarm-tool.test.ts +0 -185
  335. package/src/__tests__/swarm-worker-backend.test.ts +0 -144
  336. package/src/__tests__/swarm-worker-runner.test.ts +0 -288
  337. package/src/commands/__tests__/cc-command-registry.test.ts +0 -396
  338. package/src/commands/cc-command-registry.ts +0 -248
  339. package/src/config/bundled-skills/claude-code/SKILL.md +0 -53
  340. package/src/config/bundled-skills/claude-code/TOOLS.json +0 -47
  341. package/src/config/bundled-skills/claude-code/tools/claude-code.ts +0 -12
  342. package/src/config/bundled-skills/orchestration/SKILL.md +0 -33
  343. package/src/config/bundled-skills/orchestration/TOOLS.json +0 -35
  344. package/src/config/bundled-skills/orchestration/tools/swarm-delegate.ts +0 -12
  345. package/src/config/schemas/memory-simplified.ts +0 -101
  346. package/src/config/schemas/swarm.ts +0 -82
  347. package/src/logfire.ts +0 -135
  348. package/src/memory/archive-recall.ts +0 -516
  349. package/src/memory/archive-store.ts +0 -400
  350. package/src/memory/brief-formatting.ts +0 -33
  351. package/src/memory/brief-open-loops.ts +0 -266
  352. package/src/memory/brief-time.ts +0 -162
  353. package/src/memory/brief.ts +0 -75
  354. package/src/memory/job-handlers/backfill-simplified-memory.ts +0 -462
  355. package/src/memory/job-handlers/reduce-conversation-memory.ts +0 -229
  356. package/src/memory/migrations/185-memory-brief-state.ts +0 -52
  357. package/src/memory/migrations/186-memory-archive.ts +0 -109
  358. package/src/memory/migrations/187-memory-reducer-checkpoints.ts +0 -19
  359. package/src/memory/reducer-scheduler.ts +0 -242
  360. package/src/memory/reducer-store.ts +0 -271
  361. package/src/memory/reducer-types.ts +0 -106
  362. package/src/memory/reducer.ts +0 -467
  363. package/src/memory/schema/memory-archive.ts +0 -121
  364. package/src/memory/schema/memory-brief.ts +0 -55
  365. package/src/runtime/local-gateway-health.ts +0 -275
  366. package/src/security/secret-ingress.ts +0 -68
  367. package/src/swarm/backend-claude-code.ts +0 -225
  368. package/src/swarm/checkpoint.ts +0 -137
  369. package/src/swarm/graph-utils.ts +0 -53
  370. package/src/swarm/index.ts +0 -55
  371. package/src/swarm/limits.ts +0 -66
  372. package/src/swarm/orchestrator.ts +0 -424
  373. package/src/swarm/plan-validator.ts +0 -117
  374. package/src/swarm/router-planner.ts +0 -162
  375. package/src/swarm/router-prompts.ts +0 -39
  376. package/src/swarm/synthesizer.ts +0 -81
  377. package/src/swarm/types.ts +0 -72
  378. package/src/swarm/worker-backend.ts +0 -131
  379. package/src/swarm/worker-prompts.ts +0 -80
  380. package/src/swarm/worker-runner.ts +0 -170
  381. package/src/tools/claude-code/claude-code.ts +0 -610
  382. package/src/tools/swarm/delegate.ts +0 -205
@@ -1,400 +0,0 @@
1
- import { createHash } from "node:crypto";
2
-
3
- import { eq } from "drizzle-orm";
4
- import { v4 as uuid } from "uuid";
5
-
6
- import { estimateTextTokens } from "../context/token-estimator.js";
7
- import { getLogger } from "../util/logger.js";
8
- import { getDb, rawChanges } from "./db.js";
9
- import { enqueueMemoryJob, type MemoryJobType } from "./jobs-store.js";
10
- import {
11
- memoryChunks,
12
- memoryEpisodes,
13
- memoryObservations,
14
- } from "./schema.js";
15
-
16
- const log = getLogger("memory-archive-store");
17
-
18
- // ── Content hashing ─────────────────────────────────────────────────
19
-
20
- /**
21
- * Compute a SHA-256 content hash for a chunk's content, scoped by scopeId.
22
- * Used for idempotent upserts — if the hash already exists within the same
23
- * scope, the chunk is skipped.
24
- */
25
- export function computeChunkContentHash(
26
- scopeId: string,
27
- content: string,
28
- ): string {
29
- return createHash("sha256").update(`${scopeId}|${content}`).digest("hex");
30
- }
31
-
32
- /**
33
- * Compute a SHA-256 hash of the observation content, scoped by scopeId.
34
- * Used for idempotent chunk deduplication.
35
- */
36
- export function computeObservationContentHash(
37
- scopeId: string,
38
- content: string,
39
- ): string {
40
- return createHash("sha256").update(`${scopeId}|${content}`).digest("hex");
41
- }
42
-
43
- // ── Token estimation ────────────────────────────────────────────────
44
-
45
- /**
46
- * Rough token count estimate based on character length.
47
- * Uses the common ~4 chars/token heuristic for English text.
48
- */
49
- export function estimateTokens(text: string): number {
50
- return Math.max(1, Math.ceil(text.length / 4));
51
- }
52
-
53
- // ── Chunk upsert ────────────────────────────────────────────────────
54
-
55
- export interface UpsertChunkInput {
56
- /** Scope for memory isolation. Defaults to "default". */
57
- scopeId?: string;
58
- /** FK to the parent observation. */
59
- observationId: string;
60
- /** The chunk text to embed and recall. */
61
- content: string;
62
- /** Optional pre-computed token estimate. If omitted, estimated from content length. */
63
- tokenEstimate?: number;
64
- }
65
-
66
- export interface UpsertChunkResult {
67
- chunkId: string;
68
- /** True if a new row was inserted; false if an existing row matched the content hash. */
69
- inserted: boolean;
70
- }
71
-
72
- /**
73
- * Idempotently upsert a chunk into the archive. If a chunk with the same
74
- * (scopeId, contentHash) already exists, the insert is skipped and the
75
- * existing row's id is returned. Otherwise a new row is inserted and an
76
- * `embed_chunk` job is enqueued.
77
- */
78
- export function upsertChunk(input: UpsertChunkInput): UpsertChunkResult {
79
- const scopeId = input.scopeId ?? "default";
80
- const contentHash = computeChunkContentHash(scopeId, input.content);
81
- const tokenEstimate = input.tokenEstimate ?? estimateTokens(input.content);
82
- const db = getDb();
83
-
84
- // Check for an existing chunk with the same content hash in this scope
85
- const existing = db
86
- .select({ id: memoryChunks.id })
87
- .from(memoryChunks)
88
- .where(eq(memoryChunks.contentHash, contentHash))
89
- .get();
90
-
91
- if (existing) {
92
- log.debug(
93
- { scopeId, contentHash, existingId: existing.id },
94
- "Chunk already exists, skipping insert",
95
- );
96
- return { chunkId: existing.id, inserted: false };
97
- }
98
-
99
- const chunkId = uuid();
100
- const now = Date.now();
101
-
102
- db.insert(memoryChunks)
103
- .values({
104
- id: chunkId,
105
- scopeId,
106
- observationId: input.observationId,
107
- content: input.content,
108
- tokenEstimate,
109
- contentHash,
110
- createdAt: now,
111
- })
112
- .run();
113
-
114
- // Enqueue an embedding job for the new chunk
115
- enqueueMemoryJob("embed_chunk", {
116
- chunkId,
117
- scopeId,
118
- });
119
-
120
- log.debug(
121
- { chunkId, scopeId, contentHash },
122
- "Inserted new chunk and enqueued embed_chunk job",
123
- );
124
-
125
- return { chunkId, inserted: true };
126
- }
127
-
128
- /**
129
- * Upsert multiple chunks for a single observation. Returns results for
130
- * each input in the same order.
131
- */
132
- export function upsertChunks(inputs: UpsertChunkInput[]): UpsertChunkResult[] {
133
- return inputs.map((input) => upsertChunk(input));
134
- }
135
-
136
- // ── Chunk queries ───────────────────────────────────────────────────
137
-
138
- /**
139
- * Get a chunk by its ID.
140
- */
141
- export function getChunkById(
142
- chunkId: string,
143
- ): typeof memoryChunks.$inferSelect | undefined {
144
- const db = getDb();
145
- return db
146
- .select()
147
- .from(memoryChunks)
148
- .where(eq(memoryChunks.id, chunkId))
149
- .get();
150
- }
151
-
152
- /**
153
- * Get all chunks for a given observation.
154
- */
155
- export function getChunksByObservationId(
156
- observationId: string,
157
- ): Array<typeof memoryChunks.$inferSelect> {
158
- const db = getDb();
159
- return db
160
- .select()
161
- .from(memoryChunks)
162
- .where(eq(memoryChunks.observationId, observationId))
163
- .all();
164
- }
165
-
166
- // ── Episode insertion helpers ───────────────────────────────────────
167
-
168
- export interface InsertEpisodeParams {
169
- scopeId?: string;
170
- conversationId: string;
171
- title: string;
172
- summary: string;
173
- tokenEstimate: number;
174
- source?: string;
175
- startAt: number;
176
- endAt: number;
177
- }
178
-
179
- /**
180
- * Insert an episode row produced by conversation compaction.
181
- * Compaction episodes summarize a contiguous block of turns that was
182
- * compressed to free context-window space.
183
- *
184
- * An `embed_episode` job is enqueued automatically so the episode
185
- * becomes searchable via vector recall.
186
- */
187
- export function insertCompactionEpisode(params: InsertEpisodeParams): {
188
- episodeId: string;
189
- jobId: string;
190
- } {
191
- return insertEpisodeAndEnqueue(params);
192
- }
193
-
194
- /**
195
- * Insert an episode row produced by resolution (end-of-conversation)
196
- * summarization. Resolution episodes capture the full narrative arc
197
- * of a completed conversation.
198
- *
199
- * An `embed_episode` job is enqueued automatically so the episode
200
- * becomes searchable via vector recall.
201
- */
202
- export function insertResolutionEpisode(params: InsertEpisodeParams): {
203
- episodeId: string;
204
- jobId: string;
205
- } {
206
- return insertEpisodeAndEnqueue(params);
207
- }
208
-
209
- // ── Internal (episode) ──────────────────────────────────────────────
210
-
211
- function insertEpisodeAndEnqueue(params: InsertEpisodeParams): {
212
- episodeId: string;
213
- jobId: string;
214
- } {
215
- const db = getDb();
216
- const episodeId = uuid();
217
- const now = Date.now();
218
-
219
- db.insert(memoryEpisodes)
220
- .values({
221
- id: episodeId,
222
- scopeId: params.scopeId ?? "default",
223
- conversationId: params.conversationId,
224
- title: params.title,
225
- summary: params.summary,
226
- tokenEstimate: params.tokenEstimate,
227
- source: params.source ?? null,
228
- startAt: params.startAt,
229
- endAt: params.endAt,
230
- createdAt: now,
231
- updatedAt: now,
232
- })
233
- .run();
234
-
235
- const jobId = enqueueMemoryJob("embed_episode" satisfies MemoryJobType, {
236
- episodeId,
237
- });
238
-
239
- log.debug(
240
- { episodeId, jobId, conversationId: params.conversationId },
241
- "Inserted episode and enqueued embed job",
242
- );
243
-
244
- return { episodeId, jobId };
245
- }
246
-
247
- // ── Observation types ───────────────────────────────────────────────
248
-
249
- export interface InsertObservationParams {
250
- conversationId: string;
251
- messageId?: string | null;
252
- role: string;
253
- content: string;
254
- scopeId?: string;
255
- modality?: string;
256
- source?: string | null;
257
- }
258
-
259
- export interface InsertedObservation {
260
- observationId: string;
261
- chunkId: string | null;
262
- contentHash: string;
263
- embeddingJobId: string | null;
264
- }
265
-
266
- // ── Observation insert helpers ──────────────────────────────────────
267
-
268
- /**
269
- * Insert a raw observation row and its associated chunk. If a chunk with the
270
- * same content hash already exists in the scope, the chunk insert is skipped
271
- * (idempotent dual-write safety). An `embed_observation` job is enqueued when
272
- * a new chunk is created.
273
- *
274
- * Returns the observation ID, chunk ID (null if deduplicated), content hash,
275
- * and embedding job ID (null if no new chunk was created).
276
- */
277
- export function insertObservation(
278
- params: InsertObservationParams,
279
- ): InsertedObservation {
280
- const db = getDb();
281
- const now = Date.now();
282
- const scopeId = params.scopeId ?? "default";
283
- const modality = params.modality ?? "text";
284
-
285
- const observationId = uuid();
286
- const contentHash = computeObservationContentHash(scopeId, params.content);
287
-
288
- // Insert the observation row
289
- db.insert(memoryObservations)
290
- .values({
291
- id: observationId,
292
- scopeId,
293
- conversationId: params.conversationId,
294
- messageId: params.messageId ?? null,
295
- role: params.role,
296
- content: params.content,
297
- modality,
298
- source: params.source ?? null,
299
- createdAt: now,
300
- })
301
- .run();
302
-
303
- // Attempt to insert the chunk — the unique index on (scope_id, content_hash)
304
- // will cause a conflict if this content was already chunked. We use
305
- // onConflictDoNothing to silently skip the duplicate.
306
- const chunkId = uuid();
307
- const tokenEstimate = estimateTextTokens(params.content);
308
-
309
- db.insert(memoryChunks)
310
- .values({
311
- id: chunkId,
312
- scopeId,
313
- observationId,
314
- content: params.content,
315
- tokenEstimate,
316
- contentHash,
317
- createdAt: now,
318
- })
319
- .onConflictDoNothing({
320
- target: [memoryChunks.scopeId, memoryChunks.contentHash],
321
- })
322
- .run();
323
-
324
- // Drizzle bun:sqlite .run() returns void; use rawChanges() to detect no-ops
325
- const chunkInserted = rawChanges() > 0;
326
-
327
- let embeddingJobId: string | null = null;
328
- if (chunkInserted) {
329
- embeddingJobId = enqueueMemoryJob("embed_observation", {
330
- observationId,
331
- chunkId,
332
- });
333
- log.debug(
334
- { observationId, chunkId, contentHash, embeddingJobId },
335
- "Inserted observation with new chunk, enqueued embed job",
336
- );
337
- } else {
338
- log.debug(
339
- { observationId, contentHash },
340
- "Inserted observation, chunk deduplicated by content hash",
341
- );
342
- }
343
-
344
- return {
345
- observationId,
346
- chunkId: chunkInserted ? chunkId : null,
347
- contentHash,
348
- embeddingJobId,
349
- };
350
- }
351
-
352
- /**
353
- * Insert multiple observations in a single transaction.
354
- * Returns the results for each insertion.
355
- */
356
- export function insertObservations(
357
- observations: InsertObservationParams[],
358
- ): InsertedObservation[] {
359
- const db = getDb();
360
- const results: InsertedObservation[] = [];
361
- db.transaction((tx) => {
362
- // We don't use `tx` directly for individual inserts because
363
- // insertObservation uses getDb() internally. Instead, the transaction
364
- // wrapper ensures atomicity at the SQLite level.
365
- void tx;
366
- for (const obs of observations) {
367
- results.push(insertObservation(obs));
368
- }
369
- });
370
- return results;
371
- }
372
-
373
- /**
374
- * Look up an observation by ID.
375
- */
376
- export function getObservation(
377
- observationId: string,
378
- ): typeof memoryObservations.$inferSelect | undefined {
379
- const db = getDb();
380
- return db
381
- .select()
382
- .from(memoryObservations)
383
- .where(eq(memoryObservations.id, observationId))
384
- .get();
385
- }
386
-
387
- /**
388
- * Look up a chunk by observation ID. Returns the first chunk linked to the
389
- * given observation, or undefined if none exists.
390
- */
391
- export function getChunkByObservationId(
392
- observationId: string,
393
- ): typeof memoryChunks.$inferSelect | undefined {
394
- const db = getDb();
395
- return db
396
- .select()
397
- .from(memoryChunks)
398
- .where(eq(memoryChunks.observationId, observationId))
399
- .get();
400
- }
@@ -1,33 +0,0 @@
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
- }
@@ -1,266 +0,0 @@
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
- }