@vellumai/assistant 0.6.3 → 0.6.4

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 (667) hide show
  1. package/ARCHITECTURE.md +273 -10
  2. package/Dockerfile +2 -3
  3. package/bun.lock +5 -13
  4. package/docs/backup-troubleshooting.md +52 -0
  5. package/docs/browser-use-architecture-phase2.md +174 -0
  6. package/docs/stt-provider-onboarding.md +120 -0
  7. package/knip.json +12 -2
  8. package/node_modules/@vellumai/ces-contracts/bun.lock +8 -6
  9. package/node_modules/@vellumai/ces-contracts/package.json +3 -3
  10. package/openapi.yaml +982 -72
  11. package/package.json +4 -6
  12. package/scripts/generate-openapi.ts +0 -1
  13. package/scripts/test.sh +73 -18
  14. package/src/__tests__/agent-image-optimize.test.ts +28 -0
  15. package/src/__tests__/agent-loop.test.ts +123 -0
  16. package/src/__tests__/anthropic-provider.test.ts +263 -10
  17. package/src/__tests__/auto-analysis-end-to-end.test.ts +550 -0
  18. package/src/__tests__/auto-analysis-prompt.test.ts +50 -0
  19. package/src/__tests__/browser-fill-credential.test.ts +11 -0
  20. package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +2 -2
  21. package/src/__tests__/browser-skill-endstate.test.ts +31 -7
  22. package/src/__tests__/btw-routes.test.ts +7 -0
  23. package/src/__tests__/call-controller.test.ts +581 -20
  24. package/src/__tests__/catalog-files.test.ts +138 -0
  25. package/src/__tests__/channel-invite-transport.test.ts +2 -2
  26. package/src/__tests__/channel-readiness-routes.test.ts +16 -20
  27. package/src/__tests__/channel-readiness-service.test.ts +12 -7
  28. package/src/__tests__/checker.test.ts +157 -10
  29. package/src/__tests__/clawhub-files.test.ts +347 -0
  30. package/src/__tests__/commit-message-enrichment-service.test.ts +36 -19
  31. package/src/__tests__/config-analysis.test.ts +100 -0
  32. package/src/__tests__/config-schema.test.ts +1013 -66
  33. package/src/__tests__/config-watcher-cleanup-throttle.test.ts +339 -0
  34. package/src/__tests__/config-watcher.test.ts +43 -8
  35. package/src/__tests__/contact-store-user-file.test.ts +512 -0
  36. package/src/__tests__/contacts-write.test.ts +197 -0
  37. package/src/__tests__/context-window-manager.test.ts +88 -0
  38. package/src/__tests__/conversation-abort-tool-results.test.ts +2 -0
  39. package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -0
  40. package/src/__tests__/conversation-agent-loop.test.ts +98 -2
  41. package/src/__tests__/conversation-confirmation-signals.test.ts +135 -0
  42. package/src/__tests__/conversation-error.test.ts +70 -0
  43. package/src/__tests__/conversation-history-web-search.test.ts +11 -4
  44. package/src/__tests__/conversation-init.benchmark.test.ts +6 -1
  45. package/src/__tests__/conversation-launcher-skill-regression.test.ts +51 -0
  46. package/src/__tests__/conversation-list-source.test.ts +145 -0
  47. package/src/__tests__/conversation-pre-run-repair.test.ts +2 -0
  48. package/src/__tests__/conversation-provider-retry-repair.test.ts +2 -0
  49. package/src/__tests__/conversation-queue.test.ts +901 -60
  50. package/src/__tests__/conversation-routes-disk-view.test.ts +270 -0
  51. package/src/__tests__/conversation-runtime-assembly.test.ts +55 -0
  52. package/src/__tests__/conversation-skill-tools.test.ts +7 -4
  53. package/src/__tests__/conversation-slash-commands.test.ts +33 -0
  54. package/src/__tests__/conversation-slash-queue.test.ts +89 -18
  55. package/src/__tests__/conversation-slash-unknown.test.ts +2 -0
  56. package/src/__tests__/conversation-tool-setup-batch-authorized.test.ts +226 -0
  57. package/src/__tests__/conversation-workspace-injection.test.ts +2 -0
  58. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +2 -0
  59. package/src/__tests__/credential-health-service.test.ts +352 -0
  60. package/src/__tests__/credential-security-invariants.test.ts +5 -3
  61. package/src/__tests__/credential-vault-unit.test.ts +379 -3
  62. package/src/__tests__/credentials-cli.test.ts +40 -16
  63. package/src/__tests__/cross-provider-web-search.test.ts +146 -35
  64. package/src/__tests__/deterministic-verification-control-plane.test.ts +10 -1
  65. package/src/__tests__/device-id.test.ts +112 -0
  66. package/src/__tests__/docker-signing-key-bootstrap.test.ts +167 -4
  67. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +1 -3
  68. package/src/__tests__/email-html-renderer.test.ts +71 -0
  69. package/src/__tests__/email-invite-adapter.test.ts +36 -32
  70. package/src/__tests__/emit-event-signal.test.ts +71 -0
  71. package/src/__tests__/extension-id-sync-guard.test.ts +75 -8
  72. package/src/__tests__/fixtures/mock-chrome-extension.ts +11 -0
  73. package/src/__tests__/gateway-only-enforcement.test.ts +206 -1
  74. package/src/__tests__/gateway-only-guard.test.ts +0 -1
  75. package/src/__tests__/gemini-provider.test.ts +64 -0
  76. package/src/__tests__/get-skill-detail-audit.test.ts +325 -0
  77. package/src/__tests__/gmail-archive-fallback.test.ts +193 -0
  78. package/src/__tests__/gmail-archive-gate.test.ts +246 -0
  79. package/src/__tests__/gmail-preferences.test.ts +117 -0
  80. package/src/__tests__/headless-browser-interactions.test.ts +43 -0
  81. package/src/__tests__/headless-browser-mode.test.ts +614 -0
  82. package/src/__tests__/headless-browser-navigate.test.ts +142 -5
  83. package/src/__tests__/headless-browser-read-tools.test.ts +11 -0
  84. package/src/__tests__/headless-browser-snapshot.test.ts +10 -0
  85. package/src/__tests__/heartbeat-service.test.ts +70 -17
  86. package/src/__tests__/home-state-routes.test.ts +162 -0
  87. package/src/__tests__/host-bash-proxy.test.ts +0 -5
  88. package/src/__tests__/host-browser-e2e-cloud.test.ts +138 -4
  89. package/src/__tests__/host-browser-e2e-self-hosted.test.ts +4 -4
  90. package/src/__tests__/host-browser-ws-events-e2e.test.ts +103 -0
  91. package/src/__tests__/host-cu-proxy.test.ts +0 -5
  92. package/src/__tests__/identity-intro-cache.test.ts +40 -10
  93. package/src/__tests__/init-feature-flag-overrides.test.ts +38 -112
  94. package/src/__tests__/jobs-store-upsert-debounced.test.ts +141 -0
  95. package/src/__tests__/llm-context-normalization.test.ts +488 -0
  96. package/src/__tests__/llm-context-route-provider.test.ts +86 -5
  97. package/src/__tests__/llm-usage-store.test.ts +363 -0
  98. package/src/__tests__/media-stream-output.test.ts +555 -0
  99. package/src/__tests__/media-stream-parser.test.ts +374 -0
  100. package/src/__tests__/media-stream-server-integration.test.ts +1234 -0
  101. package/src/__tests__/media-stream-stt-session.test.ts +588 -0
  102. package/src/__tests__/media-turn-detector.test.ts +440 -0
  103. package/src/__tests__/message-queue.test.ts +125 -0
  104. package/src/__tests__/migration-export-http.test.ts +6 -6
  105. package/src/__tests__/migration-import-commit-http.test.ts +8 -6
  106. package/src/__tests__/migration-import-preflight-http.test.ts +6 -5
  107. package/src/__tests__/migration-validate-http.test.ts +3 -3
  108. package/src/__tests__/mock-gateway-ipc.ts +151 -0
  109. package/src/__tests__/model-intents.test.ts +2 -2
  110. package/src/__tests__/oauth-apps-routes.test.ts +1 -0
  111. package/src/__tests__/oauth-cli.test.ts +2 -0
  112. package/src/__tests__/oauth-connect-orchestrator.test.ts +2 -0
  113. package/src/__tests__/oauth-provider-serializer.test.ts +1 -0
  114. package/src/__tests__/oauth-providers-routes.test.ts +2 -0
  115. package/src/__tests__/oauth-store.test.ts +85 -0
  116. package/src/__tests__/oauth2-gateway-transport.test.ts +249 -6
  117. package/src/__tests__/onboarding-template-contract.test.ts +6 -13
  118. package/src/__tests__/openai-provider.test.ts +176 -0
  119. package/src/__tests__/openai-responses-cutover-guard.test.ts +184 -0
  120. package/src/__tests__/openai-responses-provider.test.ts +1105 -0
  121. package/src/__tests__/openrouter-token-estimation.test.ts +100 -0
  122. package/src/__tests__/outlook-unsubscribe.test.ts +31 -2
  123. package/src/__tests__/persona-resolver.test.ts +251 -0
  124. package/src/__tests__/platform-bash-auto-approve.test.ts +4 -0
  125. package/src/__tests__/platform.test.ts +92 -1
  126. package/src/__tests__/post-turn-tool-result-truncation.test.ts +47 -0
  127. package/src/__tests__/prechat-onboarding-contract.test.ts +267 -0
  128. package/src/__tests__/pricing.test.ts +174 -0
  129. package/src/__tests__/qdrant-manager.test.ts +29 -8
  130. package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +194 -0
  131. package/src/__tests__/relationship-state-contract.test.ts +175 -0
  132. package/src/__tests__/relay-server.test.ts +423 -5
  133. package/src/__tests__/search-skills-unified.test.ts +118 -0
  134. package/src/__tests__/secret-scanner-executor.test.ts +4 -0
  135. package/src/__tests__/secure-keys.test.ts +107 -0
  136. package/src/__tests__/send-endpoint-busy.test.ts +5 -1
  137. package/src/__tests__/sequence-store.test.ts +1 -1
  138. package/src/__tests__/server-history-render.test.ts +49 -0
  139. package/src/__tests__/settings-routes.test.ts +201 -0
  140. package/src/__tests__/skill-load-feature-flag.test.ts +1 -0
  141. package/src/__tests__/skills-file-content-endpoint.test.ts +276 -145
  142. package/src/__tests__/skills-files-catalog-fallback.test.ts +381 -93
  143. package/src/__tests__/skills.test.ts +5 -2
  144. package/src/__tests__/skillssh-files.test.ts +446 -0
  145. package/src/__tests__/slack-block-formatting.test.ts +110 -0
  146. package/src/__tests__/slack-channel-config.test.ts +564 -1
  147. package/src/__tests__/stt-catalog-parity.test.ts +282 -0
  148. package/src/__tests__/stt-stream-session.test.ts +535 -0
  149. package/src/__tests__/system-prompt.test.ts +112 -26
  150. package/src/__tests__/telephony-stt-routing.test.ts +329 -0
  151. package/src/__tests__/terminal-tools.test.ts +18 -7
  152. package/src/__tests__/test-preload.ts +18 -0
  153. package/src/__tests__/test-support/browser-skill-harness.ts +4 -1
  154. package/src/__tests__/tool-executor-lifecycle-events.test.ts +9 -5
  155. package/src/__tests__/tool-executor-shell-integration.test.ts +4 -0
  156. package/src/__tests__/tool-executor.test.ts +33 -24
  157. package/src/__tests__/tool-result-truncation.test.ts +36 -0
  158. package/src/__tests__/trust-store.test.ts +7 -1
  159. package/src/__tests__/trusted-contact-approval-notifier.test.ts +1 -1
  160. package/src/__tests__/tts-catalog-parity.test.ts +345 -0
  161. package/src/__tests__/twilio-routes-twiml.test.ts +512 -114
  162. package/src/__tests__/twilio-routes.test.ts +376 -0
  163. package/src/__tests__/unicode.test.ts +293 -0
  164. package/src/__tests__/update-bulletin-format.test.ts +59 -0
  165. package/src/__tests__/update-bulletin.test.ts +206 -5
  166. package/src/__tests__/usage-routes.test.ts +25 -4
  167. package/src/__tests__/user-reference.test.ts +46 -61
  168. package/src/__tests__/verification-control-plane-policy.test.ts +4 -0
  169. package/src/__tests__/voice-config-update.test.ts +403 -0
  170. package/src/__tests__/voice-quality.test.ts +434 -19
  171. package/src/__tests__/workspace-heartbeat-service.test.ts +7 -0
  172. package/src/__tests__/workspace-migration-033-stt-service-explicit-config.test.ts +547 -0
  173. package/src/__tests__/workspace-migration-034-remove-calls-voice-transcription-provider.test.ts +596 -0
  174. package/src/__tests__/workspace-migration-drop-user-md.test.ts +368 -0
  175. package/src/__tests__/workspace-migration-meets.test.ts +244 -0
  176. package/src/__tests__/workspace-migration-seed-device-id.test.ts +14 -20
  177. package/src/__tests__/workspace-policy.test.ts +2 -0
  178. package/src/agent/image-optimize.ts +24 -12
  179. package/src/agent/loop.ts +43 -3
  180. package/src/backup/__tests__/backup-key.test.ts +152 -0
  181. package/src/backup/__tests__/backup-worker.test.ts +767 -0
  182. package/src/backup/__tests__/list-snapshots.test.ts +87 -0
  183. package/src/backup/__tests__/local-writer.test.ts +218 -0
  184. package/src/backup/__tests__/offsite-writer.test.ts +641 -0
  185. package/src/backup/__tests__/paths.test.ts +300 -0
  186. package/src/backup/__tests__/restore.test.ts +498 -0
  187. package/src/backup/__tests__/snapshot-lock.test.ts +352 -0
  188. package/src/backup/__tests__/stream-crypt.test.ts +228 -0
  189. package/src/backup/backup-key.ts +137 -0
  190. package/src/backup/backup-worker.ts +459 -0
  191. package/src/backup/list-snapshots.ts +147 -0
  192. package/src/backup/local-writer.ts +133 -0
  193. package/src/backup/offsite-writer.ts +222 -0
  194. package/src/backup/paths.ts +226 -0
  195. package/src/backup/restore.ts +322 -0
  196. package/src/backup/snapshot-lock.ts +431 -0
  197. package/src/backup/stream-crypt.ts +263 -0
  198. package/src/bundler/package-resolver.ts +4 -0
  199. package/src/calls/audio-store.ts +11 -5
  200. package/src/calls/call-controller.ts +226 -71
  201. package/src/calls/call-domain.ts +9 -0
  202. package/src/calls/call-speech-output.ts +190 -0
  203. package/src/calls/call-transport.ts +77 -0
  204. package/src/calls/media-stream-audio-transcode.ts +173 -0
  205. package/src/calls/media-stream-output.ts +660 -0
  206. package/src/calls/media-stream-parser.ts +300 -0
  207. package/src/calls/media-stream-protocol.ts +166 -0
  208. package/src/calls/media-stream-server.ts +592 -0
  209. package/src/calls/media-stream-stt-session.ts +460 -0
  210. package/src/calls/media-turn-detector.ts +230 -0
  211. package/src/calls/relay-server.ts +90 -75
  212. package/src/calls/resolve-call-tts-provider.ts +136 -0
  213. package/src/calls/telephony-stt-routing.ts +145 -0
  214. package/src/calls/tts-call-strategy.ts +161 -0
  215. package/src/calls/tts-text-sanitizer.ts +32 -16
  216. package/src/calls/twilio-routes.ts +281 -17
  217. package/src/calls/voice-quality.ts +78 -35
  218. package/src/calls/voice-session-bridge.ts +8 -1
  219. package/src/channels/types.ts +16 -0
  220. package/src/cli/__tests__/run-assistant-command.ts +11 -1
  221. package/src/cli/commands/__tests__/backup.test.ts +1165 -0
  222. package/src/cli/commands/__tests__/domain-register.test.ts +234 -0
  223. package/src/cli/commands/__tests__/domain-status.test.ts +132 -0
  224. package/src/cli/commands/__tests__/email-attachment.test.ts +422 -0
  225. package/src/cli/commands/__tests__/email-download.test.ts +16 -1
  226. package/src/cli/commands/__tests__/email-list.test.ts +22 -4
  227. package/src/cli/commands/__tests__/email-register.test.ts +4 -4
  228. package/src/cli/commands/__tests__/email-send.test.ts +37 -4
  229. package/src/cli/commands/__tests__/email-status.test.ts +5 -1
  230. package/src/cli/commands/__tests__/email-unregister.test.ts +34 -5
  231. package/src/cli/commands/backup.ts +993 -0
  232. package/src/cli/commands/conversations.ts +77 -0
  233. package/src/cli/commands/credentials.ts +0 -1
  234. package/src/cli/commands/domain.ts +210 -0
  235. package/src/cli/commands/email.ts +255 -3
  236. package/src/cli/commands/oauth/__tests__/connect.test.ts +12 -0
  237. package/src/cli/commands/oauth/__tests__/providers-delete.test.ts +1 -0
  238. package/src/cli/commands/oauth/__tests__/providers-register.test.ts +1 -0
  239. package/src/cli/commands/oauth/__tests__/providers-update.test.ts +1 -0
  240. package/src/cli/commands/oauth/mode.ts +12 -3
  241. package/src/cli/commands/oauth/providers.ts +15 -0
  242. package/src/cli/commands/oauth/shared.ts +2 -1
  243. package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +4 -9
  244. package/src/cli/commands/platform/__tests__/connect.test.ts +6 -0
  245. package/src/cli/commands/platform/__tests__/disconnect.test.ts +7 -1
  246. package/src/cli/commands/platform/__tests__/status.test.ts +6 -0
  247. package/src/cli/program.ts +30 -4
  248. package/src/config/__tests__/backup-schema.test.ts +134 -0
  249. package/src/config/assistant-feature-flags.ts +61 -62
  250. package/src/config/bundled-skills/app-builder/references/CUSTOM_ROUTES.md +37 -1
  251. package/src/config/bundled-skills/browser/SKILL.md +30 -5
  252. package/src/config/bundled-skills/browser/TOOLS.json +123 -0
  253. package/src/config/bundled-skills/browser/tools/browser-attach.ts +12 -0
  254. package/src/config/bundled-skills/browser/tools/browser-detach.ts +12 -0
  255. package/src/config/bundled-skills/browser/tools/browser-status.ts +12 -0
  256. package/src/config/bundled-skills/browser/tools/browser-wait-for-download.ts +17 -0
  257. package/src/config/bundled-skills/contacts/SKILL.md +2 -2
  258. package/src/config/bundled-skills/gmail/SKILL.md +53 -7
  259. package/src/config/bundled-skills/gmail/TOOLS.json +33 -3
  260. package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +116 -9
  261. package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +138 -11
  262. package/src/config/bundled-skills/gmail/tools/gmail-preferences-tool.ts +59 -0
  263. package/src/config/bundled-skills/gmail/tools/gmail-preferences.ts +82 -0
  264. package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +113 -17
  265. package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +2 -2
  266. package/src/config/bundled-skills/media-processing/SKILL.md +3 -9
  267. package/src/config/bundled-skills/media-processing/TOOLS.json +1 -6
  268. package/src/config/bundled-skills/media-processing/__tests__/audio-transcribe.test.ts +125 -0
  269. package/src/config/bundled-skills/media-processing/__tests__/extract-keyframes.test.ts +181 -0
  270. package/src/config/bundled-skills/media-processing/__tests__/preprocess-audio.test.ts +141 -0
  271. package/src/config/bundled-skills/media-processing/services/audio-transcribe.ts +32 -87
  272. package/src/config/bundled-skills/media-processing/services/preprocess.ts +8 -4
  273. package/src/config/bundled-skills/media-processing/tools/extract-keyframes.ts +0 -10
  274. package/src/config/bundled-skills/messaging/SKILL.md +3 -3
  275. package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +2 -2
  276. package/src/config/bundled-skills/outlook/SKILL.md +2 -2
  277. package/src/config/bundled-skills/outlook/tools/outlook-unsubscribe.ts +2 -2
  278. package/src/config/bundled-skills/phone-calls/SKILL.md +2 -2
  279. package/src/config/bundled-skills/phone-calls/references/CONFIG.md +27 -18
  280. package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +3 -3
  281. package/src/config/bundled-skills/settings/TOOLS.json +3 -3
  282. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +26 -22
  283. package/src/config/bundled-skills/slack/SKILL.md +1 -0
  284. package/src/config/bundled-skills/transcribe/SKILL.md +9 -14
  285. package/src/config/bundled-skills/transcribe/TOOLS.json +2 -7
  286. package/src/config/bundled-skills/transcribe/tools/transcribe-media.test.ts +256 -0
  287. package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +38 -188
  288. package/src/config/bundled-tool-registry.ts +8 -0
  289. package/src/config/env-registry.ts +24 -0
  290. package/src/config/env.ts +34 -10
  291. package/src/config/feature-flag-registry.json +46 -14
  292. package/src/config/loader.ts +26 -12
  293. package/src/config/schema.ts +35 -10
  294. package/src/config/schemas/__tests__/stt.test.ts +43 -0
  295. package/src/config/schemas/analysis.ts +51 -0
  296. package/src/config/schemas/backup.ts +72 -0
  297. package/src/config/schemas/calls.ts +1 -26
  298. package/src/config/schemas/elevenlabs.ts +0 -59
  299. package/src/config/schemas/filing.ts +47 -7
  300. package/src/config/schemas/heartbeat.ts +27 -5
  301. package/src/config/schemas/host-browser.ts +47 -1
  302. package/src/config/schemas/inference.ts +1 -1
  303. package/src/config/schemas/memory-lifecycle.ts +14 -2
  304. package/src/config/schemas/services.ts +44 -0
  305. package/src/config/schemas/stt.ts +59 -0
  306. package/src/config/schemas/tts.ts +230 -0
  307. package/src/config/schemas/updates.ts +14 -0
  308. package/src/config/skills.ts +4 -0
  309. package/src/config/types.ts +4 -0
  310. package/src/contacts/contact-store.ts +56 -11
  311. package/src/contacts/contacts-write.ts +38 -1
  312. package/src/context/post-turn-tool-result-truncation.ts +3 -2
  313. package/src/context/tool-result-truncation.ts +2 -1
  314. package/src/context/window-manager.ts +45 -12
  315. package/src/credential-execution/executable-discovery.ts +12 -2
  316. package/src/credential-execution/process-manager.ts +33 -2
  317. package/src/credential-health/credential-health-service.ts +366 -0
  318. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +324 -0
  319. package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +497 -0
  320. package/src/daemon/__tests__/conversation-tool-setup.test.ts +17 -8
  321. package/src/daemon/__tests__/lifecycle-startup-ordering.test.ts +127 -0
  322. package/src/daemon/config-watcher.ts +99 -5
  323. package/src/daemon/conversation-agent-loop-handlers.ts +6 -0
  324. package/src/daemon/conversation-agent-loop.ts +101 -24
  325. package/src/daemon/conversation-error.ts +11 -0
  326. package/src/daemon/conversation-history.ts +40 -6
  327. package/src/daemon/conversation-launch.ts +220 -0
  328. package/src/daemon/conversation-lifecycle.ts +59 -9
  329. package/src/daemon/conversation-messaging.ts +37 -3
  330. package/src/daemon/conversation-notifiers.ts +5 -0
  331. package/src/daemon/conversation-process.ts +581 -19
  332. package/src/daemon/conversation-queue-manager.ts +24 -0
  333. package/src/daemon/conversation-runtime-assembly.ts +11 -1
  334. package/src/daemon/conversation-slash.ts +36 -0
  335. package/src/daemon/conversation-surfaces.ts +94 -4
  336. package/src/daemon/conversation-tool-setup.ts +25 -0
  337. package/src/daemon/conversation-usage.ts +7 -4
  338. package/src/daemon/conversation.ts +86 -28
  339. package/src/daemon/handlers/config-slack-channel.ts +269 -94
  340. package/src/daemon/handlers/conversations.ts +4 -1
  341. package/src/daemon/handlers/shared.ts +22 -0
  342. package/src/daemon/handlers/skills.ts +321 -77
  343. package/src/daemon/host-browser-proxy.ts +2 -1
  344. package/src/daemon/lifecycle.ts +122 -25
  345. package/src/daemon/message-protocol.ts +6 -0
  346. package/src/daemon/message-types/conversations.ts +34 -1
  347. package/src/daemon/message-types/home.ts +40 -0
  348. package/src/daemon/message-types/meet.ts +143 -0
  349. package/src/daemon/message-types/messages.ts +14 -0
  350. package/src/daemon/message-types/schedules.ts +34 -2
  351. package/src/daemon/message-types/skills.ts +16 -0
  352. package/src/daemon/message-types/surfaces.ts +2 -0
  353. package/src/daemon/server.ts +347 -2
  354. package/src/daemon/shutdown-handlers.ts +32 -4
  355. package/src/daemon/shutdown-registry.ts +40 -0
  356. package/src/daemon/tool-side-effects.ts +9 -0
  357. package/src/email/html-renderer.ts +76 -0
  358. package/src/heartbeat/heartbeat-service.ts +93 -7
  359. package/src/home/__tests__/assistant-feed-authoring.test.ts +156 -0
  360. package/src/home/__tests__/emit-feed-event.test.ts +169 -0
  361. package/src/home/__tests__/feed-scheduler.test.ts +194 -0
  362. package/src/home/__tests__/feed-types.test.ts +275 -0
  363. package/src/home/__tests__/feed-writer.test.ts +688 -0
  364. package/src/home/__tests__/phase5-exit-criteria.test.ts +212 -0
  365. package/src/home/__tests__/platform-gmail-digest.test.ts +222 -0
  366. package/src/home/__tests__/progress-formula.test.ts +213 -0
  367. package/src/home/__tests__/relationship-state-writer.test.ts +740 -0
  368. package/src/home/__tests__/rollup-producer.test.ts +398 -0
  369. package/src/home/assistant-feed-authoring.ts +124 -0
  370. package/src/home/emit-feed-event.ts +158 -0
  371. package/src/home/feed-scheduler.ts +247 -0
  372. package/src/home/feed-types.ts +181 -0
  373. package/src/home/feed-writer.ts +469 -0
  374. package/src/home/platform-gmail-digest.ts +163 -0
  375. package/src/home/progress-formula.ts +86 -0
  376. package/src/home/relationship-state-writer.ts +824 -0
  377. package/src/home/relationship-state.ts +143 -0
  378. package/src/home/rollup-producer.ts +384 -0
  379. package/src/hooks/runner.ts +7 -0
  380. package/src/inbound/platform-callback-registration.ts +12 -3
  381. package/src/inbound/public-ingress-urls.ts +12 -0
  382. package/src/instrument.ts +1 -1
  383. package/src/ipc/__tests__/cli-ipc.test.ts +200 -0
  384. package/src/ipc/cli-client.ts +151 -0
  385. package/src/ipc/cli-server.ts +234 -0
  386. package/src/ipc/gateway-client.ts +180 -0
  387. package/src/ipc/routes/index.ts +5 -0
  388. package/src/ipc/routes/wake-conversation.ts +19 -0
  389. package/src/memory/__tests__/auto-analysis-enqueue.test.ts +356 -0
  390. package/src/memory/__tests__/auto-analysis-guard.test.ts +57 -0
  391. package/src/memory/__tests__/conversation-analyze-job.test.ts +232 -0
  392. package/src/memory/__tests__/find-analysis-conversation.test.ts +196 -0
  393. package/src/memory/app-store.ts +1 -1
  394. package/src/memory/attachments-store.ts +70 -0
  395. package/src/memory/auto-analysis-enqueue.ts +127 -0
  396. package/src/memory/auto-analysis-guard.ts +27 -0
  397. package/src/memory/cleanup-schedule-state.ts +37 -0
  398. package/src/memory/conversation-analyze-job.ts +73 -0
  399. package/src/memory/conversation-crud.ts +99 -0
  400. package/src/memory/conversation-disk-view.ts +7 -0
  401. package/src/memory/conversation-group-migration.ts +34 -2
  402. package/src/memory/conversation-queries.ts +6 -5
  403. package/src/memory/db-init.ts +6 -0
  404. package/src/memory/db-maintenance.ts +108 -0
  405. package/src/memory/db.ts +1 -0
  406. package/src/memory/graph/conversation-graph-memory.ts +15 -0
  407. package/src/memory/graph/extraction.test.ts +23 -0
  408. package/src/memory/graph/extraction.ts +8 -0
  409. package/src/memory/graph/retriever.ts +27 -18
  410. package/src/memory/graph/scoring.test.ts +186 -0
  411. package/src/memory/graph/scoring.ts +31 -1
  412. package/src/memory/graph/tools.ts +1 -1
  413. package/src/memory/group-crud.ts +6 -1
  414. package/src/memory/indexer.ts +95 -16
  415. package/src/memory/job-handlers/cleanup.ts +11 -8
  416. package/src/memory/job-handlers/conversation-starters.ts +16 -10
  417. package/src/memory/jobs-store.ts +64 -4
  418. package/src/memory/jobs-worker.ts +22 -9
  419. package/src/memory/llm-usage-store.ts +92 -56
  420. package/src/memory/migrations/219-oauth-providers-token-exchange-body-format.ts +15 -0
  421. package/src/memory/migrations/220-normalize-user-file-by-principal.ts +190 -0
  422. package/src/memory/migrations/221-conversations-archived-at.ts +16 -0
  423. package/src/memory/migrations/index.ts +6 -0
  424. package/src/memory/migrations/registry.ts +8 -0
  425. package/src/memory/qdrant-manager.ts +43 -16
  426. package/src/memory/schema/conversations.ts +2 -0
  427. package/src/memory/schema/oauth.ts +3 -0
  428. package/src/memory/usage-buckets.ts +396 -0
  429. package/src/messaging/providers/gmail/client.ts +57 -6
  430. package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +282 -0
  431. package/src/messaging/providers/slack/adapter.ts +143 -38
  432. package/src/messaging/providers/slack/client.ts +16 -0
  433. package/src/messaging/providers/slack/types.ts +4 -0
  434. package/src/notifications/decision-engine.ts +3 -3
  435. package/src/notifications/signal.ts +5 -0
  436. package/src/oauth/__tests__/identity-verifier.test.ts +1 -0
  437. package/src/oauth/byo-connection.test.ts +18 -1
  438. package/src/oauth/byo-connection.ts +3 -1
  439. package/src/oauth/connect-orchestrator.ts +2 -0
  440. package/src/oauth/connection-resolver.ts +6 -2
  441. package/src/oauth/connection.ts +2 -0
  442. package/src/oauth/oauth-store.ts +9 -0
  443. package/src/oauth/platform-connection.test.ts +98 -0
  444. package/src/oauth/platform-connection.ts +52 -31
  445. package/src/oauth/seed-providers.ts +7 -0
  446. package/src/permissions/checker.ts +16 -6
  447. package/src/permissions/defaults.ts +49 -1
  448. package/src/permissions/trust-store.ts +3 -3
  449. package/src/permissions/workspace-policy.ts +3 -0
  450. package/src/platform/client.test.ts +10 -0
  451. package/src/platform/sync-identity.ts +129 -0
  452. package/src/prompts/persona-resolver.ts +126 -2
  453. package/src/prompts/system-prompt.ts +59 -18
  454. package/src/prompts/templates/BOOTSTRAP.md +5 -5
  455. package/src/prompts/templates/SOUL.md +3 -1
  456. package/src/prompts/templates/UPDATES.md +12 -0
  457. package/src/prompts/templates/channels/slack.md +20 -0
  458. package/src/prompts/update-bulletin-format.ts +26 -9
  459. package/src/prompts/update-bulletin.ts +34 -23
  460. package/src/prompts/user-reference.ts +20 -17
  461. package/src/providers/__tests__/provider-secret-catalog.test.ts +42 -0
  462. package/src/providers/anthropic/client.ts +157 -61
  463. package/src/providers/fireworks/client.ts +2 -2
  464. package/src/providers/gemini/client.ts +9 -1
  465. package/src/providers/model-catalog.ts +6 -0
  466. package/src/providers/model-intents.ts +4 -4
  467. package/src/providers/ollama/client.ts +2 -2
  468. package/src/providers/openai/chat-completions-provider.ts +474 -0
  469. package/src/providers/openai/client.ts +25 -440
  470. package/src/providers/openai/responses-provider.ts +502 -0
  471. package/src/providers/openrouter/client.ts +101 -4
  472. package/src/providers/provider-secret-catalog.ts +139 -0
  473. package/src/providers/registry.ts +2 -2
  474. package/src/providers/retry.ts +14 -3
  475. package/src/providers/speech-to-text/__tests__/provider-catalog.test.ts +251 -0
  476. package/src/providers/speech-to-text/__tests__/resolve.test.ts +828 -0
  477. package/src/providers/speech-to-text/deepgram-realtime.test.ts +980 -0
  478. package/src/providers/speech-to-text/deepgram-realtime.ts +767 -0
  479. package/src/providers/speech-to-text/deepgram.test.ts +332 -0
  480. package/src/providers/speech-to-text/deepgram.ts +115 -0
  481. package/src/providers/speech-to-text/google-gemini-live-stream.test.ts +743 -0
  482. package/src/providers/speech-to-text/google-gemini-live-stream.ts +625 -0
  483. package/src/providers/speech-to-text/google-gemini.test.ts +226 -0
  484. package/src/providers/speech-to-text/google-gemini.ts +101 -0
  485. package/src/providers/speech-to-text/openai-whisper-stream.test.ts +564 -0
  486. package/src/providers/speech-to-text/openai-whisper-stream.ts +381 -0
  487. package/src/providers/speech-to-text/openai-whisper.test.ts +1 -37
  488. package/src/providers/speech-to-text/openai-whisper.ts +63 -33
  489. package/src/providers/speech-to-text/provider-catalog.ts +306 -0
  490. package/src/providers/speech-to-text/resolve.ts +386 -6
  491. package/src/providers/types.ts +9 -0
  492. package/src/runtime/AGENTS.md +43 -1
  493. package/src/runtime/__tests__/agent-wake.test.ts +831 -0
  494. package/src/runtime/__tests__/runtime-mode.test.ts +62 -0
  495. package/src/runtime/__tests__/slack-block-formatting.test.ts +481 -0
  496. package/src/runtime/agent-wake.ts +512 -0
  497. package/src/runtime/auth/__tests__/route-policy.test.ts +40 -0
  498. package/src/runtime/auth/route-policy.ts +30 -5
  499. package/src/runtime/auth/token-service.ts +56 -1
  500. package/src/runtime/btw-sidechain.ts +2 -0
  501. package/src/runtime/capability-tokens.ts +10 -10
  502. package/src/runtime/channel-invite-transport.ts +1 -1
  503. package/src/runtime/channel-invite-transports/email.ts +14 -6
  504. package/src/runtime/channel-readiness-service.ts +12 -22
  505. package/src/runtime/chrome-extension-registry.ts +38 -2
  506. package/src/runtime/http-server.ts +395 -10
  507. package/src/runtime/http-types.ts +6 -2
  508. package/src/runtime/migrations/__tests__/vbundle-import-credentials.test.ts +36 -0
  509. package/src/runtime/migrations/__tests__/vbundle-legacy-user-md.test.ts +360 -0
  510. package/src/runtime/migrations/migration-transport.ts +1 -0
  511. package/src/runtime/migrations/migration-wizard.ts +1 -0
  512. package/src/runtime/migrations/vbundle-import-analyzer.ts +77 -1
  513. package/src/runtime/migrations/vbundle-importer.ts +34 -0
  514. package/src/runtime/pending-interactions.ts +0 -11
  515. package/src/runtime/routes/__tests__/backup-routes.test.ts +967 -0
  516. package/src/runtime/routes/__tests__/home-feed-routes.test.ts +507 -0
  517. package/src/runtime/routes/__tests__/migration-import-credential-filter.test.ts +208 -0
  518. package/src/runtime/routes/__tests__/stt-routes.test.ts +406 -0
  519. package/src/runtime/routes/__tests__/tts-routes.test.ts +474 -0
  520. package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +148 -17
  521. package/src/runtime/routes/app-management-routes.ts +12 -18
  522. package/src/runtime/routes/attachment-routes.test.ts +9 -3
  523. package/src/runtime/routes/attachment-routes.ts +216 -17
  524. package/src/runtime/routes/backup-routes.ts +519 -0
  525. package/src/runtime/routes/browser-extension-pair-routes.ts +82 -23
  526. package/src/runtime/routes/btw-routes.ts +8 -6
  527. package/src/runtime/routes/contact-routes.test.ts +298 -0
  528. package/src/runtime/routes/contact-routes.ts +132 -5
  529. package/src/runtime/routes/conversation-analysis-routes.ts +22 -142
  530. package/src/runtime/routes/conversation-management-routes.ts +115 -0
  531. package/src/runtime/routes/conversation-routes.ts +367 -146
  532. package/src/runtime/routes/filing-routes.ts +93 -0
  533. package/src/runtime/routes/home-feed-routes.ts +334 -0
  534. package/src/runtime/routes/home-state-routes.ts +138 -0
  535. package/src/runtime/routes/host-browser-routes.ts +3 -14
  536. package/src/runtime/routes/identity-intro-cache.ts +7 -3
  537. package/src/runtime/routes/identity-routes.ts +3 -17
  538. package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +46 -39
  539. package/src/runtime/routes/inbound-stages/transcribe-audio.ts +15 -15
  540. package/src/runtime/routes/integrations/slack/__tests__/channel.test.ts +137 -0
  541. package/src/runtime/routes/integrations/slack/__tests__/share.test.ts +179 -0
  542. package/src/runtime/routes/integrations/slack/channel.ts +11 -3
  543. package/src/runtime/routes/integrations/slack/share.ts +45 -7
  544. package/src/runtime/routes/llm-context-normalization.ts +303 -0
  545. package/src/runtime/routes/memory-item-routes.test.ts +3 -2
  546. package/src/runtime/routes/migration-routes.ts +40 -5
  547. package/src/runtime/routes/settings-routes.ts +22 -5
  548. package/src/runtime/routes/skills-routes.ts +76 -7
  549. package/src/runtime/routes/stt-routes.ts +233 -0
  550. package/src/runtime/routes/surface-action-routes.ts +41 -2
  551. package/src/runtime/routes/tts-routes.ts +108 -24
  552. package/src/runtime/routes/usage-routes.ts +30 -2
  553. package/src/runtime/routes/user-route-dispatcher.ts +50 -5
  554. package/src/runtime/routes/user-routes.ts +13 -1
  555. package/src/runtime/routes/work-items-routes.ts +8 -1
  556. package/src/runtime/runtime-mode.ts +33 -0
  557. package/src/runtime/services/__tests__/analyze-conversation.test.ts +444 -0
  558. package/src/runtime/services/__tests__/analyze-deps-singleton.test.ts +67 -0
  559. package/src/runtime/services/__tests__/auto-analysis-prompt.test.ts +53 -0
  560. package/src/runtime/services/__tests__/manual-analysis-prompt.test.ts +41 -0
  561. package/src/runtime/services/analyze-conversation.ts +344 -0
  562. package/src/runtime/services/analyze-deps-singleton.ts +32 -0
  563. package/src/runtime/services/auto-analysis-prompt.ts +55 -0
  564. package/src/runtime/skill-route-registry.ts +49 -0
  565. package/src/runtime/slack-block-formatting.ts +437 -10
  566. package/src/schedule/scheduler.ts +50 -0
  567. package/src/security/oauth2.ts +26 -4
  568. package/src/security/secure-keys.ts +25 -2
  569. package/src/security/token-manager.ts +8 -0
  570. package/src/sequence/engine.ts +23 -0
  571. package/src/sequence/types.ts +1 -1
  572. package/src/skills/catalog-files.ts +64 -2
  573. package/src/skills/category-inference.ts +122 -0
  574. package/src/skills/clawhub-files.ts +213 -0
  575. package/src/skills/clawhub.ts +84 -23
  576. package/src/skills/skill-file-provider.ts +40 -0
  577. package/src/skills/skillssh-files.ts +395 -0
  578. package/src/skills/skillssh-registry.ts +4 -4
  579. package/src/stt/__tests__/daemon-batch-transcriber.test.ts +392 -0
  580. package/src/stt/__tests__/types.test.ts +89 -0
  581. package/src/stt/daemon-batch-transcriber.ts +195 -0
  582. package/src/stt/stt-stream-session.ts +499 -0
  583. package/src/stt/types.ts +330 -0
  584. package/src/stt/wav-encoder.test.ts +373 -0
  585. package/src/stt/wav-encoder.ts +175 -0
  586. package/src/subagent/manager.ts +38 -14
  587. package/src/tools/browser/__tests__/browser-mode.test.ts +119 -0
  588. package/src/tools/browser/__tests__/browser-status.test.ts +123 -0
  589. package/src/tools/browser/browser-execution.ts +1163 -23
  590. package/src/tools/browser/browser-manager.ts +45 -0
  591. package/src/tools/browser/browser-mode-constants.ts +12 -0
  592. package/src/tools/browser/browser-mode.ts +92 -0
  593. package/src/tools/browser/browser-status-constants.ts +33 -0
  594. package/src/tools/browser/cdp-client/__tests__/cdp-inspect-client.test.ts +393 -0
  595. package/src/tools/browser/cdp-client/__tests__/extension-cdp-client.test.ts +29 -0
  596. package/src/tools/browser/cdp-client/__tests__/factory.test.ts +1648 -32
  597. package/src/tools/browser/cdp-client/cdp-inspect/__tests__/discovery.test.ts +264 -0
  598. package/src/tools/browser/cdp-client/cdp-inspect/discovery.ts +183 -17
  599. package/src/tools/browser/cdp-client/cdp-inspect-client.ts +254 -21
  600. package/src/tools/browser/cdp-client/errors.ts +15 -0
  601. package/src/tools/browser/cdp-client/extension-cdp-client.ts +39 -16
  602. package/src/tools/browser/cdp-client/factory.ts +797 -87
  603. package/src/tools/browser/cdp-client/index.ts +16 -2
  604. package/src/tools/browser/cdp-client/types.ts +68 -0
  605. package/src/tools/credentials/vault.ts +35 -6
  606. package/src/tools/network/web-fetch.ts +5 -2
  607. package/src/tools/network/web-search.ts +5 -2
  608. package/src/tools/shared/shell-output.ts +3 -1
  609. package/src/tools/side-effects.ts +2 -0
  610. package/src/tools/skills/sandbox-runner.ts +3 -2
  611. package/src/tools/terminal/safe-env.ts +10 -2
  612. package/src/tools/terminal/shell.ts +15 -4
  613. package/src/tools/tool-manifest.ts +21 -0
  614. package/src/tools/types.ts +17 -0
  615. package/src/tools/ui-surface/definitions.ts +6 -1
  616. package/src/tts/__tests__/provider-adapters.test.ts +834 -0
  617. package/src/tts/__tests__/provider-catalog-consistency.test.ts +196 -0
  618. package/src/tts/__tests__/provider-catalog.test.ts +183 -0
  619. package/src/tts/__tests__/provider-registry.test.ts +90 -0
  620. package/src/tts/provider-catalog.ts +201 -0
  621. package/src/tts/provider-registry.ts +73 -0
  622. package/src/tts/providers/deepgram-provider.ts +219 -0
  623. package/src/tts/providers/elevenlabs-provider.ts +211 -0
  624. package/src/tts/providers/fish-audio-provider.ts +183 -0
  625. package/src/tts/providers/index.ts +42 -0
  626. package/src/tts/providers/register-builtins.ts +130 -0
  627. package/src/tts/synthesize-text.ts +110 -0
  628. package/src/tts/tts-config-resolver.ts +78 -0
  629. package/src/tts/types.ts +153 -0
  630. package/src/types/onboarding-context.ts +7 -0
  631. package/src/util/abort-reasons.ts +58 -0
  632. package/src/util/device-id.ts +32 -16
  633. package/src/util/errors.ts +9 -1
  634. package/src/util/platform.ts +54 -10
  635. package/src/util/pricing.ts +66 -3
  636. package/src/util/spawn.ts +1 -1
  637. package/src/util/truncate.ts +4 -2
  638. package/src/util/unicode.ts +201 -0
  639. package/src/version.ts +19 -24
  640. package/src/watcher/engine.ts +23 -0
  641. package/src/watcher/watcher-store.ts +31 -0
  642. package/src/workspace/migrations/003-seed-device-id.ts +9 -3
  643. package/src/workspace/migrations/017-seed-persona-dirs.ts +68 -4
  644. package/src/workspace/migrations/029-seed-pkb.ts +1 -1
  645. package/src/workspace/migrations/031-drop-user-md.ts +317 -0
  646. package/src/workspace/migrations/031-llm-log-retention-zero-to-null.ts +73 -0
  647. package/src/workspace/migrations/032-tts-provider-unification.ts +227 -0
  648. package/src/workspace/migrations/033-stt-service-explicit-config.ts +122 -0
  649. package/src/workspace/migrations/034-remove-calls-voice-transcription-provider.ts +215 -0
  650. package/src/workspace/migrations/035-seed-slack-channel-persona.ts +50 -0
  651. package/src/workspace/migrations/036-update-pkb-index-bar.ts +37 -0
  652. package/src/workspace/migrations/037-create-meets-dir.ts +61 -0
  653. package/src/workspace/migrations/registry.ts +16 -0
  654. package/src/workspace/top-level-renderer.ts +13 -1
  655. package/src/workspace/turn-commit.ts +31 -0
  656. package/src/__tests__/email-cli.test.ts +0 -297
  657. package/src/__tests__/email-service-config-fallback.test.ts +0 -102
  658. package/src/cli/commands/browser-relay.ts +0 -466
  659. package/src/email/guardrails.ts +0 -221
  660. package/src/email/provider.ts +0 -117
  661. package/src/email/providers/agentmail.ts +0 -361
  662. package/src/email/providers/index.ts +0 -65
  663. package/src/email/service.ts +0 -384
  664. package/src/email/types.ts +0 -126
  665. package/src/prompts/templates/USER.md +0 -13
  666. package/src/providers/speech-to-text/types.ts +0 -17
  667. package/src/runtime/routes/browser-cdp-routes.ts +0 -229
