@vellumai/assistant 0.5.13 → 0.5.15

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 (425) hide show
  1. package/.env.example +1 -6
  2. package/AGENTS.md +4 -0
  3. package/ARCHITECTURE.md +0 -1
  4. package/bunfig.toml +1 -0
  5. package/docs/architecture/memory.md +3 -3
  6. package/openapi.yaml +127 -22
  7. package/package.json +1 -1
  8. package/src/__tests__/access-request-decision.test.ts +2 -32
  9. package/src/__tests__/actor-token-service.test.ts +1 -31
  10. package/src/__tests__/anthropic-provider.test.ts +53 -40
  11. package/src/__tests__/app-git-history.test.ts +9 -17
  12. package/src/__tests__/app-git-service.test.ts +14 -20
  13. package/src/__tests__/app-store-dir-names.test.ts +10 -20
  14. package/src/__tests__/approval-cascade.test.ts +2 -19
  15. package/src/__tests__/approval-primitive.test.ts +2 -27
  16. package/src/__tests__/approval-routes-http.test.ts +2 -30
  17. package/src/__tests__/assistant-events-sse-hardening.test.ts +2 -28
  18. package/src/__tests__/assistant-feature-flags-integration.test.ts +2 -45
  19. package/src/__tests__/attachments-store.test.ts +5 -32
  20. package/src/__tests__/audit-log-rotation.test.ts +5 -36
  21. package/src/__tests__/avatar-e2e.test.ts +1 -9
  22. package/src/__tests__/avatar-generator.test.ts +1 -7
  23. package/src/__tests__/browser-fill-credential.test.ts +0 -4
  24. package/src/__tests__/browser-manager.test.ts +0 -6
  25. package/src/__tests__/call-controller.test.ts +1 -22
  26. package/src/__tests__/call-conversation-messages.test.ts +0 -21
  27. package/src/__tests__/call-domain.test.ts +0 -25
  28. package/src/__tests__/call-pointer-messages.test.ts +0 -21
  29. package/src/__tests__/call-recovery.test.ts +0 -22
  30. package/src/__tests__/call-routes-http.test.ts +0 -24
  31. package/src/__tests__/call-store.test.ts +0 -21
  32. package/src/__tests__/cancel-resolves-conversation-key.test.ts +0 -24
  33. package/src/__tests__/canonical-guardian-store.test.ts +48 -21
  34. package/src/__tests__/channel-approval-routes.test.ts +6 -26
  35. package/src/__tests__/channel-approvals.test.ts +1 -38
  36. package/src/__tests__/channel-delivery-store.test.ts +0 -21
  37. package/src/__tests__/channel-guardian.test.ts +0 -26
  38. package/src/__tests__/channel-reply-delivery.test.ts +5 -0
  39. package/src/__tests__/channel-retry-sweep.test.ts +0 -21
  40. package/src/__tests__/checker.test.ts +26 -61
  41. package/src/__tests__/clawhub.test.ts +9 -25
  42. package/src/__tests__/cli-command-risk-guard.test.ts +0 -18
  43. package/src/__tests__/config-loader-backfill.test.ts +9 -28
  44. package/src/__tests__/config-schema-cmd.test.ts +5 -25
  45. package/src/__tests__/config-schema.test.ts +21 -40
  46. package/src/__tests__/config-watcher.test.ts +4 -91
  47. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +0 -21
  48. package/src/__tests__/contacts-tools.test.ts +0 -21
  49. package/src/__tests__/context-memory-e2e.test.ts +0 -21
  50. package/src/__tests__/context-window-manager.test.ts +130 -3
  51. package/src/__tests__/conversation-abort-tool-results.test.ts +0 -4
  52. package/src/__tests__/conversation-agent-loop-overflow.test.ts +0 -4
  53. package/src/__tests__/conversation-agent-loop.test.ts +0 -4
  54. package/src/__tests__/conversation-attachments.test.ts +1 -24
  55. package/src/__tests__/conversation-attention-store.test.ts +0 -21
  56. package/src/__tests__/conversation-attention-telegram.test.ts +0 -22
  57. package/src/__tests__/conversation-clear-safety.test.ts +0 -22
  58. package/src/__tests__/conversation-confirmation-signals.test.ts +2 -21
  59. package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +0 -24
  60. package/src/__tests__/conversation-disk-view-integration.test.ts +1 -23
  61. package/src/__tests__/conversation-disk-view.test.ts +5 -27
  62. package/src/__tests__/conversation-error.test.ts +1 -1
  63. package/src/__tests__/conversation-fork-crud.test.ts +1 -33
  64. package/src/__tests__/conversation-fork-route.test.ts +0 -27
  65. package/src/__tests__/conversation-history-web-search.test.ts +23 -16
  66. package/src/__tests__/conversation-init.benchmark.test.ts +22 -43
  67. package/src/__tests__/conversation-key-store-disk-view.test.ts +8 -34
  68. package/src/__tests__/conversation-load-history-repair.test.ts +0 -4
  69. package/src/__tests__/conversation-pre-run-repair.test.ts +0 -4
  70. package/src/__tests__/conversation-provider-retry-repair.test.ts +0 -4
  71. package/src/__tests__/conversation-queue.test.ts +8 -8
  72. package/src/__tests__/conversation-routes-disk-view.test.ts +13 -51
  73. package/src/__tests__/conversation-runtime-assembly.test.ts +64 -38
  74. package/src/__tests__/conversation-slash-commands.test.ts +5 -0
  75. package/src/__tests__/conversation-slash-queue.test.ts +0 -4
  76. package/src/__tests__/conversation-slash-unknown.test.ts +0 -4
  77. package/src/__tests__/conversation-speed-override.test.ts +326 -0
  78. package/src/__tests__/conversation-starter-routes.test.ts +0 -23
  79. package/src/__tests__/conversation-store.test.ts +0 -21
  80. package/src/__tests__/conversation-unread-route.test.ts +0 -24
  81. package/src/__tests__/conversation-usage.test.ts +56 -21
  82. package/src/__tests__/conversation-wipe.test.ts +0 -21
  83. package/src/__tests__/conversation-workspace-cache-state.test.ts +0 -4
  84. package/src/__tests__/conversation-workspace-injection.test.ts +0 -4
  85. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +0 -4
  86. package/src/__tests__/credential-execution-shell-lockdown.test.ts +8 -5
  87. package/src/__tests__/credential-vault-unit.test.ts +9 -428
  88. package/src/__tests__/credentials-cli.test.ts +10 -10
  89. package/src/__tests__/daemon-assistant-events.test.ts +0 -19
  90. package/src/__tests__/date-context.test.ts +77 -97
  91. package/src/__tests__/db-conversation-fork-lineage-migration.test.ts +7 -24
  92. package/src/__tests__/db-llm-request-log-provider-migration.test.ts +29 -42
  93. package/src/__tests__/delete-managed-skill-tool.test.ts +2 -10
  94. package/src/__tests__/deterministic-verification-control-plane.test.ts +1 -26
  95. package/src/__tests__/docker-signing-key-bootstrap.test.ts +61 -15
  96. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +2 -36
  97. package/src/__tests__/email-cli.test.ts +6 -6
  98. package/src/__tests__/ephemeral-permissions.test.ts +5 -17
  99. package/src/__tests__/first-greeting.test.ts +4 -32
  100. package/src/__tests__/followup-tools.test.ts +0 -21
  101. package/src/__tests__/gateway-only-enforcement.test.ts +0 -20
  102. package/src/__tests__/guardian-action-conversation-turn.test.ts +0 -23
  103. package/src/__tests__/guardian-action-followup-executor.test.ts +0 -23
  104. package/src/__tests__/guardian-action-followup-store.test.ts +0 -21
  105. package/src/__tests__/guardian-action-grant-mint-consume.test.ts +0 -21
  106. package/src/__tests__/guardian-action-late-reply.test.ts +0 -21
  107. package/src/__tests__/guardian-action-store.test.ts +0 -21
  108. package/src/__tests__/guardian-action-sweep.test.ts +0 -21
  109. package/src/__tests__/guardian-binding-drift-heal.test.ts +0 -23
  110. package/src/__tests__/guardian-decision-primitive-canonical.test.ts +172 -22
  111. package/src/__tests__/guardian-dispatch.test.ts +0 -21
  112. package/src/__tests__/guardian-grant-minting.test.ts +0 -22
  113. package/src/__tests__/guardian-outbound-http.test.ts +0 -22
  114. package/src/__tests__/guardian-principal-id-roundtrip.test.ts +0 -23
  115. package/src/__tests__/guardian-routing-invariants.test.ts +0 -22
  116. package/src/__tests__/guardian-routing-state.test.ts +0 -22
  117. package/src/__tests__/guardian-verification-voice-binding.test.ts +0 -24
  118. package/src/__tests__/headless-browser-interactions.test.ts +0 -4
  119. package/src/__tests__/headless-browser-navigate.test.ts +0 -4
  120. package/src/__tests__/headless-browser-read-tools.test.ts +0 -4
  121. package/src/__tests__/headless-browser-snapshot.test.ts +0 -4
  122. package/src/__tests__/heartbeat-service.test.ts +99 -26
  123. package/src/__tests__/hooks-blocking.test.ts +3 -3
  124. package/src/__tests__/hooks-config.test.ts +7 -7
  125. package/src/__tests__/hooks-discovery.test.ts +3 -3
  126. package/src/__tests__/hooks-integration.test.ts +5 -5
  127. package/src/__tests__/hooks-manager.test.ts +3 -3
  128. package/src/__tests__/hooks-runner.test.ts +5 -23
  129. package/src/__tests__/hooks-settings.test.ts +3 -3
  130. package/src/__tests__/hooks-templates.test.ts +3 -3
  131. package/src/__tests__/http-conversation-lineage.test.ts +0 -27
  132. package/src/__tests__/identity-intro-cache.test.ts +0 -4
  133. package/src/__tests__/inbound-invite-redemption.test.ts +0 -22
  134. package/src/__tests__/inline-skill-load-permissions.test.ts +5 -16
  135. package/src/__tests__/intent-routing.test.ts +2 -55
  136. package/src/__tests__/invite-redemption-service.test.ts +0 -21
  137. package/src/__tests__/invite-routes-http.test.ts +0 -21
  138. package/src/__tests__/jobs-store-qdrant-breaker.test.ts +0 -17
  139. package/src/__tests__/journal-context.test.ts +8 -75
  140. package/src/__tests__/list-messages-attachments.test.ts +0 -22
  141. package/src/__tests__/llm-context-route-provider.test.ts +0 -21
  142. package/src/__tests__/llm-request-log-turn-query.test.ts +46 -28
  143. package/src/__tests__/llm-usage-store.test.ts +0 -21
  144. package/src/__tests__/log-export-workspace.test.ts +1 -1
  145. package/src/__tests__/managed-skill-lifecycle.test.ts +1 -1
  146. package/src/__tests__/managed-store.test.ts +1 -1
  147. package/src/__tests__/mcp-cli.test.ts +7 -10
  148. package/src/__tests__/memory-context-benchmark.benchmark.test.ts +0 -21
  149. package/src/__tests__/memory-jobs-worker-backoff.test.ts +0 -11
  150. package/src/__tests__/memory-lifecycle-e2e.test.ts +0 -21
  151. package/src/__tests__/memory-recall-log-store.test.ts +0 -27
  152. package/src/__tests__/memory-recall-quality.test.ts +0 -21
  153. package/src/__tests__/memory-regressions.experimental.test.ts +31 -30
  154. package/src/__tests__/memory-regressions.test.ts +282 -70
  155. package/src/__tests__/memory-retrieval.benchmark.test.ts +0 -21
  156. package/src/__tests__/memory-upsert-concurrency.test.ts +0 -21
  157. package/src/__tests__/messaging-send-tool.test.ts +201 -0
  158. package/src/__tests__/migration-cross-version-compatibility.test.ts +18 -13
  159. package/src/__tests__/migration-export-http.test.ts +7 -1
  160. package/src/__tests__/migration-import-commit-http.test.ts +16 -14
  161. package/src/__tests__/migration-import-preflight-http.test.ts +27 -44
  162. package/src/__tests__/migration-validate-http.test.ts +1 -28
  163. package/src/__tests__/native-web-search.test.ts +25 -22
  164. package/src/__tests__/non-member-access-request.test.ts +0 -22
  165. package/src/__tests__/notification-guardian-path.test.ts +0 -21
  166. package/src/__tests__/notification-schedule-dedup.test.ts +1 -25
  167. package/src/__tests__/oauth-apps-routes.test.ts +103 -2
  168. package/src/__tests__/oauth-cli.test.ts +52 -0
  169. package/src/__tests__/oauth-provider-profiles.test.ts +0 -16
  170. package/src/__tests__/oauth-provider-serializer.test.ts +232 -0
  171. package/src/__tests__/oauth-providers-routes.test.ts +257 -0
  172. package/src/__tests__/oauth-store.test.ts +0 -21
  173. package/src/__tests__/onboarding-template-contract.test.ts +2 -2
  174. package/src/__tests__/openai-provider.test.ts +261 -0
  175. package/src/__tests__/pairing-concurrent.test.ts +6 -6
  176. package/src/__tests__/pairing-routes.test.ts +7 -1
  177. package/src/__tests__/path-policy.test.ts +1 -1
  178. package/src/__tests__/platform.test.ts +64 -88
  179. package/src/__tests__/playbook-execution.test.ts +0 -21
  180. package/src/__tests__/playbook-tools.test.ts +0 -21
  181. package/src/__tests__/pricing.test.ts +100 -0
  182. package/src/__tests__/relay-server.test.ts +1 -25
  183. package/src/__tests__/runtime-attachment-metadata.test.ts +0 -24
  184. package/src/__tests__/runtime-events-sse-parity.test.ts +2 -24
  185. package/src/__tests__/runtime-events-sse.test.ts +0 -24
  186. package/src/__tests__/sandbox-diagnostics.test.ts +2 -1
  187. package/src/__tests__/scaffold-managed-skill-tool.test.ts +1 -1
  188. package/src/__tests__/schedule-store.test.ts +0 -21
  189. package/src/__tests__/schedule-tools.test.ts +0 -21
  190. package/src/__tests__/scheduler-recurrence.test.ts +0 -21
  191. package/src/__tests__/scoped-approval-grants.test.ts +0 -21
  192. package/src/__tests__/scoped-grant-security-matrix.test.ts +0 -21
  193. package/src/__tests__/secret-allowlist.test.ts +1 -1
  194. package/src/__tests__/secret-ingress-channel.test.ts +0 -5
  195. package/src/__tests__/secret-ingress-cli.test.ts +0 -6
  196. package/src/__tests__/secret-ingress-http.test.ts +0 -5
  197. package/src/__tests__/secret-ingress.test.ts +0 -5
  198. package/src/__tests__/send-endpoint-busy.test.ts +0 -24
  199. package/src/__tests__/sequence-store.test.ts +0 -21
  200. package/src/__tests__/server-history-render.test.ts +0 -24
  201. package/src/__tests__/shell-tool-proxy-mode.test.ts +0 -4
  202. package/src/__tests__/skill-load-inline-command.test.ts +9 -0
  203. package/src/__tests__/skill-load-inline-includes.test.ts +9 -0
  204. package/src/__tests__/skill-load-tool.test.ts +11 -0
  205. package/src/__tests__/skills-uninstall.test.ts +10 -8
  206. package/src/__tests__/skills.test.ts +1 -1
  207. package/src/__tests__/slack-channel-config.test.ts +1 -1
  208. package/src/__tests__/slack-inbound-verification.test.ts +0 -22
  209. package/src/__tests__/starter-bundle.test.ts +4 -1
  210. package/src/__tests__/suggestion-routes.test.ts +2 -0
  211. package/src/__tests__/system-prompt.test.ts +1 -1
  212. package/src/__tests__/terminal-tools.test.ts +1 -1
  213. package/src/__tests__/test-preload.ts +31 -0
  214. package/src/__tests__/tool-execution-abort-cleanup.test.ts +1 -1
  215. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +1 -1
  216. package/src/__tests__/tool-executor.test.ts +0 -20
  217. package/src/__tests__/tool-input-summary.test.ts +124 -0
  218. package/src/__tests__/tool-preview-lifecycle.test.ts +2 -1
  219. package/src/__tests__/trust-store.test.ts +7 -1
  220. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +1 -1
  221. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +1 -1
  222. package/src/__tests__/trusted-contact-multichannel.test.ts +1 -1
  223. package/src/__tests__/trusted-contact-verification.test.ts +1 -1
  224. package/src/__tests__/turn-boundary-resolution.test.ts +1 -1
  225. package/src/__tests__/twilio-routes.test.ts +1 -1
  226. package/src/__tests__/update-bulletin.test.ts +1 -1
  227. package/src/__tests__/vbundle-pax-and-symlink.test.ts +1 -1
  228. package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +1 -0
  229. package/src/__tests__/voice-scoped-grant-consumer.test.ts +1 -1
  230. package/src/__tests__/voice-session-bridge.test.ts +1 -1
  231. package/src/__tests__/workspace-migration-009-backfill-conversation-disk-view.test.ts +4 -4
  232. package/src/__tests__/workspace-migration-013-repair-conversation-disk-view.test.ts +1 -1
  233. package/src/__tests__/workspace-migration-down-functions.test.ts +15 -3
  234. package/src/__tests__/workspace-migration-seed-device-id.test.ts +40 -4
  235. package/src/agent/loop.ts +6 -9
  236. package/src/approvals/guardian-decision-primitive.ts +46 -18
  237. package/src/approvals/guardian-request-resolvers.ts +19 -2
  238. package/src/calls/active-call-lease.ts +2 -2
  239. package/src/cli/AGENTS.md +1 -1
  240. package/src/cli/commands/doctor.ts +9 -9
  241. package/src/cli/commands/memory.ts +142 -0
  242. package/src/cli/commands/oauth/__tests__/connect.test.ts +13 -11
  243. package/src/cli/commands/oauth/__tests__/ping.test.ts +1 -1
  244. package/src/cli/commands/oauth/connect.ts +13 -12
  245. package/src/cli/commands/oauth/index.ts +1 -1
  246. package/src/cli/commands/oauth/providers.ts +47 -62
  247. package/src/cli/commands/platform/__tests__/connect.test.ts +72 -46
  248. package/src/cli/commands/platform/__tests__/disconnect.test.ts +54 -1
  249. package/src/cli/commands/platform/__tests__/status.test.ts +36 -0
  250. package/src/cli/commands/platform/connect.ts +17 -7
  251. package/src/cli/commands/platform/disconnect.ts +28 -3
  252. package/src/cli/commands/platform/index.ts +3 -3
  253. package/src/cli.ts +1 -299
  254. package/src/config/assistant-feature-flags.ts +23 -15
  255. package/src/config/bundled-skills/app-builder/TOOLS.json +16 -0
  256. package/src/config/bundled-skills/app-builder/tools/app-create.ts +4 -0
  257. package/src/config/bundled-skills/app-builder/tools/app-delete.ts +5 -1
  258. package/src/config/bundled-skills/app-builder/tools/app-generate-icon.ts +9 -1
  259. package/src/config/bundled-skills/app-builder/tools/app-refresh.ts +5 -1
  260. package/src/config/bundled-skills/contacts/TOOLS.json +8 -0
  261. package/src/config/bundled-skills/contacts/tools/contact-search.ts +10 -1
  262. package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +16 -2
  263. package/src/config/bundled-skills/media-processing/tools/ingest-media.ts +1 -0
  264. package/src/config/bundled-skills/messaging/SKILL.md +7 -7
  265. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +37 -0
  266. package/src/config/bundled-skills/slack/SKILL.md +18 -0
  267. package/src/config/env-registry.ts +15 -11
  268. package/src/config/env.ts +1 -11
  269. package/src/config/feature-flag-registry.json +16 -0
  270. package/src/config/schema.ts +4 -0
  271. package/src/config/schemas/heartbeat.ts +6 -1
  272. package/src/config/schemas/inference.ts +14 -3
  273. package/src/config/schemas/memory-processing.ts +16 -8
  274. package/src/config/schemas/memory-retrieval.ts +3 -3
  275. package/src/config/skills.ts +1 -1
  276. package/src/context/window-manager.ts +174 -51
  277. package/src/credential-execution/executable-discovery.ts +2 -2
  278. package/src/daemon/approved-devices-store.ts +2 -2
  279. package/src/daemon/assistant-attachments.ts +2 -0
  280. package/src/daemon/config-watcher.ts +4 -50
  281. package/src/daemon/conversation-agent-loop-handlers.ts +9 -1
  282. package/src/daemon/conversation-agent-loop.ts +12 -0
  283. package/src/daemon/conversation-error.ts +3 -5
  284. package/src/daemon/conversation-history.ts +7 -3
  285. package/src/daemon/conversation-lifecycle.ts +16 -0
  286. package/src/daemon/conversation-messaging.ts +1 -0
  287. package/src/daemon/conversation-notifiers.ts +67 -30
  288. package/src/daemon/conversation-process.ts +161 -2
  289. package/src/daemon/conversation-queue-manager.ts +2 -0
  290. package/src/daemon/conversation-runtime-assembly.ts +33 -11
  291. package/src/daemon/conversation-slash.ts +14 -3
  292. package/src/daemon/conversation-tool-setup.ts +2 -0
  293. package/src/daemon/conversation-usage.ts +32 -4
  294. package/src/daemon/conversation.ts +33 -1
  295. package/src/daemon/daemon-control.ts +32 -16
  296. package/src/daemon/date-context.ts +47 -45
  297. package/src/daemon/dictation-profile-store.ts +2 -2
  298. package/src/daemon/handlers/conversations.ts +19 -0
  299. package/src/daemon/handlers/shared.ts +14 -21
  300. package/src/daemon/lifecycle.ts +5 -7
  301. package/src/daemon/message-types/conversations.ts +2 -0
  302. package/src/daemon/message-types/guardian-actions.ts +3 -17
  303. package/src/daemon/message-types/integrations.ts +11 -1
  304. package/src/daemon/message-types/messages.ts +1 -0
  305. package/src/daemon/pairing-store.ts +2 -79
  306. package/src/daemon/server.ts +154 -8
  307. package/src/daemon/watch-handler.ts +65 -21
  308. package/src/email/guardrails.ts +3 -3
  309. package/src/heartbeat/heartbeat-service.ts +14 -7
  310. package/src/hooks/cli.ts +2 -2
  311. package/src/hooks/config.ts +2 -2
  312. package/src/hooks/discovery.ts +2 -2
  313. package/src/hooks/manager.ts +2 -2
  314. package/src/hooks/runner.ts +5 -2
  315. package/src/hooks/templates.ts +2 -2
  316. package/src/memory/admin.ts +181 -2
  317. package/src/memory/app-git-service.ts +61 -4
  318. package/src/memory/attachments-store.ts +2 -0
  319. package/src/memory/canonical-guardian-store.ts +16 -0
  320. package/src/memory/db-init.ts +8 -0
  321. package/src/memory/embedding-local.ts +5 -2
  322. package/src/memory/indexer.ts +44 -26
  323. package/src/memory/items-extractor.ts +34 -82
  324. package/src/memory/job-handlers/batch-extraction.ts +741 -0
  325. package/src/memory/job-handlers/journal-carry-forward.test.ts +383 -0
  326. package/src/memory/job-handlers/journal-carry-forward.ts +255 -0
  327. package/src/memory/jobs-store.ts +28 -0
  328. package/src/memory/jobs-worker.ts +56 -9
  329. package/src/memory/lifecycle-events-store.ts +4 -2
  330. package/src/memory/llm-request-log-store.ts +40 -2
  331. package/src/memory/llm-usage-store.ts +4 -3
  332. package/src/memory/migrations/199-guardian-request-enrichment-columns.ts +71 -0
  333. package/src/memory/migrations/200-usage-llm-call-count.ts +20 -0
  334. package/src/memory/migrations/index.ts +2 -0
  335. package/src/memory/query-expansion.ts +83 -0
  336. package/src/memory/retriever.test.ts +119 -0
  337. package/src/memory/retriever.ts +513 -105
  338. package/src/memory/schema/guardian.ts +4 -0
  339. package/src/memory/schema/infrastructure.ts +1 -0
  340. package/src/memory/search/formatting.test.ts +140 -0
  341. package/src/memory/search/formatting.ts +143 -198
  342. package/src/memory/search/mmr.ts +136 -0
  343. package/src/memory/search/staleness.ts +0 -15
  344. package/src/memory/search/tier-classifier.ts +10 -21
  345. package/src/memory/search/types.ts +17 -0
  346. package/src/messaging/providers/slack/adapter.ts +51 -5
  347. package/src/notifications/broadcaster.ts +13 -0
  348. package/src/notifications/copy-composer.ts +8 -0
  349. package/src/oauth/connect-orchestrator.ts +1 -1
  350. package/src/oauth/connection-resolver.ts +2 -2
  351. package/src/oauth/provider-serializer.ts +116 -0
  352. package/src/permissions/trust-store.ts +24 -7
  353. package/src/prompts/__tests__/build-cli-reference-section.test.ts +5 -0
  354. package/src/prompts/journal-context.ts +50 -35
  355. package/src/prompts/persona-resolver.ts +1 -1
  356. package/src/prompts/system-prompt.ts +27 -28
  357. package/src/prompts/templates/BOOTSTRAP.md +14 -1
  358. package/src/prompts/templates/HEARTBEAT.md +10 -0
  359. package/src/prompts/templates/NOW.md +19 -25
  360. package/src/prompts/templates/SOUL.md +13 -1
  361. package/src/prompts/templates/UPDATES.md +12 -0
  362. package/src/prompts/update-bulletin.ts +1 -1
  363. package/src/providers/anthropic/client.ts +89 -18
  364. package/src/providers/model-catalog.ts +22 -2
  365. package/src/providers/model-intents.ts +2 -2
  366. package/src/providers/openai/client.ts +40 -1
  367. package/src/providers/retry.ts +23 -4
  368. package/src/providers/types.ts +2 -0
  369. package/src/runtime/assistant-scope.ts +1 -1
  370. package/src/runtime/auth/__tests__/credential-service.test.ts +1 -0
  371. package/src/runtime/auth/route-policy.ts +1 -0
  372. package/src/runtime/auth/token-service.ts +51 -29
  373. package/src/runtime/confirmation-request-guardian-bridge.ts +3 -1
  374. package/src/runtime/guardian-decision-types.ts +16 -10
  375. package/src/runtime/http-server.ts +3 -14
  376. package/src/runtime/http-types.ts +1 -0
  377. package/src/runtime/migrations/vbundle-builder.ts +7 -4
  378. package/src/runtime/migrations/vbundle-import-analyzer.ts +0 -4
  379. package/src/runtime/migrations/vbundle-importer.ts +1 -1
  380. package/src/runtime/routes/conversation-query-routes.ts +40 -8
  381. package/src/runtime/routes/conversation-routes.ts +125 -3
  382. package/src/runtime/routes/guardian-action-routes.ts +9 -3
  383. package/src/runtime/routes/identity-routes.ts +25 -4
  384. package/src/runtime/routes/llm-context-normalization.ts +1 -0
  385. package/src/runtime/routes/log-export-routes.ts +34 -12
  386. package/src/runtime/routes/migration-routes.ts +6 -10
  387. package/src/runtime/routes/oauth-apps.ts +2 -9
  388. package/src/runtime/routes/oauth-providers.ts +60 -0
  389. package/src/runtime/routes/pairing-routes.ts +0 -8
  390. package/src/runtime/routes/settings-routes.ts +0 -1
  391. package/src/runtime/routes/telemetry-routes.ts +16 -4
  392. package/src/security/encrypted-store.ts +2 -2
  393. package/src/security/secret-allowlist.ts +3 -3
  394. package/src/signals/emit-event.ts +42 -0
  395. package/src/signals/user-message.ts +37 -0
  396. package/src/telemetry/usage-telemetry-reporter.test.ts +83 -19
  397. package/src/telemetry/usage-telemetry-reporter.ts +23 -17
  398. package/src/tools/browser/runtime-check.ts +2 -2
  399. package/src/tools/credentials/vault.ts +2 -249
  400. package/src/tools/memory/definitions.ts +1 -1
  401. package/src/tools/memory/handlers.test.ts +50 -8
  402. package/src/tools/memory/handlers.ts +3 -1
  403. package/src/tools/side-effects.ts +1 -6
  404. package/src/tools/terminal/safe-env.ts +3 -2
  405. package/src/tools/terminal/shell.ts +11 -14
  406. package/src/tools/tool-approval-handler.ts +20 -1
  407. package/src/tools/tool-input-summary.ts +66 -0
  408. package/src/tools/types.ts +4 -0
  409. package/src/usage/types.ts +4 -0
  410. package/src/util/device-id.ts +10 -10
  411. package/src/util/platform.ts +71 -33
  412. package/src/util/pricing.ts +19 -6
  413. package/src/util/strip-comment-lines.ts +28 -0
  414. package/src/workspace/git-service.ts +8 -18
  415. package/src/workspace/migrations/003-seed-device-id.ts +6 -4
  416. package/src/workspace/migrations/016-extract-feature-flags-to-protected.ts +7 -1
  417. package/src/workspace/migrations/017-seed-persona-dirs.ts +2 -4
  418. package/src/workspace/migrations/021-move-signals-to-workspace.ts +84 -0
  419. package/src/workspace/migrations/022-move-hooks-to-workspace.ts +94 -0
  420. package/src/workspace/migrations/023-move-config-files-to-workspace.ts +86 -0
  421. package/src/workspace/migrations/024-move-runtime-files-to-workspace.ts +126 -0
  422. package/src/workspace/migrations/migrate-to-workspace-volume.ts +3 -6
  423. package/src/workspace/migrations/registry.ts +8 -0
  424. package/src/signals/confirm.ts +0 -82
  425. package/src/signals/trust-rule.ts +0 -174
