@vellumai/assistant 0.5.6 → 0.5.8

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 (442) hide show
  1. package/.env.example +16 -2
  2. package/ARCHITECTURE.md +6 -75
  3. package/Dockerfile +3 -2
  4. package/README.md +0 -2
  5. package/bun.lock +0 -414
  6. package/docker-entrypoint.sh +9 -0
  7. package/docs/architecture/keychain-broker.md +45 -240
  8. package/docs/architecture/memory.md +13 -11
  9. package/docs/architecture/security.md +0 -17
  10. package/docs/credential-execution-service.md +2 -2
  11. package/node_modules/@vellumai/ces-contracts/package.json +1 -0
  12. package/node_modules/@vellumai/ces-contracts/src/error.ts +1 -1
  13. package/node_modules/@vellumai/ces-contracts/src/grants.ts +1 -1
  14. package/node_modules/@vellumai/ces-contracts/src/handles.ts +1 -1
  15. package/node_modules/@vellumai/ces-contracts/src/index.ts +1 -1
  16. package/node_modules/@vellumai/ces-contracts/src/rpc.ts +120 -1
  17. package/node_modules/@vellumai/credential-storage/package.json +1 -0
  18. package/node_modules/@vellumai/egress-proxy/package.json +1 -0
  19. package/package.json +2 -3
  20. package/src/__tests__/actor-token-service.test.ts +0 -114
  21. package/src/__tests__/approval-cascade.test.ts +0 -1
  22. package/src/__tests__/assistant-feature-flags-integration.test.ts +30 -29
  23. package/src/__tests__/browser-fill-credential.test.ts +1 -1
  24. package/src/__tests__/browser-skill-endstate.test.ts +6 -5
  25. package/src/__tests__/btw-routes.test.ts +0 -39
  26. package/src/__tests__/call-controller.test.ts +0 -1
  27. package/src/__tests__/call-domain.test.ts +0 -128
  28. package/src/__tests__/ces-rpc-credential-backend.test.ts +199 -0
  29. package/src/__tests__/ces-startup-timeout.test.ts +40 -0
  30. package/src/__tests__/channel-approval-routes.test.ts +0 -5
  31. package/src/__tests__/channel-readiness-service.test.ts +1 -60
  32. package/src/__tests__/checker.test.ts +4 -2
  33. package/src/__tests__/cli-command-risk-guard.test.ts +112 -0
  34. package/src/__tests__/config-schema-cmd.test.ts +0 -2
  35. package/src/__tests__/config-schema.test.ts +3 -1
  36. package/src/__tests__/conversation-abort-tool-results.test.ts +0 -1
  37. package/src/__tests__/conversation-agent-loop-overflow.test.ts +0 -2
  38. package/src/__tests__/conversation-agent-loop.test.ts +2 -4
  39. package/src/__tests__/conversation-attention-telegram.test.ts +0 -5
  40. package/src/__tests__/conversation-confirmation-signals.test.ts +0 -1
  41. package/src/__tests__/conversation-error.test.ts +15 -1
  42. package/src/__tests__/conversation-init.benchmark.test.ts +0 -2
  43. package/src/__tests__/conversation-messaging-secret-redirect.test.ts +1 -1
  44. package/src/__tests__/conversation-pre-run-repair.test.ts +0 -1
  45. package/src/__tests__/conversation-provider-retry-repair.test.ts +0 -1
  46. package/src/__tests__/conversation-queue.test.ts +0 -1
  47. package/src/__tests__/conversation-skill-tools.test.ts +0 -54
  48. package/src/__tests__/conversation-slash-queue.test.ts +0 -1
  49. package/src/__tests__/conversation-slash-unknown.test.ts +0 -1
  50. package/src/__tests__/conversation-title-service.test.ts +87 -0
  51. package/src/__tests__/conversation-workspace-injection.test.ts +0 -1
  52. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +0 -1
  53. package/src/__tests__/credential-execution-client.test.ts +5 -2
  54. package/src/__tests__/credential-execution-feature-gates.test.ts +59 -30
  55. package/src/__tests__/credential-execution-managed-contract.test.ts +35 -20
  56. package/src/__tests__/credential-security-e2e.test.ts +1 -67
  57. package/src/__tests__/credential-security-invariants.test.ts +6 -50
  58. package/src/__tests__/credentials-cli.test.ts +82 -3
  59. package/src/__tests__/daemon-credential-client.test.ts +123 -0
  60. package/src/__tests__/db-migration-rollback.test.ts +2015 -1
  61. package/src/__tests__/deterministic-verification-control-plane.test.ts +1 -0
  62. package/src/__tests__/docker-signing-key-bootstrap.test.ts +34 -143
  63. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +6 -4
  64. package/src/__tests__/gateway-client-managed-outbound.test.ts +79 -1
  65. package/src/__tests__/guardian-routing-state.test.ts +0 -5
  66. package/src/__tests__/host-shell-tool.test.ts +6 -7
  67. package/src/__tests__/http-user-message-parity.test.ts +3 -103
  68. package/src/__tests__/inbound-invite-redemption.test.ts +0 -4
  69. package/src/__tests__/inline-skill-load-permissions.test.ts +6 -8
  70. package/src/__tests__/intent-routing.test.ts +0 -13
  71. package/src/__tests__/jobs-store-qdrant-breaker.test.ts +178 -0
  72. package/src/__tests__/journal-context.test.ts +335 -0
  73. package/src/__tests__/keychain-broker-client.test.ts +161 -22
  74. package/src/__tests__/memory-context-benchmark.benchmark.test.ts +0 -3
  75. package/src/__tests__/memory-jobs-worker-backoff.test.ts +150 -0
  76. package/src/__tests__/memory-lifecycle-e2e.test.ts +70 -25
  77. package/src/__tests__/memory-recall-quality.test.ts +48 -17
  78. package/src/__tests__/memory-regressions.test.ts +408 -363
  79. package/src/__tests__/memory-retrieval.benchmark.test.ts +0 -3
  80. package/src/__tests__/migration-export-http.test.ts +2 -2
  81. package/src/__tests__/migration-import-commit-http.test.ts +2 -2
  82. package/src/__tests__/migration-import-preflight-http.test.ts +2 -2
  83. package/src/__tests__/migration-validate-http.test.ts +2 -2
  84. package/src/__tests__/non-member-access-request.test.ts +2 -7
  85. package/src/__tests__/notification-decision-fallback.test.ts +4 -0
  86. package/src/__tests__/notification-decision-identity.test.ts +4 -0
  87. package/src/__tests__/notification-decision-strategy.test.ts +71 -0
  88. package/src/__tests__/oauth-cli.test.ts +5 -1
  89. package/src/__tests__/permission-types.test.ts +1 -0
  90. package/src/__tests__/provider-commit-message-generator.test.ts +0 -37
  91. package/src/__tests__/provider-error-scenarios.test.ts +0 -267
  92. package/src/__tests__/provider-managed-proxy-integration.test.ts +5 -6
  93. package/src/__tests__/provider-streaming.benchmark.test.ts +2 -81
  94. package/src/__tests__/qdrant-manager.test.ts +28 -2
  95. package/src/__tests__/registry.test.ts +0 -6
  96. package/src/__tests__/relay-server.test.ts +1 -2
  97. package/src/__tests__/runtime-attachment-metadata.test.ts +0 -4
  98. package/src/__tests__/script-proxy-injection-runtime.test.ts +1 -1
  99. package/src/__tests__/secret-onetime-send.test.ts +1 -1
  100. package/src/__tests__/secret-routes-managed-proxy.test.ts +0 -4
  101. package/src/__tests__/secure-keys.test.ts +95 -272
  102. package/src/__tests__/shell-identity.test.ts +96 -6
  103. package/src/__tests__/skill-feature-flags-integration.test.ts +22 -14
  104. package/src/__tests__/skill-feature-flags.test.ts +46 -45
  105. package/src/__tests__/skill-load-feature-flag.test.ts +7 -10
  106. package/src/__tests__/skill-load-inline-command.test.ts +8 -12
  107. package/src/__tests__/skill-load-inline-includes.test.ts +6 -10
  108. package/src/__tests__/skill-load-tool.test.ts +0 -2
  109. package/src/__tests__/skill-memory.test.ts +17 -3
  110. package/src/__tests__/skill-projection-feature-flag.test.ts +33 -29
  111. package/src/__tests__/skills.test.ts +0 -2
  112. package/src/__tests__/slack-inbound-verification.test.ts +0 -4
  113. package/src/__tests__/stale-approval-dedup.test.ts +171 -0
  114. package/src/__tests__/stt-hints.test.ts +437 -0
  115. package/src/__tests__/suggestion-routes.test.ts +1 -32
  116. package/src/__tests__/system-prompt.test.ts +0 -1
  117. package/src/__tests__/task-memory-cleanup.test.ts +14 -0
  118. package/src/__tests__/tool-executor-shell-integration.test.ts +5 -3
  119. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +0 -5
  120. package/src/__tests__/trusted-contact-multichannel.test.ts +0 -4
  121. package/src/__tests__/twilio-routes-twiml.test.ts +139 -1
  122. package/src/__tests__/update-bulletin.test.ts +0 -2
  123. package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +6 -9
  124. package/src/__tests__/voice-quality.test.ts +58 -0
  125. package/src/__tests__/voice-scoped-grant-consumer.test.ts +0 -7
  126. package/src/__tests__/workspace-migration-015-migrate-credentials-to-keychain.test.ts +252 -0
  127. package/src/__tests__/workspace-migration-016-migrate-credentials-from-keychain.test.ts +220 -0
  128. package/src/__tests__/workspace-migration-down-functions.test.ts +1009 -0
  129. package/src/__tests__/workspace-migrations-runner.test.ts +114 -0
  130. package/src/acp/agent-process.ts +9 -1
  131. package/src/agent/loop.ts +1 -1
  132. package/src/approvals/guardian-request-resolvers.ts +164 -38
  133. package/src/calls/__tests__/tts-text-sanitizer.test.ts +254 -0
  134. package/src/calls/audio-store.test.ts +97 -0
  135. package/src/calls/audio-store.ts +205 -0
  136. package/src/calls/call-controller.ts +90 -8
  137. package/src/calls/call-domain.ts +3 -0
  138. package/src/calls/call-store.ts +10 -3
  139. package/src/calls/fish-audio-client.ts +129 -0
  140. package/src/calls/relay-server.ts +27 -0
  141. package/src/calls/stt-hints.ts +189 -0
  142. package/src/calls/tts-text-sanitizer.ts +61 -0
  143. package/src/calls/twilio-routes.ts +34 -5
  144. package/src/calls/types.ts +1 -0
  145. package/src/calls/voice-ingress-preflight.ts +0 -42
  146. package/src/calls/voice-quality.ts +38 -5
  147. package/src/calls/voice-session-bridge.ts +7 -12
  148. package/src/cli/commands/avatar.ts +2 -2
  149. package/src/cli/commands/config.ts +1 -4
  150. package/src/cli/commands/credentials.ts +128 -82
  151. package/src/cli/commands/doctor.ts +2 -2
  152. package/src/cli/commands/keys.ts +7 -7
  153. package/src/cli/commands/memory.ts +1 -1
  154. package/src/cli/commands/oauth/connections.ts +11 -29
  155. package/src/cli/commands/oauth/index.ts +7 -0
  156. package/src/cli/commands/oauth/platform.ts +525 -0
  157. package/src/cli/commands/platform.ts +3 -3
  158. package/src/cli/lib/daemon-credential-client.ts +284 -0
  159. package/src/cli.ts +1 -1
  160. package/src/config/assistant-feature-flags.ts +186 -5
  161. package/src/config/bundled-skills/AGENTS.md +34 -0
  162. package/src/config/bundled-skills/acp/SKILL.md +10 -0
  163. package/src/config/bundled-skills/app-builder/SKILL.md +0 -4
  164. package/src/config/bundled-skills/messaging/SKILL.md +5 -5
  165. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +2 -2
  166. package/src/config/bundled-skills/phone-calls/TOOLS.json +4 -0
  167. package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +1 -0
  168. package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +1 -0
  169. package/src/config/bundled-skills/settings/SKILL.md +15 -2
  170. package/src/config/bundled-skills/settings/TOOLS.json +47 -2
  171. package/src/config/bundled-skills/settings/tools/avatar-remove.ts +59 -0
  172. package/src/config/bundled-skills/settings/tools/avatar-update.ts +80 -0
  173. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +42 -0
  174. package/src/config/bundled-skills/slack/SKILL.md +1 -1
  175. package/src/config/bundled-tool-registry.ts +5 -11
  176. package/src/config/defaults.ts +0 -2
  177. package/src/config/env-registry.ts +5 -5
  178. package/src/config/env.ts +21 -14
  179. package/src/config/feature-flag-registry.json +49 -9
  180. package/src/config/loader.ts +106 -42
  181. package/src/config/schema.ts +9 -29
  182. package/src/config/schemas/calls.ts +30 -0
  183. package/src/config/schemas/fish-audio.ts +39 -0
  184. package/src/config/schemas/inference.ts +2 -2
  185. package/src/config/schemas/journal.ts +16 -0
  186. package/src/config/schemas/memory-processing.ts +2 -2
  187. package/src/config/schemas/security.ts +0 -4
  188. package/src/config/types.ts +1 -1
  189. package/src/contacts/contact-store.ts +39 -0
  190. package/src/contacts/types.ts +2 -0
  191. package/src/credential-execution/approval-bridge.ts +1 -0
  192. package/src/credential-execution/executable-discovery.ts +28 -4
  193. package/src/credential-execution/feature-gates.ts +16 -0
  194. package/src/credential-execution/process-manager.ts +38 -0
  195. package/src/credential-execution/startup-timeout.ts +36 -0
  196. package/src/daemon/approval-generators.ts +3 -9
  197. package/src/daemon/assistant-attachments.ts +9 -0
  198. package/src/daemon/config-watcher.ts +5 -0
  199. package/src/daemon/conversation-error.ts +13 -1
  200. package/src/daemon/conversation-memory.ts +1 -2
  201. package/src/daemon/conversation-process.ts +18 -1
  202. package/src/daemon/conversation-surfaces.ts +30 -1
  203. package/src/daemon/conversation-tool-setup.ts +0 -105
  204. package/src/daemon/conversation.ts +21 -1
  205. package/src/daemon/guardian-action-generators.ts +3 -9
  206. package/src/daemon/handlers/config-vercel.ts +92 -0
  207. package/src/daemon/handlers/skills.ts +2 -15
  208. package/src/daemon/install-symlink.ts +195 -0
  209. package/src/daemon/lifecycle.ts +234 -51
  210. package/src/daemon/message-types/conversations.ts +4 -4
  211. package/src/daemon/message-types/diagnostics.ts +3 -22
  212. package/src/daemon/message-types/messages.ts +0 -2
  213. package/src/daemon/message-types/upgrades.ts +8 -0
  214. package/src/daemon/server.ts +32 -95
  215. package/src/events/domain-events.ts +2 -1
  216. package/src/inbound/platform-callback-registration.ts +3 -3
  217. package/src/instrument.ts +8 -5
  218. package/src/memory/app-store.ts +31 -0
  219. package/src/memory/conversation-title-service.ts +50 -1
  220. package/src/memory/db-init.ts +16 -0
  221. package/src/memory/indexer.ts +19 -10
  222. package/src/memory/items-extractor.ts +328 -321
  223. package/src/memory/job-handlers/conversation-starters.ts +4 -1
  224. package/src/memory/job-handlers/summarization.ts +26 -16
  225. package/src/memory/jobs-store.ts +63 -6
  226. package/src/memory/jobs-worker.ts +31 -7
  227. package/src/memory/journal-memory.ts +214 -0
  228. package/src/memory/migrations/001-job-deferrals.ts +19 -0
  229. package/src/memory/migrations/004-entity-relation-dedup.ts +10 -0
  230. package/src/memory/migrations/005-fingerprint-scope-unique.ts +76 -0
  231. package/src/memory/migrations/006-scope-salted-fingerprints.ts +50 -0
  232. package/src/memory/migrations/007-assistant-id-to-self.ts +10 -0
  233. package/src/memory/migrations/008-remove-assistant-id-columns.ts +34 -0
  234. package/src/memory/migrations/009-llm-usage-events-drop-assistant-id.ts +26 -0
  235. package/src/memory/migrations/014-backfill-inbox-thread-state.ts +10 -0
  236. package/src/memory/migrations/015-drop-active-search-index.ts +17 -0
  237. package/src/memory/migrations/019-notification-tables-schema-migration.ts +12 -0
  238. package/src/memory/migrations/020-rename-macos-ios-channel-to-vellum.ts +121 -0
  239. package/src/memory/migrations/024-embedding-vector-blob.ts +74 -0
  240. package/src/memory/migrations/026a-embeddings-nullable-vector-json.ts +82 -0
  241. package/src/memory/migrations/036-normalize-phone-identities.ts +11 -0
  242. package/src/memory/migrations/116-messages-fts.ts +106 -1
  243. package/src/memory/migrations/126-backfill-guardian-principal-id.ts +52 -0
  244. package/src/memory/migrations/127-guardian-principal-id-not-null.ts +77 -0
  245. package/src/memory/migrations/134-contacts-notes-column.ts +13 -0
  246. package/src/memory/migrations/135-backfill-contact-interaction-stats.ts +20 -0
  247. package/src/memory/migrations/136-drop-assistant-id-columns.ts +52 -0
  248. package/src/memory/migrations/140-backfill-usage-cache-accounting.ts +13 -0
  249. package/src/memory/migrations/141-rename-verification-table.ts +54 -0
  250. package/src/memory/migrations/142-rename-verification-session-id-column.ts +25 -0
  251. package/src/memory/migrations/143-rename-guardian-verification-values.ts +35 -0
  252. package/src/memory/migrations/144-rename-voice-to-phone.ts +136 -0
  253. package/src/memory/migrations/145-drop-accounts-table.ts +32 -0
  254. package/src/memory/migrations/147-migrate-reminders-to-schedules.ts +14 -1
  255. package/src/memory/migrations/148-drop-reminders-table.ts +35 -1
  256. package/src/memory/migrations/150-oauth-apps-client-secret-path.ts +69 -1
  257. package/src/memory/migrations/162-guardian-timestamps-epoch-ms.ts +290 -0
  258. package/src/memory/migrations/169-rename-gmail-provider-key-to-google.ts +51 -1
  259. package/src/memory/migrations/174-rename-thread-starters-table.ts +47 -1
  260. package/src/memory/migrations/176-drop-capability-card-state.ts +13 -0
  261. package/src/memory/migrations/180-backfill-inline-attachments-to-disk.ts +16 -0
  262. package/src/memory/migrations/181-rename-thread-starters-checkpoints.ts +28 -1
  263. package/src/memory/migrations/190-call-session-skip-disclosure.ts +15 -0
  264. package/src/memory/migrations/191-backfill-audio-attachment-mime-types.ts +64 -0
  265. package/src/memory/migrations/192-contacts-user-file-column.ts +15 -0
  266. package/src/memory/migrations/193-add-source-type-columns.ts +81 -0
  267. package/src/memory/migrations/index.ts +5 -0
  268. package/src/memory/migrations/registry.ts +98 -0
  269. package/src/memory/migrations/validate-migration-state.ts +137 -11
  270. package/src/memory/qdrant-circuit-breaker.ts +9 -0
  271. package/src/memory/qdrant-manager.ts +64 -7
  272. package/src/memory/retriever.test.ts +37 -25
  273. package/src/memory/retriever.ts +24 -49
  274. package/src/memory/schema/calls.ts +1 -0
  275. package/src/memory/schema/contacts.ts +1 -0
  276. package/src/memory/schema/memory-core.ts +2 -0
  277. package/src/memory/search/formatting.ts +7 -44
  278. package/src/memory/search/staleness.ts +4 -0
  279. package/src/memory/search/tier-classifier.ts +10 -2
  280. package/src/memory/search/types.ts +2 -5
  281. package/src/memory/task-memory-cleanup.ts +4 -3
  282. package/src/notifications/adapters/slack.ts +168 -6
  283. package/src/notifications/broadcaster.ts +1 -0
  284. package/src/notifications/copy-composer.ts +59 -2
  285. package/src/notifications/decision-engine.ts +4 -1
  286. package/src/notifications/signal.ts +2 -0
  287. package/src/notifications/types.ts +2 -0
  288. package/src/oauth/connection-resolver.ts +6 -4
  289. package/src/permissions/checker.ts +0 -38
  290. package/src/permissions/shell-identity.ts +76 -22
  291. package/src/permissions/types.ts +4 -2
  292. package/src/platform/client.ts +35 -7
  293. package/src/prompts/journal-context.ts +133 -0
  294. package/src/prompts/persona-resolver.ts +194 -0
  295. package/src/prompts/system-prompt.ts +44 -4
  296. package/src/prompts/templates/SOUL.md +10 -0
  297. package/src/prompts/templates/users/default.md +1 -0
  298. package/src/providers/provider-send-message.ts +3 -32
  299. package/src/providers/registry.ts +29 -179
  300. package/src/providers/types.ts +1 -1
  301. package/src/runtime/access-request-helper.ts +4 -0
  302. package/src/runtime/auth/__tests__/credential-service.test.ts +0 -1
  303. package/src/runtime/auth/__tests__/external-assistant-id.test.ts +13 -68
  304. package/src/runtime/auth/__tests__/guard-tests.test.ts +9 -50
  305. package/src/runtime/auth/external-assistant-id.ts +13 -59
  306. package/src/runtime/auth/route-policy.ts +17 -1
  307. package/src/runtime/auth/token-service.ts +43 -138
  308. package/src/runtime/channel-readiness-service.ts +1 -16
  309. package/src/runtime/gateway-client.ts +47 -4
  310. package/src/runtime/guardian-decision-types.ts +45 -4
  311. package/src/runtime/http-server.ts +31 -3
  312. package/src/runtime/middleware/error-handler.ts +1 -9
  313. package/src/runtime/routes/access-request-decision.ts +2 -2
  314. package/src/runtime/routes/app-management-routes.ts +2 -1
  315. package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +219 -30
  316. package/src/runtime/routes/approval-strategies/guardian-text-engine-strategy.ts +37 -14
  317. package/src/runtime/routes/audio-routes.ts +40 -0
  318. package/src/runtime/routes/btw-routes.ts +0 -17
  319. package/src/runtime/routes/channel-readiness-routes.ts +9 -4
  320. package/src/runtime/routes/conversation-query-routes.ts +63 -1
  321. package/src/runtime/routes/conversation-routes.ts +4 -44
  322. package/src/runtime/routes/debug-routes.ts +12 -9
  323. package/src/runtime/routes/diagnostics-routes.ts +1 -477
  324. package/src/runtime/routes/guardian-approval-interception.ts +168 -11
  325. package/src/runtime/routes/guardian-approval-prompt.ts +6 -1
  326. package/src/runtime/routes/guardian-approval-reply-helpers.ts +103 -21
  327. package/src/runtime/routes/identity-routes.ts +19 -30
  328. package/src/runtime/routes/inbound-message-handler.ts +31 -1
  329. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +64 -5
  330. package/src/runtime/routes/inbound-stages/background-dispatch.ts +52 -40
  331. package/src/runtime/routes/inbound-stages/secret-ingress-check.ts +4 -33
  332. package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +1 -1
  333. package/src/runtime/routes/integrations/twilio.ts +52 -10
  334. package/src/runtime/routes/integrations/vercel.ts +89 -0
  335. package/src/runtime/routes/log-export-routes.ts +5 -0
  336. package/src/runtime/routes/memory-item-routes.test.ts +3 -3
  337. package/src/runtime/routes/memory-item-routes.ts +46 -14
  338. package/src/runtime/routes/migration-rollback-routes.ts +209 -0
  339. package/src/runtime/routes/migration-routes.ts +17 -1
  340. package/src/runtime/routes/notification-routes.ts +58 -0
  341. package/src/runtime/routes/schedule-routes.ts +65 -0
  342. package/src/runtime/routes/secret-routes.ts +141 -10
  343. package/src/runtime/routes/settings-routes.ts +41 -1
  344. package/src/runtime/routes/tts-routes.ts +96 -0
  345. package/src/runtime/routes/upgrade-broadcast-routes.ts +26 -2
  346. package/src/runtime/routes/workspace-commit-routes.ts +62 -0
  347. package/src/runtime/routes/workspace-routes.test.ts +22 -1
  348. package/src/runtime/routes/workspace-routes.ts +1 -1
  349. package/src/runtime/routes/workspace-utils.ts +86 -2
  350. package/src/security/ces-credential-client.ts +75 -29
  351. package/src/security/ces-rpc-credential-backend.ts +86 -0
  352. package/src/security/credential-backend.ts +22 -92
  353. package/src/security/keychain-broker-client.ts +10 -2
  354. package/src/security/secure-keys.ts +113 -115
  355. package/src/skills/catalog-install.ts +6 -32
  356. package/src/skills/skill-memory.ts +1 -0
  357. package/src/subagent/manager.ts +2 -5
  358. package/src/telemetry/usage-telemetry-reporter.ts +4 -2
  359. package/src/tools/acp/spawn.ts +78 -1
  360. package/src/tools/calls/call-start.ts +1 -0
  361. package/src/tools/credentials/vault.ts +5 -3
  362. package/src/tools/executor.ts +0 -4
  363. package/src/tools/memory/definitions.ts +3 -2
  364. package/src/tools/memory/handlers.ts +10 -7
  365. package/src/tools/network/script-proxy/session-manager.ts +19 -4
  366. package/src/tools/network/web-fetch.ts +3 -1
  367. package/src/tools/skills/execute.ts +1 -1
  368. package/src/tools/terminal/safe-env.ts +1 -0
  369. package/src/tools/types.ts +0 -8
  370. package/src/util/browser.ts +15 -0
  371. package/src/util/errors.ts +0 -12
  372. package/src/util/platform.ts +4 -51
  373. package/src/workspace/git-service.ts +5 -2
  374. package/src/workspace/migrations/001-avatar-rename.ts +15 -0
  375. package/src/workspace/migrations/003-seed-device-id.ts +17 -1
  376. package/src/workspace/migrations/004-extract-collect-usage-data.ts +33 -0
  377. package/src/workspace/migrations/005-add-send-diagnostics.ts +3 -0
  378. package/src/workspace/migrations/006-services-config.ts +49 -0
  379. package/src/workspace/migrations/007-web-search-provider-rename.ts +27 -0
  380. package/src/workspace/migrations/008-voice-timeout-and-max-steps.ts +3 -0
  381. package/src/workspace/migrations/009-backfill-conversation-disk-view.ts +4 -0
  382. package/src/workspace/migrations/010-app-dir-rename.ts +78 -0
  383. package/src/workspace/migrations/011-backfill-installation-id.ts +11 -0
  384. package/src/workspace/migrations/012-rename-conversation-disk-view-dirs.ts +44 -0
  385. package/src/workspace/migrations/013-repair-conversation-disk-view.ts +5 -0
  386. package/src/workspace/migrations/015-migrate-credentials-to-keychain.ts +153 -0
  387. package/src/workspace/migrations/016-extract-feature-flags-to-protected.ts +156 -0
  388. package/src/workspace/migrations/016-migrate-credentials-from-keychain.ts +150 -0
  389. package/src/workspace/migrations/017-seed-persona-dirs.ts +96 -0
  390. package/src/workspace/migrations/018-rekey-compound-credential-keys.ts +184 -0
  391. package/src/workspace/migrations/019-scope-journal-to-guardian.ts +103 -0
  392. package/src/workspace/migrations/migrate-to-workspace-volume.ts +27 -5
  393. package/src/workspace/migrations/registry.ts +12 -0
  394. package/src/workspace/migrations/runner.ts +106 -2
  395. package/src/workspace/migrations/types.ts +4 -0
  396. package/src/workspace/provider-commit-message-generator.ts +12 -21
  397. package/src/__tests__/claude-code-skill-regression.test.ts +0 -206
  398. package/src/__tests__/claude-code-tool-profiles.test.ts +0 -99
  399. package/src/__tests__/diagnostics-export.test.ts +0 -288
  400. package/src/__tests__/local-gateway-health.test.ts +0 -209
  401. package/src/__tests__/provider-fail-open-selection.test.ts +0 -271
  402. package/src/__tests__/provider-failover-actual-provider.test.ts +0 -66
  403. package/src/__tests__/secret-ingress-handler.test.ts +0 -120
  404. package/src/__tests__/swarm-conversation-integration.test.ts +0 -358
  405. package/src/__tests__/swarm-dag-pathological.test.ts +0 -547
  406. package/src/__tests__/swarm-orchestrator.test.ts +0 -463
  407. package/src/__tests__/swarm-plan-validator.test.ts +0 -384
  408. package/src/__tests__/swarm-recursion.test.ts +0 -197
  409. package/src/__tests__/swarm-router-planner.test.ts +0 -234
  410. package/src/__tests__/swarm-tool.test.ts +0 -185
  411. package/src/__tests__/swarm-worker-backend.test.ts +0 -144
  412. package/src/__tests__/swarm-worker-runner.test.ts +0 -288
  413. package/src/commands/__tests__/cc-command-registry.test.ts +0 -396
  414. package/src/commands/cc-command-registry.ts +0 -248
  415. package/src/config/bundled-skills/claude-code/SKILL.md +0 -53
  416. package/src/config/bundled-skills/claude-code/TOOLS.json +0 -47
  417. package/src/config/bundled-skills/claude-code/tools/claude-code.ts +0 -12
  418. package/src/config/bundled-skills/orchestration/SKILL.md +0 -33
  419. package/src/config/bundled-skills/orchestration/TOOLS.json +0 -35
  420. package/src/config/bundled-skills/orchestration/tools/swarm-delegate.ts +0 -12
  421. package/src/config/schemas/swarm.ts +0 -82
  422. package/src/logfire.ts +0 -135
  423. package/src/memory/search/lexical.ts +0 -48
  424. package/src/providers/failover.ts +0 -186
  425. package/src/runtime/local-gateway-health.ts +0 -275
  426. package/src/security/secret-ingress.ts +0 -68
  427. package/src/swarm/backend-claude-code.ts +0 -225
  428. package/src/swarm/checkpoint.ts +0 -137
  429. package/src/swarm/graph-utils.ts +0 -53
  430. package/src/swarm/index.ts +0 -55
  431. package/src/swarm/limits.ts +0 -66
  432. package/src/swarm/orchestrator.ts +0 -424
  433. package/src/swarm/plan-validator.ts +0 -117
  434. package/src/swarm/router-planner.ts +0 -162
  435. package/src/swarm/router-prompts.ts +0 -39
  436. package/src/swarm/synthesizer.ts +0 -81
  437. package/src/swarm/types.ts +0 -72
  438. package/src/swarm/worker-backend.ts +0 -131
  439. package/src/swarm/worker-prompts.ts +0 -80
  440. package/src/swarm/worker-runner.ts +0 -170
  441. package/src/tools/claude-code/claude-code.ts +0 -610
  442. package/src/tools/swarm/delegate.ts +0 -205