@@ -5,9 +5,12 @@ import {
5
5
  computeEffectiveSignificance,
6
6
  computeRecencyBoost,
7
7
  computeTemporalBoost,
8
+ DEFAULT_WEIGHTS,
8
9
  PER_TURN_WEIGHTS,
10
+ PROCEDURAL_WEIGHTS,
9
11
  scoreCandidate,
10
12
  type ScoringWeights,
13
+ weightsForContextLoad,
11
14
  } from "./scoring.js";
12
15
  import type { MemoryNode } from "./types.js";
13
16
 
@@ -546,3 +549,186 @@ describe("scoreCandidate", () => {
546
549
  expect(result.scoreBreakdown.effectiveSignificance).toBe(0.7);
547
550
  });
548
551
  });
552
+
553
+ // ---------------------------------------------------------------------------
554
+ // weightsForContextLoad + PROCEDURAL_WEIGHTS
555
+ // ---------------------------------------------------------------------------
556
+
557
+ describe("weightsForContextLoad", () => {
558
+ test("returns PROCEDURAL_WEIGHTS for procedural nodes", () => {
559
+ const node = makeNode({ type: "procedural" });
560
+ expect(weightsForContextLoad(node)).toBe(PROCEDURAL_WEIGHTS);
561
+ });
562
+
563
+ test("returns DEFAULT_WEIGHTS for episodic nodes", () => {
564
+ const node = makeNode({ type: "episodic" });
565
+ expect(weightsForContextLoad(node)).toBe(DEFAULT_WEIGHTS);
566
+ });
567
+
568
+ test("returns DEFAULT_WEIGHTS for semantic/emotional/prospective/behavioral/narrative/shared nodes", () => {
569
+ for (const type of [
570
+ "semantic",
571
+ "emotional",
572
+ "prospective",
573
+ "behavioral",
574
+ "narrative",
575
+ "shared",
576
+ ] as const) {
577
+ const node = makeNode({ type });
578
+ expect(weightsForContextLoad(node)).toBe(DEFAULT_WEIGHTS);
579
+ }
580
+ });
581
+ });
582
+
583
+ describe("PROCEDURAL_WEIGHTS", () => {
584
+ test("weights sum to 1.0", () => {
585
+ const sum =
586
+ PROCEDURAL_WEIGHTS.semanticSimilarity +
587
+ PROCEDURAL_WEIGHTS.effectiveSignificance +
588
+ PROCEDURAL_WEIGHTS.emotionalIntensity +
589
+ PROCEDURAL_WEIGHTS.temporalBoost +
590
+ PROCEDURAL_WEIGHTS.recencyBoost +
591
+ PROCEDURAL_WEIGHTS.triggerBoost +
592
+ PROCEDURAL_WEIGHTS.activationBoost;
593
+ expect(sum).toBeCloseTo(1.0, 5);
594
+ });
595
+
596
+ test("zeroes out emotionalIntensity and temporalBoost", () => {
597
+ // Procedural memories have no emotional charge and no time-of-day pattern
598
+ // by nature — grading on these signals is just dead weight.
599
+ expect(PROCEDURAL_WEIGHTS.emotionalIntensity).toBe(0);
600
+ expect(PROCEDURAL_WEIGHTS.temporalBoost).toBe(0);
601
+ });
602
+
603
+ test("weights semanticSimilarity and effectiveSignificance more heavily than DEFAULT_WEIGHTS", () => {
604
+ expect(PROCEDURAL_WEIGHTS.semanticSimilarity).toBeGreaterThan(
605
+ DEFAULT_WEIGHTS.semanticSimilarity,
606
+ );
607
+ expect(PROCEDURAL_WEIGHTS.effectiveSignificance).toBeGreaterThan(
608
+ DEFAULT_WEIGHTS.effectiveSignificance,
609
+ );
610
+ });
611
+
612
+ test("procedural node outscores otherwise-identical episodic node under type-aware weights", () => {
613
+ // A procedural memory with zero emotional charge, zero recency, zero
614
+ // trigger — all dead signals — should still surface under PROCEDURAL_WEIGHTS
615
+ // thanks to semantic relevance and significance. Under DEFAULT_WEIGHTS the
616
+ // same signals would leave ~45% of the budget dead.
617
+ const proceduralNode = makeNode({ type: "procedural" });
618
+ const episodicNode = makeNode({ id: "node-2", type: "episodic" });
619
+
620
+ // Components a procedural memory typically carries: semantic hit + stable
621
+ // significance, but no emotional charge, no recency, no trigger.
622
+ const proceduralComponents = {
623
+ semanticSimilarity: 0.8,
624
+ effectiveSignificance: 0.7,
625
+ emotionalIntensity: 0,
626
+ temporalBoost: 0.5, // neutral
627
+ recencyBoost: 0,
628
+ triggerBoost: 0,
629
+ activationBoost: 0,
630
+ };
631
+
632
+ const proceduralScore = scoreCandidate(
633
+ proceduralNode,
634
+ proceduralComponents,
635
+ weightsForContextLoad(proceduralNode),
636
+ ).score;
637
+ const episodicScore = scoreCandidate(
638
+ episodicNode,
639
+ proceduralComponents,
640
+ weightsForContextLoad(episodicNode),
641
+ ).score;
642
+
643
+ expect(proceduralScore).toBeGreaterThan(episodicScore);
644
+ });
645
+
646
+ test("episodic node with full signal still outscores procedural with only semantic signal", () => {
647
+ // The change must NOT break episodic retrieval: an episodic memory with
648
+ // emotional charge + recency + moderate significance should still outrank
649
+ // a procedural memory that only has semantic relevance.
650
+ const episodicNode = makeNode({ type: "episodic" });
651
+ const proceduralNode = makeNode({ id: "node-2", type: "procedural" });
652
+
653
+ const episodicComponents = {
654
+ semanticSimilarity: 0.5,
655
+ effectiveSignificance: 0.6,
656
+ emotionalIntensity: 0.7,
657
+ temporalBoost: 0.6,
658
+ recencyBoost: 0.9,
659
+ triggerBoost: 0.4,
660
+ activationBoost: 0.3,
661
+ };
662
+
663
+ const proceduralComponents = {
664
+ semanticSimilarity: 0.5,
665
+ effectiveSignificance: 0.3,
666
+ emotionalIntensity: 0,
667
+ temporalBoost: 0.5,
668
+ recencyBoost: 0,
669
+ triggerBoost: 0,
670
+ activationBoost: 0,
671
+ };
672
+
673
+ const episodicScore = scoreCandidate(
674
+ episodicNode,
675
+ episodicComponents,
676
+ weightsForContextLoad(episodicNode),
677
+ ).score;
678
+ const proceduralScore = scoreCandidate(
679
+ proceduralNode,
680
+ proceduralComponents,
681
+ weightsForContextLoad(proceduralNode),
682
+ ).score;
683
+
684
+ expect(episodicScore).toBeGreaterThan(proceduralScore);
685
+ });
686
+ });
687
+
688
+ // ---------------------------------------------------------------------------
689
+ // Regression: DEFAULT_WEIGHTS behavior unchanged
690
+ // ---------------------------------------------------------------------------
691
+
692
+ describe("DEFAULT_WEIGHTS (regression)", () => {
693
+ test("weights sum to 1.0", () => {
694
+ const sum =
695
+ DEFAULT_WEIGHTS.semanticSimilarity +
696
+ DEFAULT_WEIGHTS.effectiveSignificance +
697
+ DEFAULT_WEIGHTS.emotionalIntensity +
698
+ DEFAULT_WEIGHTS.temporalBoost +
699
+ DEFAULT_WEIGHTS.recencyBoost +
700
+ DEFAULT_WEIGHTS.triggerBoost +
701
+ DEFAULT_WEIGHTS.activationBoost;
702
+ expect(sum).toBeCloseTo(1.0, 5);
703
+ });
704
+
705
+ test("preserves exact weight values", () => {
706
+ expect(DEFAULT_WEIGHTS).toEqual({
707
+ semanticSimilarity: 0.25,
708
+ effectiveSignificance: 0.15,
709
+ emotionalIntensity: 0.15,
710
+ temporalBoost: 0.05,
711
+ recencyBoost: 0.15,
712
+ triggerBoost: 0.15,
713
+ activationBoost: 0.1,
714
+ });
715
+ });
716
+
717
+ test("scoreCandidate without weights argument uses DEFAULT_WEIGHTS", () => {
718
+ // Backwards-compat: existing callers that pass no weights argument should
719
+ // continue to get DEFAULT_WEIGHTS scoring.
720
+ const node = makeNode({ type: "episodic" });
721
+ const components = {
722
+ semanticSimilarity: 1.0,
723
+ effectiveSignificance: 1.0,
724
+ emotionalIntensity: 1.0,
725
+ temporalBoost: 1.0,
726
+ recencyBoost: 1.0,
727
+ triggerBoost: 1.0,
728
+ activationBoost: 1.0,
729
+ };
730
+ const implicit = scoreCandidate(node, components).score;
731
+ const explicit = scoreCandidate(node, components, DEFAULT_WEIGHTS).score;
732
+ expect(implicit).toBe(explicit);
733
+ });
734
+ });
@@ -166,7 +166,7 @@ export interface ScoringWeights {
166
166
  }