@@ -0,0 +1,383 @@
1
+ import { mkdtempSync, rmSync } from "node:fs";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
4
+ import {
5
+ afterAll,
6
+ beforeAll,
7
+ beforeEach,
8
+ describe,
9
+ expect,
10
+ mock,
11
+ test,
12
+ } from "bun:test";
13
+
14
+ const testDir = mkdtempSync(join(tmpdir(), "journal-carry-forward-test-"));
15
+
16
+ mock.module("../../util/platform.js", () => ({
17
+ getDataDir: () => testDir,
18
+ isMacOS: () => process.platform === "darwin",
19
+ isLinux: () => process.platform === "linux",
20
+ isWindows: () => process.platform === "win32",
21
+ getPidPath: () => join(testDir, "test.pid"),
22
+ getDbPath: () => join(testDir, "test.db"),
23
+ getLogPath: () => join(testDir, "test.log"),
24
+ ensureDataDir: () => {},
25
+ }));
26
+
27
+ mock.module("../../util/logger.js", () => ({
28
+ getLogger: () =>
29
+ new Proxy({} as Record<string, unknown>, {
30
+ get: () => () => {},
31
+ }),
32
+ }));
33
+
34
+ // Track enqueueMemoryJob calls
35
+ const enqueuedJobs: Array<{ type: string; payload: Record<string, unknown> }> =
36
+ [];
37
+
38
+ mock.module("../jobs-store.js", () => {
39
+ const actual =
40
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
41
+ require("../jobs-store.js") as typeof import("../jobs-store.js");
42
+ return {
43
+ ...actual,
44
+ enqueueMemoryJob: (type: string, payload: Record<string, unknown>) => {
45
+ enqueuedJobs.push({ type, payload });
46
+ return "mock-job-id";
47
+ },
48
+ };
49
+ });
50
+
51
+ // Mock the provider
52
+ let mockProviderResponse: unknown = null;
53
+
54
+ mock.module("../../providers/provider-send-message.js", () => ({
55
+ getConfiguredProvider: async () => {
56
+ if (!mockProviderResponse) return null;
57
+ return {
58
+ sendMessage: async () => mockProviderResponse,
59
+ };
60
+ },
61
+ extractToolUse: (response: { content: Array<{ type: string }> }) => {
62
+ return response.content.find(
63
+ (b: { type: string }) => b.type === "tool_use",
64
+ );
65
+ },
66
+ userMessage: (text: string) => ({
67
+ role: "user",
68
+ content: [{ type: "text", text }],
69
+ }),
70
+ }));
71
+
72
+ import { eq } from "drizzle-orm";
73
+
74
+ import { getDb, initializeDb, resetDb } from "../db.js";
75
+ import type { MemoryJob } from "../jobs-store.js";
76
+ import { memoryItems } from "../schema.js";
77
+ import { journalCarryForwardJob } from "./journal-carry-forward.js";
78
+
79
+ function makeJob(payload: Record<string, unknown>): MemoryJob {
80
+ return {
81
+ id: "test-job-id",
82
+ type: "journal_carry_forward",
83
+ payload,
84
+ status: "running",
85
+ attempts: 0,
86
+ deferrals: 0,
87
+ runAfter: Date.now(),
88
+ lastError: null,
89
+ startedAt: Date.now(),
90
+ createdAt: Date.now(),
91
+ updatedAt: Date.now(),
92
+ };
93
+ }
94
+
95
+ function makeProviderResponse(
96
+ items: Array<{
97
+ kind: string;
98
+ subject: string;
99
+ statement: string;
100
+ importance: number;
101
+ }>,
102
+ ) {
103
+ return {
104
+ content: [
105
+ {
106
+ type: "tool_use" as const,
107
+ id: "tool-1",
108
+ name: "store_journal_memories",
109
+ input: { items },
110
+ },
111
+ ],
112
+ model: "test-model",
113
+ stop_reason: "tool_use",
114
+ usage: { input_tokens: 0, output_tokens: 0 },
115
+ };
116
+ }
117
+
118
+ beforeAll(() => {
119
+ initializeDb();
120
+ });
121
+
122
+ beforeEach(() => {
123
+ resetDb();
124
+ initializeDb();
125
+ // Clear memory items table between tests
126
+ getDb().delete(memoryItems).run();
127
+ enqueuedJobs.length = 0;
128
+ mockProviderResponse = null;
129
+ });
130
+
131
+ afterAll(() => {
132
+ rmSync(testDir, { recursive: true, force: true });
133
+ });
134
+
135
+ describe("journalCarryForwardJob", () => {
136
+ test("extracts memory items from journal content", async () => {
137
+ mockProviderResponse = makeProviderResponse([
138
+ {
139
+ kind: "identity",
140
+ subject: "Therapy breakthrough",
141
+ statement:
142
+ "Had a major realization in therapy about patterns of avoidance",
143
+ importance: 0.9,
144
+ },
145
+ {
146
+ kind: "event",
147
+ subject: "Growth milestone",
148
+ statement:
149
+ "First time feeling genuinely at peace with uncertainty about the future",
150
+ importance: 0.85,
151
+ },
152
+ ]);
153
+
154
+ await journalCarryForwardJob(
155
+ makeJob({
156
+ journalContent: "Today in therapy I had a breakthrough...",
157
+ userSlug: "testuser",
158
+ filename: "2025-03-15-therapy.md",
159
+ scopeId: "test-scope",
160
+ }),
161
+ );
162
+
163
+ const db = getDb();
164
+ const items = db
165
+ .select()
166
+ .from(memoryItems)
167
+ .where(eq(memoryItems.scopeId, "test-scope"))
168
+ .all();
169
+
170
+ expect(items).toHaveLength(2);
171
+ });
172
+
173
+ test("items have importance >= 0.7", async () => {
174
+ mockProviderResponse = makeProviderResponse([
175
+ {
176
+ kind: "preference",
177
+ subject: "Minor detail",
178
+ statement: "Some minor logistical preference",
179
+ importance: 0.3, // Below 0.7 -- should be floored
180
+ },
181
+ {
182
+ kind: "identity",
183
+ subject: "Core realization",
184
+ statement: "A deeply personal insight",
185
+ importance: 0.95,
186
+ },
187
+ ]);
188
+
189
+ await journalCarryForwardJob(
190
+ makeJob({
191
+ journalContent: "Some journal content...",
192
+ userSlug: "testuser",
193
+ filename: "entry.md",
194
+ scopeId: "test-scope",
195
+ }),
196
+ );
197
+
198
+ const db = getDb();
199
+ const items = db
200
+ .select()
201
+ .from(memoryItems)
202
+ .where(eq(memoryItems.scopeId, "test-scope"))
203
+ .all();
204
+
205
+ expect(items).toHaveLength(2);
206
+ for (const item of items) {
207
+ expect(item.importance).toBeGreaterThanOrEqual(0.7);
208
+ }
209
+ });
210
+
211
+ test('items have sourceType "journal_carry_forward"', async () => {
212
+ mockProviderResponse = makeProviderResponse([
213
+ {
214
+ kind: "journal",
215
+ subject: "Reflection",
216
+ statement: "A meaningful reflection on growth",
217
+ importance: 0.8,
218
+ },
219
+ ]);
220
+
221
+ await journalCarryForwardJob(
222
+ makeJob({
223
+ journalContent: "Reflecting on my journey...",
224
+ userSlug: "testuser",
225
+ filename: "reflection.md",
226
+ scopeId: "test-scope",
227
+ }),
228
+ );
229
+
230
+ const db = getDb();
231
+ const items = db
232
+ .select()
233
+ .from(memoryItems)
234
+ .where(eq(memoryItems.scopeId, "test-scope"))
235
+ .all();
236
+
237
+ expect(items).toHaveLength(1);
238
+ expect(items[0].sourceType).toBe("journal_carry_forward");
239
+ });
240
+
241
+ test('items have verificationState "user_confirmed"', async () => {
242
+ mockProviderResponse = makeProviderResponse([
243
+ {
244
+ kind: "identity",
245
+ subject: "Self knowledge",
246
+ statement: "I know who I am",
247
+ importance: 0.9,
248
+ },
249
+ ]);
250
+
251
+ await journalCarryForwardJob(
252
+ makeJob({
253
+ journalContent: "I know who I am...",
254
+ userSlug: "testuser",
255
+ filename: "knowing.md",
256
+ scopeId: "test-scope",
257
+ }),
258
+ );
259
+
260
+ const db = getDb();
261
+ const items = db
262
+ .select()
263
+ .from(memoryItems)
264
+ .where(eq(memoryItems.scopeId, "test-scope"))
265
+ .all();
266
+
267
+ expect(items).toHaveLength(1);
268
+ expect(items[0].verificationState).toBe("user_confirmed");
269
+ });
270
+
271
+ test("deduplicates items by fingerprint", async () => {
272
+ mockProviderResponse = makeProviderResponse([
273
+ {
274
+ kind: "identity",
275
+ subject: "Duplicate item",
276
+ statement: "Exact same statement",
277
+ importance: 0.8,
278
+ },
279
+ ]);
280
+
281
+ // Run twice with the same content
282
+ const job = makeJob({
283
+ journalContent: "Same content...",
284
+ userSlug: "testuser",
285
+ filename: "dup.md",
286
+ scopeId: "test-scope",
287
+ });
288
+
289
+ await journalCarryForwardJob(job);
290
+ await journalCarryForwardJob(job);
291
+
292
+ const db = getDb();
293
+ const items = db
294
+ .select()
295
+ .from(memoryItems)
296
+ .where(eq(memoryItems.scopeId, "test-scope"))
297
+ .all();
298
+
299
+ // Should only have 1 item despite running twice
300
+ expect(items).toHaveLength(1);
301
+ });
302
+
303
+ test("enqueues embed_item jobs for new items", async () => {
304
+ mockProviderResponse = makeProviderResponse([
305
+ {
306
+ kind: "event",
307
+ subject: "Milestone",
308
+ statement: "Reached a significant milestone",
309
+ importance: 0.85,
310
+ },
311
+ ]);
312
+
313
+ await journalCarryForwardJob(
314
+ makeJob({
315
+ journalContent: "Today was a milestone...",
316
+ userSlug: "testuser",
317
+ filename: "milestone.md",
318
+ scopeId: "test-scope",
319
+ }),
320
+ );
321
+
322
+ const embedJobs = enqueuedJobs.filter((j) => j.type === "embed_item");
323
+ expect(embedJobs).toHaveLength(1);
324
+ expect(embedJobs[0].payload.itemId).toBeDefined();
325
+ });
326
+
327
+ test("skips invalid kinds", async () => {
328
+ mockProviderResponse = makeProviderResponse([
329
+ {
330
+ kind: "nonsense_kind",
331
+ subject: "Invalid",
332
+ statement: "Should be skipped",
333
+ importance: 0.9,
334
+ },
335
+ {
336
+ kind: "identity",
337
+ subject: "Valid item",
338
+ statement: "Should be kept",
339
+ importance: 0.9,
340
+ },
341
+ ]);
342
+
343
+ await journalCarryForwardJob(
344
+ makeJob({
345
+ journalContent: "Content...",
346
+ userSlug: "testuser",
347
+ filename: "kinds.md",
348
+ scopeId: "test-scope",
349
+ }),
350
+ );
351
+
352
+ const db = getDb();
353
+ const items = db
354
+ .select()
355
+ .from(memoryItems)
356
+ .where(eq(memoryItems.scopeId, "test-scope"))
357
+ .all();
358
+
359
+ expect(items).toHaveLength(1);
360
+ expect(items[0].kind).toBe("identity");
361
+ });
362
+
363
+ test("returns early when journalContent is missing", async () => {
364
+ mockProviderResponse = makeProviderResponse([]);
365
+
366
+ await journalCarryForwardJob(
367
+ makeJob({
368
+ userSlug: "testuser",
369
+ filename: "missing.md",
370
+ scopeId: "test-scope",
371
+ }),
372
+ );
373
+
374
+ const db = getDb();
375
+ const items = db
376
+ .select()
377
+ .from(memoryItems)
378
+ .where(eq(memoryItems.scopeId, "test-scope"))
379
+ .all();
380
+
381
+ expect(items).toHaveLength(0);
382
+ });
383
+ });
@@ -0,0 +1,255 @@
1
+ /**
2
+ * Job handler for preserving journal entries that rotate out of the active
3
+ * context window. Extracts durable memories from journal content using a
4
+ * journal-specific prompt that prioritizes emotional significance and
5
+ * personal meaning over event logging.
6
+ */
7
+
8
+ import { and, eq } from "drizzle-orm";
9
+ import { v4 as uuid } from "uuid";
10
+
11
+ import {
12
+ extractToolUse,
13
+ getConfiguredProvider,
14
+ userMessage,
15
+ } from "../../providers/provider-send-message.js";
16
+ import { BackendUnavailableError, ProviderError } from "../../util/errors.js";
17
+ import { getLogger } from "../../util/logger.js";
18
+ import { setMemoryCheckpoint } from "../checkpoints.js";
19
+ import { getDb } from "../db.js";
20
+ import { computeMemoryFingerprint } from "../fingerprint.js";
21
+ import { asString } from "../job-utils.js";
22
+ import { enqueueMemoryJob, type MemoryJob } from "../jobs-store.js";
23
+ import { memoryItems } from "../schema.js";
24
+ import { clampUnitInterval } from "../validation.js";
25
+
26
+ const log = getLogger("journal-carry-forward");
27
+
28
+ const JOURNAL_EXTRACTION_SYSTEM_PROMPT = `You are extracting durable memories from a journal entry that is about to leave the active context window.
29
+ This is a personal journal -- focus on what things MEANT and how they FELT, not just what happened.
30
+
31
+ Good extraction: "An evidence-based psychologist read everything about us and said yes -- it made me feel like we're not crazy"
32
+ Bad extraction: "Coaching session happened on March 27"
33
+
34
+ Extract the most important memories -- emotional significance, personal growth, relationship milestones,
35
+ hard-won realizations. Skip logistical details unless they carry emotional weight.
36
+
37
+ For each memory, provide:
38
+ - kind: Category of memory (identity, preference, project, decision, constraint, event, journal)
39
+ - subject: A short label (2-8 words) identifying what this is about
40
+ - statement: A rich, feeling-aware statement to remember (1-2 sentences). Capture the emotional texture, not just the facts.
41
+ - importance: How significant this is (0.0-1.0). Journal entries are personal -- most items should be 0.7 or higher.
42
+
43
+ Rules:
44
+ - Focus on what matters emotionally, not what happened chronologically
45
+ - Preserve the author's voice and perspective -- these are their words
46
+ - Extract fewer, richer items rather than many shallow ones
47
+ - If the entry contains nothing worth preserving as a durable memory, return an empty array`;
48
+
49
+ const JOURNAL_MEMORY_KINDS = [
50
+ "identity",
51
+ "preference",
52
+ "project",
53
+ "decision",
54
+ "constraint",
55
+ "event",
56
+ "journal",
57
+ ] as const;
58
+
59
+ interface JournalExtractedItem {
60
+ kind: string;
61
+ subject: string;
62
+ statement: string;
63
+ importance: number;
64
+ }
65
+
66
+ export async function journalCarryForwardJob(job: MemoryJob): Promise<void> {
67
+ const journalContent = asString(job.payload.journalContent);
68
+ const userSlug = asString(job.payload.userSlug);
69
+ const filename = asString(job.payload.filename);
70
+ const scopeId = asString(job.payload.scopeId) ?? "default";
71
+
72
+ if (!journalContent || !filename) {
73
+ log.warn({ jobId: job.id }, "Missing journalContent or filename in payload");
74
+ return;
75
+ }
76
+
77
+ const provider = await getConfiguredProvider();
78
+ if (!provider) {
79
+ throw new BackendUnavailableError(
80
+ "Provider unavailable for journal carry-forward extraction",
81
+ );
82
+ }
83
+
84
+ const userContext = userSlug ? ` (author: ${userSlug})` : "";
85
+ const response = await provider.sendMessage(
86
+ [
87
+ userMessage(
88
+ `Journal entry "${filename}"${userContext}:\n\n${journalContent}`,
89
+ ),
90
+ ],
91
+ [
92
+ {
93
+ name: "store_journal_memories",
94
+ description:
95
+ "Store durable memories extracted from a journal entry leaving the context window",
96
+ input_schema: {
97
+ type: "object" as const,
98
+ properties: {
99
+ items: {
100
+ type: "array",
101
+ items: {
102
+ type: "object",
103
+ properties: {
104
+ kind: {
105
+ type: "string",
106
+ enum: [...JOURNAL_MEMORY_KINDS],
107
+ description: "Category of memory item",
108
+ },
109
+ subject: {
110
+ type: "string",
111
+ description:
112
+ "Short label (2-8 words) for what this is about",
113
+ },
114
+ statement: {
115
+ type: "string",
116
+ description:
117
+ "Rich, feeling-aware statement to remember (1-2 sentences)",
118
+ },
119
+ importance: {
120
+ type: "number",
121
+ description:
122
+ "How significant this is to remember (0.0-1.0, most journal items should be 0.7+)",
123
+ },
124
+ },
125
+ required: ["kind", "subject", "statement", "importance"],
126
+ },
127
+ },
128
+ },
129
+ required: ["items"],
130
+ },
131
+ },
132
+ ],
133
+ JOURNAL_EXTRACTION_SYSTEM_PROMPT,
134
+ {
135
+ config: {
136
+ modelIntent: "quality-optimized",
137
+ tool_choice: {
138
+ type: "tool" as const,
139
+ name: "store_journal_memories",
140
+ },
141
+ },
142
+ },
143
+ );
144
+
145
+ const toolBlock = extractToolUse(response);
146
+ if (!toolBlock) {
147
+ throw new ProviderError(
148
+ "No tool_use block in journal carry-forward response",
149
+ "unknown",
150
+ 502,
151
+ );
152
+ }
153
+
154
+ const input = toolBlock.input as { items?: JournalExtractedItem[] };
155
+ if (!Array.isArray(input.items)) {
156
+ throw new ProviderError(
157
+ "Invalid items structure in journal carry-forward response",
158
+ "unknown",
159
+ 502,
160
+ );
161
+ }
162
+
163
+ const db = getDb();
164
+ const validKinds = new Set<string>(JOURNAL_MEMORY_KINDS);
165
+ let inserted = 0;
166
+
167
+ for (const raw of input.items) {
168
+ if (!validKinds.has(raw.kind)) continue;
169
+ if (!raw.subject || !raw.statement) continue;
170
+
171
+ const subject = String(raw.subject).trim();
172
+ const statement = String(raw.statement).trim();
173
+ // Journal entries are inherently personal/load-bearing -- floor at 0.7
174
+ const importance = clampUnitInterval(
175
+ Math.max(0.7, parseImportance(raw.importance)),
176
+ );
177
+ const confidence = 0.95;
178
+
179
+ const fingerprint = computeMemoryFingerprint(
180
+ scopeId,
181
+ raw.kind,
182
+ subject,
183
+ statement,
184
+ );
185
+
186
+ // Dedup: skip if an item with this fingerprint already exists in this scope
187
+ const existing = db
188
+ .select({ id: memoryItems.id })
189
+ .from(memoryItems)
190
+ .where(
191
+ and(
192
+ eq(memoryItems.fingerprint, fingerprint),
193
+ eq(memoryItems.scopeId, scopeId),
194
+ ),
195
+ )
196
+ .get();
197
+
198
+ if (existing) {
199
+ log.debug(
200
+ { fingerprint, subject },
201
+ "Skipping duplicate journal memory item",
202
+ );
203
+ continue;
204
+ }
205
+
206
+ const now = Date.now();
207
+ const itemId = uuid();
208
+
209
+ db.insert(memoryItems)
210
+ .values({
211
+ id: itemId,
212
+ kind: raw.kind,
213
+ subject,
214
+ statement,
215
+ status: "active",
216
+ confidence,
217
+ importance,
218
+ fingerprint,
219
+ sourceType: "journal_carry_forward",
220
+ verificationState: "user_confirmed",
221
+ scopeId,
222
+ firstSeenAt: now,
223
+ lastSeenAt: now,
224
+ lastUsedAt: null,
225
+ supersedes: null,
226
+ overrideConfidence: "inferred",
227
+ })
228
+ .run();
229
+
230
+ enqueueMemoryJob("embed_item", { itemId });
231
+ inserted += 1;
232
+ }
233
+
234
+ // Write checkpoint only after successful completion so failed jobs can be
235
+ // re-enqueued on the next prompt render cycle.
236
+ const checkpointKey = `journal_carry_forward:${userSlug || "unknown"}:${filename}`;
237
+ setMemoryCheckpoint(checkpointKey, String(Date.now()));
238
+
239
+ log.info(
240
+ {
241
+ filename,
242
+ userSlug,
243
+ scopeId,
244
+ extracted: input.items.length,
245
+ inserted,
246
+ },
247
+ "Journal carry-forward complete",
248
+ );
249
+ }
250
+
251
+ function parseImportance(value: unknown): number {
252
+ if (value == null || value === "") return 0.7;
253
+ const n = Number(value);
254
+ return Number.isFinite(n) ? n : 0.7;
255
+ }
@@ -18,6 +18,7 @@ export type MemoryJobType =
18
18
  | "embed_summary"