@@ -1,21 +1,24 @@
1
- import { and, eq, like, sql } from "drizzle-orm";
1
+ import { and, desc, eq, like, sql } from "drizzle-orm";
2
2
  import { v4 as uuid } from "uuid";
3
3
 
4
4
  import { getConfig } from "../config/loader.js";
5
5
  import type { MemoryExtractionConfig } from "../config/types.js";
6
+ import { getAssistantName } from "../daemon/identity-helpers.js";
7
+ import { resolveGuardianPersona } from "../prompts/persona-resolver.js";
6
8
  import { buildCoreIdentityContext } from "../prompts/system-prompt.js";
7
9
  import {
8
- createTimeout,
9
10
  extractToolUse,
10
11
  getConfiguredProvider,
11
12
  userMessage,
12
13
  } from "../providers/provider-send-message.js";
14
+ import { BackendUnavailableError } from "../util/errors.js";
13
15
  import { getLogger } from "../util/logger.js";
14
16
  import { truncate } from "../util/truncate.js";
15
17
  import { maybeEnqueueConversationStartersJob } from "./conversation-starters-cadence.js";
16
18
  import { getDb } from "./db.js";
17
19
  import { computeMemoryFingerprint } from "./fingerprint.js";
18
20
  import { enqueueMemoryJob } from "./jobs-store.js";