167
167
 
168
168
  /** Weights for context-load (conversation start): balanced across all signals. */
169
- const DEFAULT_WEIGHTS: ScoringWeights = {
169
+ export const DEFAULT_WEIGHTS: ScoringWeights = {
170
170
  semanticSimilarity: 0.25,
171
171
  effectiveSignificance: 0.15,
172
172
  emotionalIntensity: 0.15,
@@ -176,6 +176,28 @@ const DEFAULT_WEIGHTS: ScoringWeights = {
176
176
  activationBoost: 0.1,
177
177
  };
178
178
 
179
+ /**
180
+ * Weights for context-load of procedural memories (learned skills, how-to).
181
+ * Procedural memories have no emotional charge, no time-of-day pattern, and
182
+ * are often old-but-stable (a workaround learned months ago stays useful).
183
+ * Grading them on DEFAULT_WEIGHTS wastes ~45% of the budget on signals that
184
+ * are structurally ~0 for procedurals, causing them to lose out to episodic
185
+ * and emotional memories that simply have more signals lit up.
186
+ *
187
+ * This redistributes the dead weight onto semantic similarity and
188
+ * significance — the signals that actually differentiate useful procedurals
189
+ * from stale ones.
190
+ */
191
+ export const PROCEDURAL_WEIGHTS: ScoringWeights = {
192
+ semanticSimilarity: 0.45,
193
+ effectiveSignificance: 0.25,
194
+ emotionalIntensity: 0.0,
195
+ temporalBoost: 0.0,
196
+ recencyBoost: 0.05,
197
+ triggerBoost: 0.1,
198
+ activationBoost: 0.15,
199
+ };
200
+
179
201
  /**
180
202
  * Weights for per-turn injection: heavily biased toward semantic similarity.
181
203
  * Per-turn injections should only surface memories directly relevant to
@@ -191,6 +213,14 @@ export const PER_TURN_WEIGHTS: ScoringWeights = {
191
213
  activationBoost: 0.05,
192
214
  };
193
215
 
216
+ /**
217
+ * Pick the appropriate context-load weights for a node based on its type.
218
+ * Procedural nodes use PROCEDURAL_WEIGHTS; everything else uses DEFAULT_WEIGHTS.
219
+ */
220
+ export function weightsForContextLoad(node: MemoryNode): ScoringWeights {
221
+ return node.type === "procedural" ? PROCEDURAL_WEIGHTS : DEFAULT_WEIGHTS;
222
+ }
223
+
194
224
  /**
195
225
  * Compute the final retrieval score for a candidate node.
196
226
  * All components should be in [0, 1] range before weighting.
@@ -82,7 +82,7 @@ export const graphRecallDefinition: ToolDefinition = {
82
82
  export const graphRememberDefinition: ToolDefinition = {
83
83
  name: "remember",
84
84
  description:
85
- "Save a fact to your knowledge base. Call this AGGRESSIVELY — every preference, location, name, date, habit, opinion, health detail, plan, relationship fact, routine, or commitment. The bar is: if you wouldn't want to ask about it again, remember it. Examples: 'Prefers UberEats over DoorDash', 'Lives in NYC, from Texas', 'Takes 45mg nicotine daily, tapering', 'Girlfriend Yen is in Texas', 'Watches vampire show Saturday nights', 'NYU Summit April 10-11'. Call this multiple times per conversation — it's cheap (one line appended to a file). Don't wait until the end. Don't batch. Every new fact, immediately. Remembering too much is infinitely better than forgetting something that mattered.",
85
+ "Save a fact to your knowledge base. Call this AGGRESSIVELY — capture anything concrete about their life: preferences, locations, names, dates, habits, opinions, health details, plans, relationship facts, routines, commitments. Default to remembering; only skip obvious noise (small talk, hypotheticals, things they're just musing about). Don't judge importance filing decides that later. Examples: 'Prefers UberEats over DoorDash', 'Lives in NYC, from Texas', 'Takes 45mg nicotine daily, tapering', 'Girlfriend Yen is in Texas', 'Watches vampire show Saturday nights', 'NYU Summit April 10-11'. Call this multiple times per conversation — it's cheap (one line appended to a file). Don't wait until the end. Don't batch. Every new fact, immediately. Remembering too much is infinitely better than forgetting something that mattered. CORRECTIONS are the highest priority — when the user corrects a fact you had wrong, `remember` the correction immediately. The wrong version is already propagated in your prior turns and memory graph; skipping a correction means future-you keeps operating on the old value. Never skip a correction even if you'd skip the equivalent fresh fact.",
86
86
  input_schema: {
87
87
  type: "object",
88
88
  properties: {
@@ -25,6 +25,11 @@ export interface ConversationGroupRow {
25
25
 
26
26
  export function listGroups(): ConversationGroupRow[] {
27
27
  ensureGroupMigration();
28
+ // Migration markers are stored as rows in conversation_groups with a leading
29
+ // underscore (e.g. `_backfill_complete`). System groups use the `system:`
30
+ // prefix and custom groups use UUIDs, so no legitimate group id starts with
31
+ // `_`. GLOB treats `_` as literal (unlike LIKE), so `_*` matches any id
32
+ // whose first character is an underscore.
28
33
  const rows = rawAll<{
29
34
  id: string;
30
35
  name: string;
@@ -33,7 +38,7 @@ export function listGroups(): ConversationGroupRow[] {
33
38
  created_at: number;
34
39
  updated_at: number;
35
40
  }>(
36
- "SELECT id, name, sort_position, is_system_group, created_at, updated_at FROM conversation_groups WHERE id NOT IN ('_backfill_complete', '_backfill_all_complete', '_sort_shift_complete') ORDER BY sort_position ASC",
41
+ "SELECT id, name, sort_position, is_system_group, created_at, updated_at FROM conversation_groups WHERE id NOT GLOB '_*' ORDER BY sort_position ASC",
37
42
  );
38
43
  return rows.map((r) => ({
39
44
  id: r.id,
@@ -1,10 +1,13 @@
1
1
  import { createHash } from "crypto";
2
2
  import { desc, eq } from "drizzle-orm";
3
3
 
4
+ import { isAssistantFeatureFlagEnabled } from "../config/assistant-feature-flags.js";
4
5
  import { getConfig } from "../config/loader.js";
5
6
  import type { MemoryConfig } from "../config/types.js";
6
7
  import type { TrustClass } from "../runtime/actor-trust-resolver.js";
7
8
  import { getLogger } from "../util/logger.js";
9
+ import { enqueueAutoAnalysisIfEnabled } from "./auto-analysis-enqueue.js";
10
+ import { isAutoAnalysisConversation } from "./auto-analysis-guard.js";
8
11
  import { getMemoryCheckpoint, setMemoryCheckpoint } from "./checkpoints.js";
9
12
  import { getDb } from "./db.js";
10
13
  import { selectedBackendSupportsMultimodal } from "./embedding-backend.js";
@@ -154,6 +157,7 @@ export async function indexMessageNow(
154
157
  // ── Batch extraction tracking ──────────────────────────────────────
155
158
  // Instead of per-message extraction, track pending unextracted messages
156
159
  // and trigger batch extraction when the threshold is reached or after idle.
160
+ const isAutoAnalysisSource = isAutoAnalysisConversation(input.conversationId);
157
161
  if (
158
162
  shouldExtract &&
159
163
  isTrustedActor &&
@@ -163,26 +167,89 @@ export async function indexMessageNow(
163
167
  const batchSize = config.extraction.batchSize ?? 10;
164
168
  const idleTimeoutMs = config.extraction.idleTimeoutMs ?? 300_000;
165
169
 
166
- // ── Graph extraction ────────────────────────────────────────────
167
- const graphPendingKey = `graph_extract:${input.conversationId}:pending_count`;
168
- const graphCurrentVal = getMemoryCheckpoint(graphPendingKey);
169
- const graphPendingCount =
170
- (graphCurrentVal ? parseInt(graphCurrentVal, 10) : 0) + 1;
171
- setMemoryCheckpoint(graphPendingKey, String(graphPendingCount));
170
+ // Recursion guard: skip graph extraction + auto-analysis enqueues
171
+ // when the source conversation is itself an auto-analysis
172
+ // conversation. The analysis agent writes memory directly via tools,
173
+ // so extracting from its reflective musings would double-count and
174
+ // analyzing its own output would loop indefinitely.
175
+ // Summaries still run — they feed the graph retrieval pipeline and
176
+ // are not recursion-prone.
177
+ if (!isAutoAnalysisSource) {
178
+ // ── Graph extraction ────────────────────────────────────────────
179
+ const graphPendingKey = `graph_extract:${input.conversationId}:pending_count`;
180
+ const graphCurrentVal = getMemoryCheckpoint(graphPendingKey);
181
+ const graphPendingCount =
182
+ (graphCurrentVal ? parseInt(graphCurrentVal, 10) : 0) + 1;
183
+ setMemoryCheckpoint(graphPendingKey, String(graphPendingCount));
172
184
 
173
- if (graphPendingCount >= batchSize) {
174
- enqueueMemoryJob("graph_extract", {
185
+ const graphBatchFired = graphPendingCount >= batchSize;
186
+ if (graphBatchFired) {
187
+ setMemoryCheckpoint(graphPendingKey, "0");
188
+ }
189
+
190
+ // Single pending `graph_extract` row per conversation. If the
191
+ // batch threshold just fired, pull `runAfter` back to now so the
192
+ // job runs immediately; otherwise debounce by the idle timeout.
193
+ // Routing both paths through `upsertDebouncedJob` ensures the
194
+ // row's `runAfter` reflects whichever trigger ran last, so a
195
+ // batch crossing always takes effect immediately.
196
+ upsertDebouncedJob(
197
+ "graph_extract",
198
+ {
199
+ conversationId: input.conversationId,
200
+ scopeId: input.scopeId ?? "default",
201
+ },
202
+ graphBatchFired ? Date.now() : Date.now() + idleTimeoutMs,
203
+ );
204
+
205
+ // ── Auto-analysis triggers ─────────────────────────────────────
206
+ // Both triggers route through `upsertDebouncedJob` in the helper,
207
+ // so a single pending row is shared. Order matters: the idle
208
+ // upsert runs first (pushing `runAfter` into the future); the
209
+ // batch trigger runs last so a threshold crossing pulls
210
+ // `runAfter` back to "now" and overrides the idle debounce.
211
+ enqueueAutoAnalysisIfEnabled({
175
212
  conversationId: input.conversationId,
176
- scopeId: input.scopeId ?? "default",
213
+ trigger: "idle",
177
214
  });
178
- setMemoryCheckpoint(graphPendingKey, "0");
179
- }
180
215
 
181
- upsertDebouncedJob(
182
- "graph_extract",
183
- { conversationId: input.conversationId },
184
- Date.now() + idleTimeoutMs,
185
- );
216
+ // Auto-analysis cadence is tracked by its own pending-count
217
+ // checkpoint so it fires at `analysis.batchSize` (default 30).
218
+ // Gated behind the `auto-analyze` feature flag so the counter
219
+ // does not accumulate stale counts while the flag is off — if it
220
+ // did, flipping the flag on would trigger an immediate batch from
221
+ // messages buffered during the disabled period. Reading config
222
+ // here is best-effort: if it fails we skip the batch trigger
223
+ // (the idle-debounced enqueue above still runs).
224
+ let analysisConfig: ReturnType<typeof getConfig> | null = null;
225
+ try {
226
+ analysisConfig = getConfig();
227
+ } catch (err) {
228
+ log.debug(
229
+ { err, conversationId: input.conversationId },
230
+ "Skipping auto-analysis batch trigger: failed to load config",
231
+ );
232
+ }
233
+ if (
234
+ analysisConfig != null &&
235
+ isAssistantFeatureFlagEnabled("auto-analyze", analysisConfig)
236
+ ) {
237
+ const analysisBatchSize = analysisConfig.analysis.batchSize;
238
+ const analysisPendingKey = `conversation_analyze:${input.conversationId}:pending_count`;
239
+ const analysisCurrentVal = getMemoryCheckpoint(analysisPendingKey);
240
+ const analysisPendingCount =
241
+ (analysisCurrentVal ? parseInt(analysisCurrentVal, 10) : 0) + 1;
242
+ setMemoryCheckpoint(analysisPendingKey, String(analysisPendingCount));
243
+
244
+ if (analysisPendingCount >= analysisBatchSize) {
245
+ setMemoryCheckpoint(analysisPendingKey, "0");
246
+ enqueueAutoAnalysisIfEnabled({
247
+ conversationId: input.conversationId,
248
+ trigger: "batch",
249
+ });
250
+ }
251
+ }
252
+ }
186
253
 
187
254
  // ── Conversation summarization (independent of extraction) ────────
188
255
  // Summaries feed the graph retrieval pipeline via fetchRecentSummaries().
@@ -228,6 +295,18 @@ export async function indexMessageNow(
228
295
  );
229
296
  }
230
297
 
298
+ if (
299
+ isAutoAnalysisSource &&
300
+ shouldExtract &&
301
+ isTrustedActor &&
302
+ !input.automated &&
303
+ config.extraction.useLLM
304
+ ) {
305
+ log.debug(
306
+ "Skipping graph_extract + auto-analysis enqueues: source is an auto-analysis conversation",
307
+ );
308
+ }
309
+
231
310
  const storedSegments = segments.length - skippedShortSegments;
232
311
  const enqueuedJobs = storedSegments - skippedEmbedJobs + mediaBlocks.length;
233
312
  return {
@@ -17,15 +17,18 @@ export function pruneOldLlmRequestLogsJob(
17
17
  job: MemoryJob,
18
18
  config: AssistantConfig,
19
19
  ): void {
20
+ const rawRetention = job.payload.retentionMs;
20
21
  const retentionMs =
21
- typeof job.payload.retentionMs === "number" &&
22
- Number.isFinite(job.payload.retentionMs) &&
23
- job.payload.retentionMs >= 0
24
- ? job.payload.retentionMs
25
- : config.memory.cleanup.llmRequestLogRetentionMs;
26
-
27
- // 0 means disabled
28
- if (retentionMs === 0) return;
22
+ rawRetention === null
23
+ ? null
24
+ : typeof rawRetention === "number" &&
25
+ Number.isFinite(rawRetention) &&
26
+ rawRetention >= 0
27
+ ? rawRetention
28
+ : config.memory.cleanup.llmRequestLogRetentionMs;
29
+
30
+ // null means "keep forever" — skip pruning entirely
31
+ if (retentionMs === null || retentionMs === undefined) return;
29
32
 
30
33
  const cutoffMs = Date.now() - retentionMs;
31
34
 
@@ -197,7 +197,7 @@ async function generateStarters(scopeId: string): Promise<GeneratedStarter[]> {
197
197
  })}`;
198
198
 
199
199
  // Truncate identity context to prevent oversized prompts when SOUL.md /
200
- // IDENTITY.md / USER.md are large.
200
+ // IDENTITY.md / users/<slug>.md are large.
201
201
  const rawIdentityContext = buildCoreIdentityContext({
202
202
  userPersona: resolveGuardianPersona(),
203
203
  });
@@ -205,11 +205,11 @@ async function generateStarters(scopeId: string): Promise<GeneratedStarter[]> {
205
205
  ? truncate(rawIdentityContext, 2000, "\n…[truncated]")
206
206
  : null;
207
207
 
208
- const systemPrompt = `You are generating 4 conversation starters for a personal assistant app. These appear as clickable chips on the empty conversation page — the first thing the user sees when they open the app. Clicking a chip sends its prompt as a message from the user.
208
+ const systemPrompt = `You are generating conversation starters for a personal assistant app. These appear as clickable chips on the empty conversation page — the first thing the user sees when they open the app. Clicking a chip sends its prompt as a message from the user.
209
209
 
210
210
  ${timeContext}
211
211
 
212
- Your goal: suggest the 4 most useful things this person could ask you to do right now.
212
+ Your goal: suggest the most useful things this person could ask you to do right now. Produce 8 candidates, ranked best-first; only the top 4 will be shown.
213
213
 
214
214
  ${
215
215
  identityContext
@@ -223,7 +223,7 @@ ${skills}
223
223
 
224
224
  ## Selection
225
225
 
226
- Generate exactly 4 starters, ranked #1 (best) to #4.
226
+ Generate exactly 8 starters, ranked #1 (best) to #8. The top 4 will be shown; the rest are fallbacks in case any fail downstream validation (e.g. label too long). Put real effort into every slot — any of them may end up displayed.
227
227
 
228
228
  Start from the user's situation, not from the skill list. Ask yourself:
229
229
  - What is this person likely dealing with right now (given the day/time and their context)?
@@ -250,7 +250,7 @@ Favor what is live over what is merely true. Recent changes matter more than old
250
250
  ## Output format
251
251
 
252
252
  Each starter has:
253
- - label: 3-6 words, max 40 chars, starts with a verb. Written in the user's voice — something they'd want to do, not something the assistant is offering.
253
+ - label: 3-6 words, max 40 chars, starts with a verb. Written in the user's voice — something they'd want to do, not something the assistant is offering. MUST be a grammatically complete phrase: if it uses an adjective ("quarterly", "weekly"), include the noun it modifies ("quarterly review", "weekly sync"). Never end on a dangling modifier, preposition, or trailing "the/my/a". Prefer completeness over tightness when you have room under 40 chars.
254
254
  - prompt: 1-2 natural sentences, as the user would actually say them.
255
255
  - category: one of ${CONVERSATION_STARTER_CATEGORIES.join(", ")}
256
256
 
@@ -258,9 +258,9 @@ Each starter has:
258
258
 
259
259
  **Voice**: The user clicks these chips to send a message. Every label must read as something the user is asking to do, never something the assistant is saying to the user.
260
260
 
261
- **Coherence**: The 4 starters should feel like one set — similar abstraction level, no jarring mix of mundane chores and life strategy.
261
+ **Coherence**: The top 4 starters should feel like one set — similar abstraction level, no jarring mix of mundane chores and life strategy. The remaining 4 fallbacks may branch into adjacent topics.
262
262
 
263
- **Diversity**: Each chip covers a distinct topic. Never two chips about the same tool, project, or theme. Four topics, four chips.
263
+ **Diversity**: Each chip covers a distinct topic. Never two chips about the same tool, project, or theme. Across all 8 starters, avoid repeating topics.
264
264
 
265
265
  **No setup chips**: Never include a chip whose primary meaning is configuration or "set up X for Y" unless it solves an urgent pain the user is actively feeling. Prefer the outcome over the mechanism.
266
266
 
@@ -276,7 +276,12 @@ Bad → Good (ticket-speak → natural):
276
276
 
277
277
  Bad → Good (assistant voice → user voice):
278
278
  - "You've got a busy week ahead" → "Plan my week ahead"
279
- - "Let me check your calendar" → "Check my Thursday schedule"`;
279
+ - "Let me check your calendar" → "Check my Thursday schedule"
280
+
281
+ Bad → Good (incomplete phrase → complete):
282
+ - "Prep for Friday's quarterly" → "Prep for Friday's quarterly review"
283
+ - "Finish the onboarding" → "Finish the onboarding guide"
284
+ - "Draft the release" → "Draft the release notes"`;
280
285
 
281
286
  const { signal, cleanup } = createTimeout(20000);
282
287
  try {
@@ -326,7 +331,7 @@ Bad → Good (assistant voice → user voice):
326
331
  {
327
332
  config: {
328
333
  modelIntent: "quality-optimized",
329
- max_tokens: 1024,
334
+ max_tokens: 2048,
330
335
  tool_choice: {
331
336
  type: "tool" as const,
332
337
  name: "store_conversation_starters",
@@ -356,12 +361,13 @@ Bad → Good (assistant voice → user voice):
356
361
  (s) =>
357
362
  typeof s.label === "string" &&
358
363
  s.label.length > 0 &&
364
+ s.label.length <= 40 &&
359
365
  typeof s.prompt === "string" &&
360
366
  s.prompt.length > 0
361
367
  )
362
368
  .slice(0, 4)
363
369
  .map((s) => ({
364
- label: truncate(s.label, 40, ""),
370
+ label: s.label,
365
371
  prompt: truncate(s.prompt, 500, ""),
366
372
  category:
367
373
  typeof s.category === "string" &&
@@ -18,6 +18,7 @@ export type MemoryJobType =
18
18
  | "prune_old_conversations"
19
19
  | "prune_old_llm_request_logs"
20
20
  | "build_conversation_summary"
21
+ | "conversation_analyze"
21
22
  | "backfill"
22
23
  | "rebuild_index"
23
24
  | "delete_qdrant_vectors"
@@ -89,15 +90,19 @@ export function enqueueMemoryJob(
89
90
 
90
91
  /**
91
92
  * Upsert a debounced job: if a pending job of the same type and conversation
92
- * already exists, push its `runAfter` forward instead of creating a duplicate.
93
- * This prevents rapid message indexing from spawning redundant jobs.
93
+ * already exists, merge the new payload into the existing row and update
94
+ * `runAfter` instead of creating a duplicate. This prevents rapid message
95
+ * indexing from spawning redundant jobs while ensuring the latest payload
96
+ * keys (e.g. `scopeId`) reach the handler — including on upgraded instances
97
+ * where the existing pending row was enqueued by an older build that did
98
+ * not write those keys.
94
99
  *
95
100
  * Pass a `dbOverride` (transaction handle) to make this call atomic with
96
101
  * surrounding writes.
97
102
  */
98
103
  export function upsertDebouncedJob(
99
104
  type: MemoryJobType,
100
- payload: { conversationId: string },
105
+ payload: { conversationId: string } & Record<string, unknown>,
101
106
  runAfter: number,
102
107
  dbOverride?: Parameters<ReturnType<typeof getDb>["transaction"]>[0] extends (
103
108
  tx: infer T,
@@ -118,8 +123,19 @@ export function upsertDebouncedJob(
118
123
  )
119
124
  .get();
120
125
  if (existing) {
126
+ let existingPayload: Record<string, unknown> = {};
127
+ try {
128
+ existingPayload = JSON.parse(existing.payload) as Record<string, unknown>;
129
+ } catch {
130
+ existingPayload = {};
131
+ }
132
+ const mergedPayload = { ...existingPayload, ...payload };
121
133
  db.update(memoryJobs)
122
- .set({ runAfter, updatedAt: Date.now() })
134
+ .set({
135
+ payload: JSON.stringify(mergedPayload),
136
+ runAfter,
137
+ updatedAt: Date.now(),
138
+ })
123
139
  .where(eq(memoryJobs.id, existing.id))
124
140
  .run();
125
141
  } else {
@@ -127,6 +143,50 @@ export function upsertDebouncedJob(
127
143
  }
128
144
  }
129
145
 
146
+ /**
147
+ * Upsert a pending `conversation_analyze` job keyed by both
148
+ * `conversationId` and `triggerGroup`. Immediate triggers (batch,
149
+ * compaction) and debounced triggers (idle, lifecycle) live in separate
150
+ * rows so an idle enqueue cannot push an already-scheduled immediate
151
+ * row's `runAfter` into the future (and vice versa). Each group still
152
+ * coalesces within itself: two batch crossings, or two idle triggers,
153
+ * collapse to a single pending row.
154
+ */
155
+ export function upsertAutoAnalysisJob(
156
+ payload: {
157
+ conversationId: string;
158
+ triggerGroup: "immediate" | "debounced";
159
+ },
160
+ runAfter: number,
161
+ dbOverride?: Parameters<ReturnType<typeof getDb>["transaction"]>[0] extends (
162
+ tx: infer T,
163
+ ) => unknown
164
+ ? T
165
+ : never,
166
+ ): void {
167
+ const db = dbOverride ?? getDb();
168
+ const existing = db
169
+ .select()
170
+ .from(memoryJobs)
171
+ .where(
172
+ and(
173
+ eq(memoryJobs.type, "conversation_analyze"),
174
+ eq(memoryJobs.status, "pending"),
175
+ sql`json_extract(${memoryJobs.payload}, '$.conversationId') = ${payload.conversationId}`,
176
+ sql`json_extract(${memoryJobs.payload}, '$.triggerGroup') = ${payload.triggerGroup}`,
177
+ ),
178
+ )
179
+ .get();
180
+ if (existing) {
181
+ db.update(memoryJobs)
182
+ .set({ runAfter, updatedAt: Date.now() })
183
+ .where(eq(memoryJobs.id, existing.id))
184
+ .run();
185
+ } else {
186
+ enqueueMemoryJob("conversation_analyze", payload, runAfter, dbOverride);
187
+ }
188
+ }
189
+
130
190
  /**
131
191
  * Check whether a pending or running job of the given type already exists.
132
192
  * Used to prevent duplicate enqueues for long-running maintenance jobs.