19
19
  | "extract_items"
20
20
  | "extract_entities"
21
+ | "batch_extract"
21
22
  | "cleanup_stale_superseded_items"
22
23
  | "prune_old_conversations"
23
24
  | "backfill_entity_relations"
@@ -31,6 +32,7 @@ export type MemoryJobType =
31
32
  | "embed_media"
32
33
  | "embed_attachment"
33
34
  | "generate_conversation_starters"
35
+ | "journal_carry_forward"
34
36
  | "generate_capability_cards" // legacy compat — silently dropped by worker (capability cards removed)
35
37
  | "generate_thread_starters"; // legacy compat — silently dropped by worker (renamed to generate_conversation_starters)
36
38
 
@@ -126,6 +128,32 @@ export function upsertDebouncedJob(
126
128
  }
127
129
  }
128
130
 
131
+ /**
132
+ * Check whether a pending or running `journal_carry_forward` job already
133
+ * exists for the given filename + userSlug. Used to prevent duplicate
134
+ * enqueues while a carry-forward job is still in flight.
135
+ */
136
+ export function hasActiveCarryForwardJob(
137
+ filename: string,
138
+ userSlug: string,
139
+ ): boolean {
140
+ const db = getDb();
141
+ return (
142
+ db
143
+ .select({ id: memoryJobs.id })
144
+ .from(memoryJobs)
145
+ .where(
146
+ and(
147
+ eq(memoryJobs.type, "journal_carry_forward"),
148
+ inArray(memoryJobs.status, ["pending", "running"]),
149
+ sql`json_extract(${memoryJobs.payload}, '$.filename') = ${filename}`,
150
+ sql`json_extract(${memoryJobs.payload}, '$.userSlug') = ${userSlug}`,
151
+ ),
152
+ )
153
+ .get() != null
154
+ );
155
+ }
156
+
129
157
  export function enqueueCleanupStaleSupersededItemsJob(
130
158
  retentionMs?: number,
131
159
  ): string {