21
+ import { upsertJournalMemoriesFromDisk } from "./journal-memory.js";
19
22
  import { extractTextFromStoredMessageContent } from "./message-content.js";
20
23
  import { withQdrantBreaker } from "./qdrant-circuit-breaker.js";
21
24
  import { getQdrantClient } from "./qdrant-client.js";
@@ -31,7 +34,8 @@ export type MemoryItemKind =
31
34
  | "project"
32
35
  | "decision"
33
36
  | "constraint"
34
- | "event";
37
+ | "event"
38
+ | "journal";
35
39
 
36
40
  export type OverrideConfidence = "explicit" | "tentative" | "inferred";
37
41
 
@@ -55,8 +59,16 @@ const VALID_KINDS = new Set<string>([
55
59
  "decision",
56
60
  "constraint",
57
61
  "event",
62
+ "journal",
58
63
  ]);
59
64
 
65
+ /**
66
+ * Kinds the LLM is allowed to produce during extraction. Excludes "journal"
67
+ * because journal memories are created directly from disk files — any
68
+ * LLM-produced journal items would be silently dropped, wasting tokens.
69
+ */
70
+ const EXTRACTION_KINDS = [...VALID_KINDS].filter((k) => k !== "journal");
71
+
60
72
  /** Maps old kind names to their new equivalents for graceful migration. */
61
73
  const KIND_MIGRATION_MAP: Record<string, MemoryItemKind> = {
62
74
  profile: "identity",
@@ -150,6 +162,7 @@ function buildExtractionSystemPrompt(
150
162
  statement: string;
151
163
  }>,
152
164
  messageRole: string,
165
+ userPersona?: string | null,
153
166
  ): string {
154
167
  // Build the fixed instruction body first so we can measure it and allocate
155
168
  // the remaining budget to identity context.
@@ -182,17 +195,57 @@ For each item, provide:
182
195
  Rules:
183
196
  - Only extract genuinely memorable information. Skip pleasantries, filler, and transient discussion.
184
197
  - Do NOT extract information about what tools the assistant used or what files it read — only extract substantive facts about the user, their projects, and their preferences.
185
- - Do NOT extract claims about actions the assistant performed, outcomes it achieved, or progress it reported (e.g., "I booked an appointment", "I sent the email"). Only extract facts stated by the user or from external sources — the assistant's self-reports are not reliable memory material.
186
198
  - Do NOT extract raw code snippets, JSON fragments, YAML, configuration values, log output, or data structures. Only extract the human-readable meaning or intent behind such content, not the literal syntax.
187
199
  - Prefer fewer high-quality items over many low-quality ones.
188
- - If the message contains no memorable information, return an empty array.`;
200
+ - If the message contains no memorable information, return an empty array.
201
+ - The preceding conversation context (if provided) is for disambiguation only. Extract items ONLY from the final message after the --- separator, not from the context messages.`;
202
+
203
+ // Try to extract user name from persona text
204
+ let userName = "the user";
205
+ if (userPersona) {
206
+ const nameMatch = userPersona.match(/\*\*Name:\*\*\s*(.+)/);
207
+ if (nameMatch) {
208
+ userName = nameMatch[1].trim();
209
+ }
210
+ }
189
211
 
190
212
  if (messageRole === "assistant") {
191
213
  instructions += `
192
214
 
193
- IMPORTANT: The message below is from the ASSISTANT, not the user. Do NOT attribute the assistant's own statements, feelings, self-descriptions, or introspection to the user. Only extract facts about the user, the world, or the project that the assistant is referencing or relaying — NOT the assistant's own identity, uncertainty, or behavior. If the assistant is simply talking about itself (e.g., introducing itself, expressing uncertainty about its own purpose), extract nothing.`;
215
+ IMPORTANT: The message below is from the ASSISTANT. You may extract facts about actions taken, decisions made, and outcomes achieved. However, do NOT attribute the assistant's own identity, personality, or self-descriptions to the user. If the assistant is just introducing itself or expressing uncertainty about its own nature, extract nothing.`;
194
216
  }
195
217
 
218
+ instructions += `
219
+
220
+ ## Examples
221
+
222
+ Good extractions from user messages:
223
+ - "I'm a backend engineer at Acme Corp, mostly working with Go and PostgreSQL"
224
+ → kind: identity, subject: "Role at Acme Corp", statement: "${userName} is a backend engineer at Acme Corp, works primarily with Go and PostgreSQL"
225
+
226
+ - "Always use semantic commits in this repo. I hate squash merges."
227
+ → kind: constraint, subject: "Git conventions", statement: "${userName} requires semantic commit messages. Strongly dislikes squash merges."
228
+
229
+ - "We decided to go with Redis for the cache layer because DynamoDB was too expensive at our read volume"
230
+ → kind: decision, subject: "Cache layer choice", statement: "${userName} chose Redis over DynamoDB for caching due to cost at high read volumes"
231
+
232
+ Good extractions from assistant messages:
233
+ - "Based on your earlier mention, I see you're using Next.js 14 with the app router for the dashboard project."
234
+ → kind: project, subject: "Dashboard tech stack", statement: "${userName}'s dashboard project uses Next.js 14 with the app router"
235
+
236
+ - "Since you mentioned your team follows trunk-based development, I'll keep the changes in a single commit."
237
+ → kind: constraint, subject: "Team branching strategy", statement: "${userName}'s team follows trunk-based development"
238
+
239
+ - "I've refactored the auth middleware to use JWT validation and added rate limiting to the login endpoint."
240
+ → kind: project, subject: "Auth middleware changes", statement: "Auth middleware was refactored to use JWT validation with rate limiting on the login endpoint"
241
+
242
+ Do NOT extract:
243
+ - "I'll check that file for you" → assistant operational statement with no lasting information
244
+ - "I think the best approach would be to refactor this" → speculative, no action taken yet
245
+ - "The tests passed" → transient status
246
+ - "Sure, sounds good" → filler
247
+ - "\`\`\`json {"key": "val"} \`\`\`" → raw code/data, extract meaning not syntax`;
248
+
196
249
  if (existingItems.length > 0) {
197
250
  instructions += `\n\nExisting memory items (use these to identify supersession targets — set \`supersedes\` to the item ID if the new information replaces one of these):\n`;
198
251
  for (const item of existingItems) {
@@ -204,9 +257,10 @@ IMPORTANT: The message below is from the ASSISTANT, not the user. Do NOT attribu
204
257
  // generic "User ..." labels. Budget is dynamically computed: whatever
205
258
  // remains after the fixed instructions fits within the system prompt
206
259
  // ceiling, preventing oversized prompts from exceeding the provider input
207
- // window (which would cause sendMessage to error and fall back to
208
- // lower-quality pattern-based extraction).
209
- const rawIdentityContext = buildCoreIdentityContext();
260
+ // window (which would cause sendMessage to error).
261
+ const rawIdentityContext = buildCoreIdentityContext(
262
+ userPersona ? { userPersona } : undefined,
263
+ );
210
264
 
211
265
  let prompt = "";
212
266
  if (rawIdentityContext) {
@@ -316,186 +370,200 @@ async function extractItemsWithLLM(
316
370
  extractionConfig: MemoryExtractionConfig,
317
371
  scopeId: string,
318
372
  messageRole: string,
373
+ precedingMessages: Array<{ role: string; content: string }>,
374
+ userPersona?: string | null,
319
375
  ): Promise<ExtractedItem[]> {
320
376
  const provider = await getConfiguredProvider();
321
377
  if (!provider) {
322
- log.debug(
323
- "Configured provider unavailable for LLM extraction, falling back to pattern-based",
378
+ throw new BackendUnavailableError(
379
+ "Provider unavailable for memory extraction",
324
380
  );
325
- return extractItemsPatternBased(text, scopeId);
326
381
  }
327
382
 
328
- try {
329
- const { signal, cleanup } = createTimeout(15000);
330
-
331
- try {
332
- // Query existing items to give the LLM supersession context
333
- const existingItems = queryExistingItemsForContext(scopeId, text);
334
- const systemPrompt = buildExtractionSystemPrompt(
335
- existingItems,
336
- messageRole,
337
- );
383
+ // Query existing items to give the LLM supersession context
384
+ const existingItems = queryExistingItemsForContext(scopeId, text);
385
+ const systemPrompt = buildExtractionSystemPrompt(
386
+ existingItems,
387
+ messageRole,
388
+ userPersona,
389
+ );
390
+
391
+ const assistantName = getAssistantName() ?? "the assistant";
392
+ const messagePrefix =
393
+ messageRole === "assistant"
394
+ ? `[This message is from ${assistantName}]\n\n`
395
+ : `[This message is from the user]\n\n`;
396
+
397
+ // Build user content with optional preceding conversation context
398
+ const contextParts: string[] = [];
399
+ for (const msg of precedingMessages) {
400
+ const msgText = extractTextFromStoredMessageContent(msg.content);
401
+ if (msgText.length === 0) continue;
402
+ const roleLabel =
403
+ msg.role === "assistant"
404
+ ? (getAssistantName() ?? "assistant")
405
+ : "user";
406
+ contextParts.push(`[${roleLabel}]: ${msgText}`);
407
+ }
408
+ let userContent = `${messagePrefix}${text}`;
409
+ if (contextParts.length > 0) {
410
+ userContent = `Preceding conversation context:\n${contextParts.join("\n\n")}\n\n---\n\nMessage to extract from:\n${messagePrefix}${text}`;
411
+ }
338
412
 
339
- const messagePrefix =
340
- messageRole === "assistant"
341
- ? "[This message is from the assistant]\n\n"
342
- : "";
343
- const response = await provider.sendMessage(
344
- [userMessage(`${messagePrefix}${text}`)],
345
- [
346
- {
347
- name: "store_memory_items",
348
- description: "Store extracted memory items from the message",
349
- input_schema: {
350
- type: "object" as const,
351
- properties: {
352
- items: {
353
- type: "array",
354
- items: {
355
- type: "object",
356
- properties: {
357
- kind: {
358
- type: "string",
359
- enum: [...VALID_KINDS],
360
- description: "Category of memory item",
361
- },
362
- subject: {
363
- type: "string",
364
- description:
365
- "Short label (2-8 words) for what this is about",
366
- },
367
- statement: {
368
- type: "string",
369
- description:
370
- "Relationship-rich factual statement to remember (1-2 sentences). Include relational context.",
371
- },
372
- confidence: {
373
- type: "number",
374
- description:
375
- "Confidence that this is accurate (0.0-1.0)",
376
- },
377
- importance: {
378
- type: "number",
379
- description:
380
- "How valuable this is to remember (0.0-1.0)",
381
- },
382
- supersedes: {
383
- type: ["string", "null"],
384
- description:
385
- "ID of the existing memory item this replaces, or null if not replacing anything",
386
- },
387
- overrideConfidence: {
388
- type: "string",
389
- enum: ["explicit", "tentative", "inferred"],
390
- description:
391
- "How confident you are that this overrides an existing item: explicit (clear override), tentative (ambiguous), inferred (weak signal)",
392
- },
393
- },
394
- required: [
395
- "kind",
396
- "subject",
397
- "statement",
398
- "confidence",
399
- "importance",
400
- "supersedes",
401
- "overrideConfidence",
402
- ],
413
+ const response = await provider.sendMessage(
414
+ [userMessage(userContent)],
415
+ [
416
+ {
417
+ name: "store_memory_items",
418
+ description: "Store extracted memory items from the message",
419
+ input_schema: {
420
+ type: "object" as const,
421
+ properties: {
422
+ items: {
423
+ type: "array",
424
+ items: {
425
+ type: "object",
426
+ properties: {
427
+ kind: {
428
+ type: "string",
429
+ enum: EXTRACTION_KINDS,
430
+ description: "Category of memory item",
431
+ },
432
+ subject: {
433
+ type: "string",
434
+ description:
435
+ "Short label (2-8 words) for what this is about",
436
+ },
437
+ statement: {
438
+ type: "string",
439
+ description:
440
+ "Relationship-rich factual statement to remember (1-2 sentences). Include relational context.",
441
+ },
442
+ confidence: {
443
+ type: "number",
444
+ description: "Confidence that this is accurate (0.0-1.0)",
445
+ },
446
+ importance: {
447
+ type: "number",
448
+ description: "How valuable this is to remember (0.0-1.0)",
449
+ },
450
+ supersedes: {
451
+ type: ["string", "null"],
452
+ description:
453
+ "ID of the existing memory item this replaces, or null if not replacing anything",
454
+ },
455
+ overrideConfidence: {
456
+ type: "string",
457
+ enum: ["explicit", "tentative", "inferred"],
458
+ description:
459
+ "How confident you are that this overrides an existing item: explicit (clear override), tentative (ambiguous), inferred (weak signal)",
403
460
  },
404
461
  },
462
+ required: [
463
+ "kind",
464
+ "subject",
465
+ "statement",
466
+ "confidence",
467
+ "importance",
468
+ "supersedes",
469
+ "overrideConfidence",
470
+ ],
405
471
  },
406
- required: ["items"],
407
472
  },
408
473
  },
409
- ],
410
- systemPrompt,
411
- {
412
- config: {
413
- modelIntent: extractionConfig.modelIntent,
414
- max_tokens: 1024,
415
- tool_choice: { type: "tool" as const, name: "store_memory_items" },
416
- },
417
- signal,
474
+ required: ["items"],
418
475
  },
419
- );
420
- cleanup();
476
+ },
477
+ ],
478
+ systemPrompt,
479
+ {
480
+ config: {
481
+ modelIntent: extractionConfig.modelIntent,
482
+ tool_choice: { type: "tool" as const, name: "store_memory_items" },
483
+ },
484
+ },
485
+ );
421
486
 
422
- const toolBlock = extractToolUse(response);
423
- if (!toolBlock) {
424
- log.warn(
425
- "No tool_use block in LLM extraction response, falling back to pattern-based",
426
- );
427
- return extractItemsPatternBased(text, scopeId);
428
- }
487
+ const toolBlock = extractToolUse(response);
488
+ if (!toolBlock) {
489
+ throw new Error("No tool_use block in LLM extraction response");
490
+ }
429
491
 
430
- const input = toolBlock.input as { items?: LLMExtractedItem[] };
431
- if (!Array.isArray(input.items)) {
432
- log.warn(
433
- "Invalid items in LLM extraction response, falling back to pattern-based",
434
- );
435
- return extractItemsPatternBased(text, scopeId);
436
- }
492
+ const input = toolBlock.input as { items?: LLMExtractedItem[] };
493
+ if (!Array.isArray(input.items)) {
494
+ throw new Error("Invalid items structure in LLM extraction response");
495
+ }
437
496
 
438
- // Build set of known existing item IDs for supersession validation
439
- const existingItemIds = new Set(existingItems.map((e) => e.id));
440
-
441
- const items: ExtractedItem[] = [];
442
- for (const raw of input.items) {
443
- // Apply kind migration map for old kind names, then validate
444
- const resolvedKind = KIND_MIGRATION_MAP[raw.kind] ?? raw.kind;
445
- if (!VALID_KINDS.has(resolvedKind)) continue;
446
- if (!raw.subject || !raw.statement) continue;
447
- const subject = truncate(String(raw.subject), 80, "");
448
- const statement = truncate(String(raw.statement), 500, "");
449
- const confidence = clampUnitInterval(parseScore(raw.confidence, 0.5));
450
- const importance = clampUnitInterval(parseScore(raw.importance, 0.5));
451
- const fingerprint = computeMemoryFingerprint(
452
- scopeId,
453
- resolvedKind,
454
- subject,
455
- statement,
456
- );
497
+ // Build set of known existing item IDs for supersession validation
498
+ const existingItemIds = new Set(existingItems.map((e) => e.id));
457
499
 
458
- // Validate supersedes: must reference a known existing item ID.
459
- // Reject hallucinated IDs that don't match any item we showed the LLM.
460
- const rawSupersedes =
461
- typeof raw.supersedes === "string" && raw.supersedes.length > 0
462
- ? raw.supersedes
463
- : null;
464
- const supersedes =
465
- rawSupersedes && existingItemIds.has(rawSupersedes)
466
- ? rawSupersedes
467
- : null;
468
- const supersedesRejected = !!rawSupersedes && !supersedes;
469
- const overrideConfidence = VALID_OVERRIDE_CONFIDENCES.has(
470
- raw.overrideConfidence,
471
- )
472
- ? (raw.overrideConfidence as OverrideConfidence)
473
- : "inferred";
474
-
475
- items.push({
476
- kind: resolvedKind as MemoryItemKind,
477
- subject,
478
- statement,
479
- confidence,
480
- importance,
481
- fingerprint,
482
- supersedes,
483
- overrideConfidence,
484
- supersedesRejected,
485
- });
486
- }
500
+ const items: ExtractedItem[] = [];
501
+ for (const raw of input.items) {
502
+ // Apply kind migration map for old kind names, then validate
503
+ const resolvedKind = KIND_MIGRATION_MAP[raw.kind] ?? raw.kind;
504
+ if (resolvedKind === "journal") continue; // journal memories created directly from disk
505
+ if (!VALID_KINDS.has(resolvedKind)) continue;
506
+ if (!raw.subject || !raw.statement) continue;
507
+ const subject = String(raw.subject).trim();
508
+ const statement = String(raw.statement).trim();
509
+ const confidence = clampUnitInterval(parseScore(raw.confidence, 0.5));
510
+ const importance = clampUnitInterval(parseScore(raw.importance, 0.5));
511
+ const fingerprint = computeMemoryFingerprint(
512
+ scopeId,
513
+ resolvedKind,
514
+ subject,
515
+ statement,
516
+ );
487
517
 
488
- return deduplicateItems(items);
489
- } finally {
490
- cleanup();
491
- }
518
+ // Validate supersedes: must reference a known existing item ID.
519
+ // Reject hallucinated IDs that don't match any item we showed the LLM.
520
+ const rawSupersedes =
521
+ typeof raw.supersedes === "string" && raw.supersedes.length > 0
522
+ ? raw.supersedes
523
+ : null;
524
+ const supersedes =
525
+ rawSupersedes && existingItemIds.has(rawSupersedes)
526
+ ? rawSupersedes
527
+ : null;
528
+ const supersedesRejected = !!rawSupersedes && !supersedes;
529
+ const overrideConfidence = VALID_OVERRIDE_CONFIDENCES.has(
530
+ raw.overrideConfidence,
531
+ )
532
+ ? (raw.overrideConfidence as OverrideConfidence)
533
+ : "inferred";
534
+
535
+ items.push({
536
+ kind: resolvedKind as MemoryItemKind,
537
+ subject,
538
+ statement,
539
+ confidence,
540
+ importance,
541
+ fingerprint,
542
+ supersedes,
543
+ overrideConfidence,
544
+ supersedesRejected,
545
+ });
546
+ }
547
+
548
+ return deduplicateItems(items);
549
+ }
550
+
551
+ /**
552
+ * Fire conversation starters generation when journal memories were created.
553
+ * Wrapped in try/catch so failures never propagate to the caller.
554
+ */
555
+ function triggerConversationStartersIfNeeded(
556
+ count: number,
557
+ scopeId: string,
558
+ ): void {
559
+ if (count <= 0) return;
560
+ try {
561
+ maybeEnqueueConversationStartersJob(scopeId);
492
562
  } catch (err) {
493
- const message = err instanceof Error ? err.message : String(err);
494
563
  log.warn(
495
- { err: message },
496
- "LLM extraction failed, falling back to pattern-based",
564
+ { err: err instanceof Error ? err.message : String(err) },
565
+ "Failed to check conversation starters cadence",
497
566
  );
498
- return extractItemsPatternBased(text, scopeId);
499
567
  }
500
568
  }
501
569
 
@@ -513,6 +581,7 @@ export async function extractAndUpsertMemoryItemsForMessage(
513
581
  role: messages.role,
514
582
  content: messages.content,
515
583
  createdAt: messages.createdAt,
584
+ conversationId: messages.conversationId,
516
585
  })
517
586
  .from(messages)
518
587
  .where(eq(messages.id, messageId))
@@ -520,28 +589,77 @@ export async function extractAndUpsertMemoryItemsForMessage(
520
589
 
521
590
  if (!message) return 0;
522
591
 
592
+ // Fetch up to 6 preceding messages from the same conversation for
593
+ // disambiguation context (e.g. resolving "that framework" or "yes, do it").
594
+ const effectiveConversationId = conversationId ?? message.conversationId;
595
+ const precedingMessages = effectiveConversationId
596
+ ? db
597
+ .select({ role: messages.role, content: messages.content })
598
+ .from(messages)
599
+ .where(
600
+ and(
601
+ eq(messages.conversationId, effectiveConversationId),
602
+ sql`${messages.createdAt} < ${message.createdAt}`,
603
+ ),
604
+ )
605
+ .orderBy(desc(messages.createdAt))
606
+ .limit(6)
607
+ .all()
608
+ .reverse()
609
+ : [];
610
+
611
+ const effectiveScopeId = scopeId ?? "default";
612
+
613
+ // Directly create journal memories from any journal files written during
614
+ // this message, bypassing LLM extraction (which would summarize/rewrite them).
615
+ // This must run before the extraction guards (semantic density, useLLM, etc.)
616
+ // because journal disk scanning is independent of LLM extraction.
617
+ let journalUpserted = 0;
618
+ if (message.role === "assistant") {
619
+ journalUpserted = upsertJournalMemoriesFromDisk(
620
+ message.createdAt,
621
+ effectiveScopeId,
622
+ messageId,
623
+ );
624
+ }
625
+
523
626
  const text = extractTextFromStoredMessageContent(message.content);
524
627
  if (!hasSemanticDensity(text)) {
525
628
  log.debug(
526
629
  { messageId },
527
630
  "Skipping extraction — message lacks semantic density",
528
631
  );
529
- return 0;
632
+ triggerConversationStartersIfNeeded(journalUpserted, effectiveScopeId);
633
+ return journalUpserted;
530
634
  }
531
635
 
532
636
  const config = getConfig();
533
637
  const extractionConfig = config.memory.extraction;
534
- const effectiveScopeId = scopeId ?? "default";
535
- const extracted = extractionConfig.useLLM
536
- ? await extractItemsWithLLM(
537
- text,
538
- extractionConfig,
539
- effectiveScopeId,
540
- message.role,
541
- )
542
- : extractItemsPatternBased(text, effectiveScopeId);
543
638
 
544
- if (extracted.length === 0) return 0;
639
+ // Resolve the guardian's persona to provide personality-aware extraction
640
+ // context. Currently uses the guardian persona for all conversations —
641
+ // non-guardian conversations are rare and the guardian's persona provides
642
+ // better extraction context than none.
643
+ const userPersona = resolveGuardianPersona();
644
+
645
+ if (!extractionConfig.useLLM) {
646
+ triggerConversationStartersIfNeeded(journalUpserted, effectiveScopeId);
647
+ return journalUpserted;
648
+ }
649
+
650
+ const extracted = await extractItemsWithLLM(
651
+ text,
652
+ extractionConfig,
653
+ effectiveScopeId,
654
+ message.role,
655
+ precedingMessages,
656
+ userPersona,
657
+ );
658
+
659
+ if (extracted.length === 0) {
660
+ triggerConversationStartersIfNeeded(journalUpserted, effectiveScopeId);
661
+ return journalUpserted;
662
+ }
545
663
 
546
664
  // Guard: re-check after the async LLM call. The event loop yields during
547
665
  // extractItemsWithLLM, so another task could have marked the conversation
@@ -551,13 +669,10 @@ export async function extractAndUpsertMemoryItemsForMessage(
551
669
  { messageId, conversationId },
552
670
  "Skipping upsert — conversation marked failed during extraction",
553
671
  );
554
- return 0;
672
+ triggerConversationStartersIfNeeded(journalUpserted, effectiveScopeId);
673
+ return journalUpserted;
555
674
  }
556
675
 
557
- // Determine verification state from message role
558
- const verificationState =
559
- message.role === "user" ? "user_reported" : "assistant_inferred";
560
-
561
676
  let upserted = 0;
562
677
  for (const item of extracted) {
563
678
  const now = Date.now();
@@ -577,13 +692,21 @@ export async function extractAndUpsertMemoryItemsForMessage(
577
692
  let effectiveStatus: string = "active";
578
693
  if (existing) {
579
694
  memoryItemId = existing.id;
580
- // Promote verification state if re-seen from a more trusted source
581
- const promotedState =
582
- existing.verificationState === "assistant_inferred" &&
583
- verificationState === "user_reported"
584
- ? "user_reported"
585
- : existing.verificationState;
586
695
  effectiveStatus = "active";
696
+ // Preserve sourceType for tool-sourced items — extraction should not
697
+ // demote items the user explicitly saved.
698
+ const effectiveSourceType =
699
+ existing.sourceType === "tool" ? "tool" : "extraction";
700
+
701
+ // Dual-write verificationState alongside sourceType for client compat.
702
+ // Promote from assistant_inferred → user_reported when re-seen from user.
703
+ const effectiveVerificationState =
704
+ message.role === "user" || existing.verificationState === "user_reported"
705
+ ? "user_reported"
706
+ : existing.verificationState === "user_confirmed"
707
+ ? "user_confirmed"
708
+ : "assistant_inferred";
709
+
587
710
  db.update(memoryItems)
588
711
  .set({
589
712
  status: effectiveStatus,
@@ -594,7 +717,9 @@ export async function extractAndUpsertMemoryItemsForMessage(
594
717
  Math.max(existing.importance ?? 0, item.importance),
595
718
  ),
596
719
  lastSeenAt: Math.max(existing.lastSeenAt, seenAt),
597
- verificationState: promotedState,
720
+ sourceType: effectiveSourceType,
721
+ sourceMessageRole: message.role,
722
+ verificationState: effectiveVerificationState,
598
723
  })
599
724
  .where(eq(memoryItems.id, existing.id))
600
725
  .run();
@@ -610,7 +735,11 @@ export async function extractAndUpsertMemoryItemsForMessage(
610
735
  confidence: item.confidence,
611
736
  importance: item.importance,
612
737
  fingerprint: item.fingerprint,
613
- verificationState,
738
+ sourceType: "extraction",
739
+ sourceMessageRole: message.role,
740
+ // Dual-write verificationState for client compat
741
+ verificationState:
742
+ message.role === "user" ? "user_reported" : "assistant_inferred",
614
743
  scopeId: effectiveScopeId,
615
744
  firstSeenAt: message.createdAt,
616
745
  lastSeenAt: seenAt,
@@ -701,11 +830,9 @@ export async function extractAndUpsertMemoryItemsForMessage(
701
830
  }
702
831
 
703
832
  // Fallback subject-match supersession: only when the LLM did not
704
- // explicitly handle supersession for this item. This preserves the
705
- // original behavior for pattern-based extraction and items without
706
- // LLM-directed supersession. Skip items whose supersedes ID was
707
- // rejected (hallucinated) — they should coexist, not trigger
708
- // subject-based replacement.
833
+ // explicitly handle supersession for this item. Skip items whose
834
+ // supersedes ID was rejected (hallucinated) they should coexist,
835
+ // not trigger subject-based replacement.
709
836
  if (
710
837
  !item.supersedes &&
711
838
  !item.supersedesRejected &&
@@ -730,7 +857,7 @@ export async function extractAndUpsertMemoryItemsForMessage(
730
857
  .values({
731
858
  memoryItemId,
732
859
  messageId,
733
- evidence: truncate(item.statement, 500, ""),
860
+ evidence: item.statement,
734
861
  createdAt: now,
735
862
  })
736
863
  .onConflictDoNothing()
@@ -739,139 +866,19 @@ export async function extractAndUpsertMemoryItemsForMessage(
739
866
  enqueueMemoryJob("embed_item", { itemId: memoryItemId });
740
867
  }
741
868
 
869
+ upserted += journalUpserted;
870
+
742
871
  log.debug(
743
872
  { messageId, extracted: extracted.length, upserted },
744
873
  "Extracted memory items from message",
745
874
  );
746
875
 
747
876
  // Trigger conversation starters generation when new items are upserted
748
- if (upserted > 0) {
749
- try {
750
- maybeEnqueueConversationStartersJob(effectiveScopeId);
751
- } catch (err) {
752
- log.warn(
753
- { err: err instanceof Error ? err.message : String(err) },
754
- "Failed to check conversation starters cadence",
755
- );
756
- }
757
- }
877
+ triggerConversationStartersIfNeeded(upserted, effectiveScopeId);
758
878
 
759
879
  return upserted;
760
880
  }
761
881
 
762
- // ── Pattern-based extraction (fallback) ────────────────────────────────
763
-
764
- function extractItemsPatternBased(
765
- text: string,
766
- scopeId: string = "default",
767
- ): ExtractedItem[] {
768
- const sentences = text
769
- .split(/[\n\r]+|(?<=[.!?])\s+/)
770
- .map((s) => s.trim())
771
- .filter((s) => s.length >= 20 && s.length <= 500);
772
-
773
- const items: ExtractedItem[] = [];
774
- for (const sentence of sentences) {
775
- const lower = sentence.toLowerCase();
776
- const classification = classifySentence(lower);
777
- if (!classification) continue;
778
- const subject = inferSubject(sentence, classification.kind);
779
- const statement = sentence.replace(/\s+/g, " ").trim();
780
- const fingerprint = computeMemoryFingerprint(
781
- scopeId,
782
- classification.kind,
783
- subject,
784
- statement,
785
- );
786
- items.push({
787
- kind: classification.kind,
788
- subject,
789
- statement,
790
- confidence: classification.confidence,
791
- importance: classification.importance,
792
- fingerprint,
793
- supersedes: null,
794
- overrideConfidence: "inferred" as OverrideConfidence,
795
- });
796
- }
797
-
798
- return deduplicateItems(items);
799
- }
800
-
801
- function classifySentence(
802
- lower: string,
803
- ): { kind: MemoryItemKind; confidence: number; importance: number } | null {
804
- if (
805
- includesAny(lower, [
806
- "i prefer",
807
- "prefer to",
808
- "favorite",
809
- "i like",
810
- "i dislike",
811
- ])
812
- ) {
813
- return { kind: "preference", confidence: 0.78, importance: 0.7 };
814
- }
815
- if (
816
- includesAny(lower, [
817
- "my name is",
818
- "i am ",
819
- "i work as",
820
- "i live in",
821
- "timezone",
822
- ])
823
- ) {
824
- return { kind: "identity", confidence: 0.72, importance: 0.8 };
825
- }
826
- if (includesAny(lower, ["project", "repository", "repo", "codebase"])) {
827
- return { kind: "project", confidence: 0.68, importance: 0.6 };
828
- }
829
- if (
830
- includesAny(lower, ["we decided", "decision", "chosen approach", "we will"])
831
- ) {
832
- return { kind: "decision", confidence: 0.75, importance: 0.7 };
833
- }
834
- if (
835
- includesAny(lower, ["todo", "to do", "next step", "follow up", "need to"])
836
- ) {
837
- return { kind: "project", confidence: 0.74, importance: 0.6 };
838
- }
839
- if (
840
- includesAny(lower, [
841
- "must",
842
- "cannot",
843
- "should not",
844
- "constraint",
845
- "requirement",
846
- ])
847
- ) {
848
- return { kind: "constraint", confidence: 0.7, importance: 0.7 };
849
- }
850
- if (includesAny(lower, ["remember", "important", "fact", "noted"])) {
851
- return { kind: "identity", confidence: 0.62, importance: 0.5 };
852
- }
853
- return null;
854
- }
855
-
856
- function inferSubject(sentence: string, kind: MemoryItemKind): string {
857
- const trimmed = sentence.trim();
858
- if (kind === "project") {
859
- const match = trimmed.match(
860
- /(?:project|repo(?:sitory)?)\s+([A-Za-z0-9._/-]{2,80})/i,
861
- );
862
- if (match) return match[1];
863
- }
864
- const words = trimmed.split(/\s+/).slice(0, 6).join(" ");
865
- return truncate(words, 80, "");
866
- }
867
-
868
- function includesAny(text: string, needles: string[]): boolean {
869
- for (const needle of needles) {
870
- if (text.includes(needle)) return true;
871
- }
872
- return false;
873
- }
874
-
875
882
  // ── Helpers ────────────────────────────────────────────────────────────
876
883
 
877
884
  function deduplicateItems(items: ExtractedItem[]): ExtractedItem[] {