@vellumai/assistant 0.4.49 → 0.4.51

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 (353) hide show
  1. package/ARCHITECTURE.md +24 -33
  2. package/README.md +3 -3
  3. package/docs/architecture/integrations.md +2 -2
  4. package/docs/architecture/keychain-broker.md +6 -6
  5. package/docs/architecture/memory.md +180 -119
  6. package/knip.json +32 -0
  7. package/package.json +3 -2
  8. package/src/__tests__/agent-loop.test.ts +3 -1
  9. package/src/__tests__/anthropic-provider.test.ts +114 -23
  10. package/src/__tests__/approval-cascade.test.ts +1 -15
  11. package/src/__tests__/approval-routes-http.test.ts +2 -0
  12. package/src/__tests__/assistant-feature-flag-guard.test.ts +0 -23
  13. package/src/__tests__/btw-routes.test.ts +61 -5
  14. package/src/__tests__/canonical-guardian-store.test.ts +95 -0
  15. package/src/__tests__/checker.test.ts +13 -0
  16. package/src/__tests__/config-schema.test.ts +1 -68
  17. package/src/__tests__/config-watcher.test.ts +8 -0
  18. package/src/__tests__/context-memory-e2e.test.ts +11 -100
  19. package/src/__tests__/conversation-routes-guardian-reply.test.ts +8 -0
  20. package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -0
  21. package/src/__tests__/credential-security-e2e.test.ts +1 -0
  22. package/src/__tests__/credential-security-invariants.test.ts +8 -7
  23. package/src/__tests__/credential-vault-unit.test.ts +23 -18
  24. package/src/__tests__/credential-vault.test.ts +30 -18
  25. package/src/__tests__/credentials-cli.test.ts +257 -82
  26. package/src/__tests__/cu-unified-flow.test.ts +532 -0
  27. package/src/__tests__/date-context.test.ts +93 -77
  28. package/src/__tests__/deterministic-verification-control-plane.test.ts +64 -0
  29. package/src/__tests__/guardian-routing-invariants.test.ts +93 -0
  30. package/src/__tests__/history-repair.test.ts +245 -0
  31. package/src/__tests__/host-cu-proxy.test.ts +165 -3
  32. package/src/__tests__/http-user-message-parity.test.ts +1 -0
  33. package/src/__tests__/inbound-invite-redemption.test.ts +36 -7
  34. package/src/__tests__/integration-status.test.ts +31 -30
  35. package/src/__tests__/invite-redemption-service.test.ts +166 -13
  36. package/src/__tests__/invite-routes-http.test.ts +166 -5
  37. package/src/__tests__/keychain-broker-client.test.ts +4 -4
  38. package/src/__tests__/list-messages-attachments.test.ts +193 -0
  39. package/src/__tests__/memory-context-benchmark.benchmark.test.ts +56 -18
  40. package/src/__tests__/memory-lifecycle-e2e.test.ts +244 -387
  41. package/src/__tests__/memory-recall-quality.test.ts +244 -407
  42. package/src/__tests__/memory-regressions.experimental.test.ts +126 -101
  43. package/src/__tests__/memory-regressions.test.ts +477 -2841
  44. package/src/__tests__/memory-retrieval.benchmark.test.ts +33 -150
  45. package/src/__tests__/memory-upsert-concurrency.test.ts +5 -244
  46. package/src/__tests__/mime-builder.test.ts +28 -0
  47. package/src/__tests__/native-web-search.test.ts +1 -0
  48. package/src/__tests__/oauth-cli.test.ts +824 -31
  49. package/src/__tests__/oauth-provider-profiles.test.ts +1 -1
  50. package/src/__tests__/oauth-store.test.ts +363 -17
  51. package/src/__tests__/qdrant-collection-migration.test.ts +53 -8
  52. package/src/__tests__/registry.test.ts +0 -1
  53. package/src/__tests__/relay-server.test.ts +55 -1
  54. package/src/__tests__/schedule-tools.test.ts +32 -0
  55. package/src/__tests__/script-proxy-certs.test.ts +1 -1
  56. package/src/__tests__/secret-onetime-send.test.ts +1 -0
  57. package/src/__tests__/secret-routes-managed-proxy.test.ts +183 -0
  58. package/src/__tests__/secure-keys.test.ts +78 -18
  59. package/src/__tests__/send-endpoint-busy.test.ts +3 -0
  60. package/src/__tests__/server-history-render.test.ts +2 -2
  61. package/src/__tests__/session-abort-tool-results.test.ts +1 -14
  62. package/src/__tests__/session-agent-loop-overflow.test.ts +1583 -0
  63. package/src/__tests__/session-agent-loop.test.ts +19 -15
  64. package/src/__tests__/session-confirmation-signals.test.ts +1 -15
  65. package/src/__tests__/session-error.test.ts +124 -2
  66. package/src/__tests__/session-history-web-search.test.ts +918 -0
  67. package/src/__tests__/session-pre-run-repair.test.ts +1 -14
  68. package/src/__tests__/session-provider-retry-repair.test.ts +25 -28
  69. package/src/__tests__/session-queue.test.ts +37 -27
  70. package/src/__tests__/session-runtime-assembly.test.ts +54 -0
  71. package/src/__tests__/session-slash-known.test.ts +1 -15
  72. package/src/__tests__/session-slash-queue.test.ts +1 -15
  73. package/src/__tests__/session-slash-unknown.test.ts +1 -15
  74. package/src/__tests__/session-workspace-cache-state.test.ts +3 -33
  75. package/src/__tests__/session-workspace-injection.test.ts +3 -37
  76. package/src/__tests__/session-workspace-tool-tracking.test.ts +3 -37
  77. package/src/__tests__/skills-install-extract.test.ts +93 -0
  78. package/src/__tests__/skills.test.ts +2 -2
  79. package/src/__tests__/skillssh-registry.test.ts +451 -0
  80. package/src/__tests__/slack-channel-config.test.ts +10 -8
  81. package/src/__tests__/trust-store.test.ts +15 -0
  82. package/src/__tests__/twilio-config.test.ts +11 -10
  83. package/src/__tests__/twilio-provider.test.ts +9 -4
  84. package/src/__tests__/voice-invite-redemption.test.ts +85 -5
  85. package/src/agent/ax-tree-compaction.test.ts +51 -0
  86. package/src/agent/loop.ts +39 -12
  87. package/src/approvals/AGENTS.md +1 -1
  88. package/src/approvals/guardian-request-resolvers.ts +14 -2
  89. package/src/bundler/compiler-tools.ts +66 -2
  90. package/src/calls/call-domain.ts +134 -3
  91. package/src/calls/call-store.ts +6 -0
  92. package/src/calls/relay-server.ts +44 -6
  93. package/src/calls/relay-setup-router.ts +17 -1
  94. package/src/calls/twilio-config.ts +5 -4
  95. package/src/calls/twilio-provider.ts +14 -9
  96. package/src/calls/twilio-rest.ts +10 -7
  97. package/src/calls/types.ts +3 -1
  98. package/src/cli/commands/config.ts +14 -9
  99. package/src/cli/commands/contacts.ts +3 -0
  100. package/src/cli/commands/credentials.ts +170 -174
  101. package/src/cli/commands/doctor.ts +11 -8
  102. package/src/cli/commands/keys.ts +9 -9
  103. package/src/cli/commands/mcp.ts +46 -59
  104. package/src/cli/commands/memory.ts +16 -165
  105. package/src/cli/commands/oauth/apps.ts +68 -10
  106. package/src/cli/commands/oauth/connections.ts +475 -105
  107. package/src/cli/commands/oauth/index.ts +3 -3
  108. package/src/cli/commands/oauth/providers.ts +18 -4
  109. package/src/cli/commands/sessions.ts +5 -2
  110. package/src/cli/commands/skills.ts +173 -1
  111. package/src/cli/http-client.ts +0 -20
  112. package/src/cli/main-screen.tsx +2 -2
  113. package/src/cli/program.ts +5 -6
  114. package/src/cli.ts +20 -22
  115. package/src/config/__tests__/feature-flag-registry-bundled.test.ts +39 -0
  116. package/src/config/bundled-skills/computer-use/TOOLS.json +1 -1
  117. package/src/config/bundled-skills/computer-use/tools/computer-use-observe.ts +12 -0
  118. package/src/config/bundled-skills/contacts/SKILL.md +35 -11
  119. package/src/config/bundled-skills/contacts/tools/google-contacts.ts +1 -1
  120. package/src/config/bundled-skills/gmail/SKILL.md +1 -1
  121. package/src/config/bundled-skills/gmail/TOOLS.json +52 -0
  122. package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +13 -3
  123. package/src/config/bundled-skills/gmail/tools/gmail-attachments.ts +9 -2
  124. package/src/config/bundled-skills/gmail/tools/gmail-draft.ts +5 -1
  125. package/src/config/bundled-skills/gmail/tools/gmail-filters.ts +5 -1
  126. package/src/config/bundled-skills/gmail/tools/gmail-follow-up.ts +5 -1
  127. package/src/config/bundled-skills/gmail/tools/gmail-forward.ts +5 -1
  128. package/src/config/bundled-skills/gmail/tools/gmail-label.ts +9 -2
  129. package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +5 -1
  130. package/src/config/bundled-skills/gmail/tools/gmail-send-draft.ts +5 -1
  131. package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +5 -1
  132. package/src/config/bundled-skills/gmail/tools/gmail-trash.ts +5 -1
  133. package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +5 -1
  134. package/src/config/bundled-skills/gmail/tools/gmail-vacation.ts +5 -1
  135. package/src/config/bundled-skills/google-calendar/TOOLS.json +20 -0
  136. package/src/config/bundled-skills/google-calendar/tools/calendar-check-availability.ts +2 -1
  137. package/src/config/bundled-skills/google-calendar/tools/calendar-create-event.ts +2 -1
  138. package/src/config/bundled-skills/google-calendar/tools/calendar-get-event.ts +2 -1
  139. package/src/config/bundled-skills/google-calendar/tools/calendar-list-events.ts +2 -1
  140. package/src/config/bundled-skills/google-calendar/tools/calendar-rsvp.ts +2 -1
  141. package/src/config/bundled-skills/google-calendar/tools/shared.ts +8 -2
  142. package/src/config/bundled-skills/messaging/SKILL.md +1 -1
  143. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +2 -2
  144. package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +2 -2
  145. package/src/config/bundled-skills/messaging/tools/messaging-auth-test.ts +2 -2
  146. package/src/config/bundled-skills/messaging/tools/messaging-list-conversations.ts +2 -2
  147. package/src/config/bundled-skills/messaging/tools/messaging-mark-read.ts +2 -2
  148. package/src/config/bundled-skills/messaging/tools/messaging-read.ts +2 -2
  149. package/src/config/bundled-skills/messaging/tools/messaging-search.ts +2 -2
  150. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +2 -2
  151. package/src/config/bundled-skills/messaging/tools/messaging-sender-digest.ts +2 -2
  152. package/src/config/bundled-skills/messaging/tools/shared.ts +7 -5
  153. package/src/config/bundled-skills/slack/tools/shared.ts +1 -1
  154. package/src/config/bundled-skills/slack/tools/slack-add-reaction.ts +1 -1
  155. package/src/config/bundled-skills/slack/tools/slack-channel-details.ts +1 -1
  156. package/src/config/bundled-skills/slack/tools/slack-delete-message.ts +1 -1
  157. package/src/config/bundled-skills/slack/tools/slack-edit-message.ts +1 -1
  158. package/src/config/bundled-skills/slack/tools/slack-leave-channel.ts +1 -1
  159. package/src/config/bundled-skills/slack/tools/slack-scan-digest.ts +1 -1
  160. package/src/config/bundled-tool-registry.ts +2 -5
  161. package/src/config/loader.ts +6 -42
  162. package/src/config/schema.ts +1 -12
  163. package/src/config/schemas/memory-lifecycle.ts +0 -9
  164. package/src/config/schemas/memory-processing.ts +0 -180
  165. package/src/config/schemas/memory-retrieval.ts +32 -104
  166. package/src/config/schemas/memory.ts +0 -10
  167. package/src/config/types.ts +0 -4
  168. package/src/contacts/contact-store.ts +39 -2
  169. package/src/contacts/contacts-write.ts +9 -0
  170. package/src/context/window-manager.ts +4 -1
  171. package/src/daemon/config-watcher.ts +55 -2
  172. package/src/daemon/daemon-control.ts +1 -1
  173. package/src/daemon/date-context.ts +114 -31
  174. package/src/daemon/handlers/config-ingress.ts +2 -2
  175. package/src/daemon/handlers/config-slack-channel.ts +59 -39
  176. package/src/daemon/handlers/config-telegram.ts +23 -14
  177. package/src/daemon/handlers/session-history.ts +1 -358
  178. package/src/daemon/handlers/sessions.ts +18 -13
  179. package/src/daemon/handlers/shared.ts +3 -17
  180. package/src/daemon/handlers/skills.ts +20 -1
  181. package/src/daemon/history-repair.ts +72 -8
  182. package/src/daemon/host-cu-proxy.ts +55 -26
  183. package/src/daemon/lifecycle.ts +39 -4
  184. package/src/daemon/mcp-reload-service.ts +2 -2
  185. package/src/daemon/message-types/computer-use.ts +1 -12
  186. package/src/daemon/message-types/memory.ts +4 -16
  187. package/src/daemon/message-types/messages.ts +1 -0
  188. package/src/daemon/message-types/sessions.ts +4 -42
  189. package/src/daemon/server.ts +6 -1
  190. package/src/daemon/session-agent-loop-handlers.ts +38 -0
  191. package/src/daemon/session-agent-loop.ts +334 -48
  192. package/src/daemon/session-error.ts +89 -6
  193. package/src/daemon/session-history.ts +17 -7
  194. package/src/daemon/session-media-retry.ts +6 -2
  195. package/src/daemon/session-memory.ts +69 -149
  196. package/src/daemon/session-process.ts +10 -1
  197. package/src/daemon/session-runtime-assembly.ts +49 -19
  198. package/src/daemon/session-slash.ts +3 -5
  199. package/src/daemon/session-surfaces.ts +4 -1
  200. package/src/daemon/session-tool-setup.ts +7 -1
  201. package/src/daemon/session.ts +12 -2
  202. package/src/email/providers/index.ts +2 -2
  203. package/src/instrument.ts +61 -1
  204. package/src/media/avatar-router.ts +1 -1
  205. package/src/memory/admin.ts +2 -191
  206. package/src/memory/canonical-guardian-store.ts +38 -2
  207. package/src/memory/conversation-crud.ts +0 -33
  208. package/src/memory/conversation-queries.ts +25 -83
  209. package/src/memory/db-init.ts +32 -0
  210. package/src/memory/embedding-backend.ts +84 -8
  211. package/src/memory/embedding-types.ts +9 -1
  212. package/src/memory/indexer.ts +7 -46
  213. package/src/memory/invite-store.ts +19 -0
  214. package/src/memory/items-extractor.ts +274 -76
  215. package/src/memory/job-handlers/backfill.ts +2 -127
  216. package/src/memory/job-handlers/cleanup.ts +2 -16
  217. package/src/memory/job-handlers/extraction.ts +2 -138
  218. package/src/memory/job-handlers/index-maintenance.ts +1 -6
  219. package/src/memory/job-handlers/summarization.ts +3 -148
  220. package/src/memory/job-utils.ts +21 -59
  221. package/src/memory/jobs-store.ts +1 -159
  222. package/src/memory/jobs-worker.ts +9 -52
  223. package/src/memory/migrations/104-core-indexes.ts +3 -3
  224. package/src/memory/migrations/149-oauth-tables.ts +2 -0
  225. package/src/memory/migrations/150-oauth-apps-client-secret-path.ts +98 -0
  226. package/src/memory/migrations/151-oauth-providers-ping-url.ts +11 -0
  227. package/src/memory/migrations/152-memory-item-supersession.ts +44 -0
  228. package/src/memory/migrations/153-drop-entity-tables.ts +15 -0
  229. package/src/memory/migrations/154-drop-fts.ts +20 -0
  230. package/src/memory/migrations/155-drop-conflicts.ts +7 -0
  231. package/src/memory/migrations/156-call-session-invite-metadata.ts +24 -0
  232. package/src/memory/migrations/157-invite-contact-id.ts +104 -0
  233. package/src/memory/migrations/index.ts +8 -0
  234. package/src/memory/migrations/registry.ts +6 -0
  235. package/src/memory/qdrant-client.ts +148 -51
  236. package/src/memory/raw-query.ts +1 -1
  237. package/src/memory/retriever.test.ts +294 -273
  238. package/src/memory/retriever.ts +421 -645
  239. package/src/memory/schema/calls.ts +2 -0
  240. package/src/memory/schema/contacts.ts +1 -0
  241. package/src/memory/schema/memory-core.ts +3 -48
  242. package/src/memory/schema/oauth.ts +2 -0
  243. package/src/memory/search/formatting.ts +263 -176
  244. package/src/memory/search/lexical.ts +1 -254
  245. package/src/memory/search/ranking.ts +0 -455
  246. package/src/memory/search/semantic.ts +100 -14
  247. package/src/memory/search/staleness.ts +47 -0
  248. package/src/memory/search/tier-classifier.ts +21 -0
  249. package/src/memory/search/types.ts +15 -77
  250. package/src/memory/task-memory-cleanup.ts +4 -6
  251. package/src/messaging/provider.ts +1 -1
  252. package/src/messaging/providers/gmail/adapter.ts +1 -1
  253. package/src/messaging/providers/gmail/mime-builder.ts +17 -7
  254. package/src/messaging/providers/telegram-bot/adapter.ts +17 -8
  255. package/src/messaging/providers/whatsapp/adapter.ts +13 -9
  256. package/src/messaging/registry.ts +9 -5
  257. package/src/oauth/byo-connection.test.ts +40 -25
  258. package/src/oauth/connect-orchestrator.ts +4 -10
  259. package/src/oauth/connection-resolver.ts +20 -6
  260. package/src/oauth/manual-token-connection.ts +5 -5
  261. package/src/oauth/oauth-store.ts +183 -31
  262. package/src/oauth/platform-connection.test.ts +1 -1
  263. package/src/oauth/provider-behaviors.ts +503 -4
  264. package/src/oauth/seed-providers.ts +214 -8
  265. package/src/oauth/token-persistence.ts +31 -16
  266. package/src/permissions/defaults.ts +1 -0
  267. package/src/permissions/trust-store.ts +23 -1
  268. package/src/playbooks/playbook-compiler.ts +1 -1
  269. package/src/prompts/system-prompt.ts +18 -2
  270. package/src/providers/anthropic/client.ts +56 -126
  271. package/src/providers/types.ts +7 -1
  272. package/src/runtime/AGENTS.md +9 -0
  273. package/src/runtime/auth/route-policy.ts +6 -3
  274. package/src/runtime/channel-readiness-service.ts +48 -40
  275. package/src/runtime/guardian-reply-router.ts +24 -22
  276. package/src/runtime/http-server.ts +2 -2
  277. package/src/runtime/http-types.ts +2 -0
  278. package/src/runtime/invite-redemption-service.ts +72 -12
  279. package/src/runtime/invite-service.ts +43 -0
  280. package/src/runtime/middleware/twilio-validation.ts +1 -1
  281. package/src/runtime/pending-interactions.ts +2 -2
  282. package/src/runtime/routes/brain-graph-routes.ts +10 -90
  283. package/src/runtime/routes/btw-routes.ts +10 -5
  284. package/src/runtime/routes/conversation-routes.ts +56 -11
  285. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +21 -12
  286. package/src/runtime/routes/integrations/slack/channel.ts +2 -2
  287. package/src/runtime/routes/integrations/telegram.ts +2 -2
  288. package/src/runtime/routes/integrations/twilio.ts +17 -17
  289. package/src/runtime/routes/invite-routes.ts +29 -4
  290. package/src/runtime/routes/memory-item-routes.test.ts +754 -0
  291. package/src/runtime/routes/memory-item-routes.ts +503 -0
  292. package/src/runtime/routes/secret-routes.ts +17 -0
  293. package/src/runtime/routes/session-management-routes.ts +3 -3
  294. package/src/runtime/routes/settings-routes.ts +3 -3
  295. package/src/runtime/routes/trust-rules-routes.ts +14 -0
  296. package/src/runtime/routes/workspace-routes.ts +9 -4
  297. package/src/runtime/routes/workspace-utils.ts +8 -2
  298. package/src/schedule/integration-status.ts +26 -19
  299. package/src/security/keychain-broker-client.ts +17 -4
  300. package/src/security/oauth2.ts +6 -7
  301. package/src/security/secure-keys.ts +44 -19
  302. package/src/security/token-manager.ts +46 -39
  303. package/src/services/vercel-deploy.ts +0 -24
  304. package/src/signals/confirm.ts +78 -0
  305. package/src/signals/mcp-reload.ts +18 -0
  306. package/src/skills/catalog-install.ts +74 -18
  307. package/src/skills/skillssh-registry.ts +503 -0
  308. package/src/tools/assets/search.ts +5 -1
  309. package/src/tools/computer-use/definitions.ts +0 -10
  310. package/src/tools/computer-use/registry.ts +1 -1
  311. package/src/tools/credentials/vault.ts +22 -7
  312. package/src/tools/memory/definitions.ts +4 -13
  313. package/src/tools/memory/handlers.test.ts +83 -103
  314. package/src/tools/memory/handlers.ts +50 -85
  315. package/src/tools/network/script-proxy/session-manager.ts +8 -8
  316. package/src/tools/schedule/create.ts +10 -3
  317. package/src/tools/schedule/update.ts +8 -1
  318. package/src/tools/skills/load.ts +25 -2
  319. package/src/watcher/provider-types.ts +1 -1
  320. package/src/watcher/providers/github.ts +1 -1
  321. package/src/watcher/providers/gmail.ts +3 -3
  322. package/src/watcher/providers/google-calendar.ts +3 -3
  323. package/src/watcher/providers/linear.ts +1 -1
  324. package/src/__tests__/clarification-resolver.test.ts +0 -193
  325. package/src/__tests__/conflict-intent-tokenization.test.ts +0 -160
  326. package/src/__tests__/conflict-policy.test.ts +0 -269
  327. package/src/__tests__/conflict-store.test.ts +0 -372
  328. package/src/__tests__/contradiction-checker.test.ts +0 -361
  329. package/src/__tests__/entity-extractor.test.ts +0 -211
  330. package/src/__tests__/entity-search.test.ts +0 -1117
  331. package/src/__tests__/profile-compiler.test.ts +0 -392
  332. package/src/__tests__/session-conflict-gate.test.ts +0 -1228
  333. package/src/__tests__/session-profile-injection.test.ts +0 -557
  334. package/src/config/bundled-skills/knowledge-graph/SKILL.md +0 -25
  335. package/src/config/bundled-skills/knowledge-graph/TOOLS.json +0 -66
  336. package/src/config/bundled-skills/knowledge-graph/tools/graph-query.ts +0 -211
  337. package/src/daemon/session-conflict-gate.ts +0 -167
  338. package/src/daemon/session-dynamic-profile.ts +0 -77
  339. package/src/memory/clarification-resolver.ts +0 -417
  340. package/src/memory/conflict-intent.ts +0 -205
  341. package/src/memory/conflict-policy.ts +0 -127
  342. package/src/memory/conflict-store.ts +0 -410
  343. package/src/memory/contradiction-checker.ts +0 -508
  344. package/src/memory/entity-extractor.ts +0 -535
  345. package/src/memory/format-recall.ts +0 -47
  346. package/src/memory/fts-reconciler.ts +0 -165
  347. package/src/memory/job-handlers/conflict.ts +0 -200
  348. package/src/memory/profile-compiler.ts +0 -195
  349. package/src/memory/recall-cache.ts +0 -117
  350. package/src/memory/search/entity.ts +0 -535
  351. package/src/memory/search/query-expansion.test.ts +0 -70
  352. package/src/memory/search/query-expansion.ts +0 -118
  353. package/src/runtime/routes/mcp-routes.ts +0 -20
@@ -1,417 +0,0 @@
1
- import {
2
- createTimeout,
3
- extractToolUse,
4
- getConfiguredProvider,
5
- userMessage,
6
- } from "../providers/provider-send-message.js";
7
- import type { ModelIntent } from "../providers/types.js";
8
- import { truncate } from "../util/truncate.js";
9
-
10
- const DEFAULT_RESOLVER_MODEL_INTENT: ModelIntent = "latency-optimized";
11
- const DEFAULT_RESOLVER_TIMEOUT_MS = 12_000;
12
-
13
- const DIRECTIONAL_EXISTING_CUES = [
14
- "existing",
15
- "old",
16
- "previous",
17
- "first",
18
- "earlier",
19
- "original",
20
- ];
21
- const DIRECTIONAL_CANDIDATE_CUES = [
22
- "candidate",
23
- "new",
24
- "latest",
25
- "second",
26
- "updated",
27
- "instead",
28
- "replace",
29
- ];
30
- const MERGE_CUES = [
31
- "both",
32
- "merge",
33
- "combine",
34
- "together",
35
- "depends",
36
- "either",
37
- "mix",
38
- ];
39
-
40
- const STOP_WORDS = new Set([
41
- "about",
42
- "after",
43
- "again",
44
- "also",
45
- "because",
46
- "been",
47
- "before",
48
- "being",
49
- "between",
50
- "could",
51
- "doing",
52
- "from",
53
- "have",
54
- "into",
55
- "just",
56
- "more",
57
- "most",
58
- "only",
59
- "over",
60
- "same",
61
- "should",
62
- "some",
63
- "than",
64
- "that",
65
- "their",
66
- "there",
67
- "these",
68
- "they",
69
- "this",
70
- "those",
71
- "were",
72
- "what",
73
- "when",
74
- "where",
75
- "which",
76
- "while",
77
- "with",
78
- "would",
79
- "your",
80
- ]);
81
-
82
- export type ClarificationResolution =
83
- | "keep_existing"
84
- | "keep_candidate"
85
- | "merge"
86
- | "still_unclear";
87
-
88
- export type ClarificationStrategy =
89
- | "heuristic"
90
- | "llm"
91
- | "llm_timeout"
92
- | "llm_error"
93
- | "no_llm_key";
94
-
95
- export interface ClarificationResolverInput {
96
- existingStatement: string;
97
- candidateStatement: string;
98
- userMessage: string;
99
- }
100
-
101
- export interface ClarificationResolverOptions {
102
- apiKey?: string;
103
- model?: string;
104
- modelIntent?: ModelIntent;
105
- timeoutMs?: number;
106
- }
107
-
108
- export interface ClarificationResolverResult {
109
- resolution: ClarificationResolution;
110
- strategy: ClarificationStrategy;
111
- resolvedStatement: string | null;
112
- explanation: string;
113
- }
114
-
115
- export async function resolveConflictClarification(
116
- input: ClarificationResolverInput,
117
- options?: ClarificationResolverOptions,
118
- ): Promise<ClarificationResolverResult> {
119
- const heuristicResult = resolveWithHeuristics(input);
120
- if (heuristicResult) return heuristicResult;
121
-
122
- const provider = getConfiguredProvider();
123
- if (!provider) {
124
- return {
125
- resolution: "still_unclear",
126
- strategy: "no_llm_key",
127
- resolvedStatement: null,
128
- explanation:
129
- "Configured provider unavailable for clarification fallback.",
130
- };
131
- }
132
-
133
- try {
134
- return await resolveWithLlm(input, {
135
- model: options?.model,
136
- modelIntent: options?.modelIntent ?? DEFAULT_RESOLVER_MODEL_INTENT,
137
- timeoutMs: options?.timeoutMs ?? DEFAULT_RESOLVER_TIMEOUT_MS,
138
- });
139
- } catch (err) {
140
- const message = err instanceof Error ? err.message : String(err);
141
- if (message === "clarification_resolver_timeout") {
142
- return {
143
- resolution: "still_unclear",
144
- strategy: "llm_timeout",
145
- resolvedStatement: null,
146
- explanation: "Clarification resolver timed out.",
147
- };
148
- }
149
- return {
150
- resolution: "still_unclear",
151
- strategy: "llm_error",
152
- resolvedStatement: null,
153
- explanation: `Clarification resolver failed: ${truncate(
154
- message,
155
- 300,
156
- "",
157
- )}`,
158
- };
159
- }
160
- }
161
-
162
- function resolveWithHeuristics(
163
- input: ClarificationResolverInput,
164
- ): ClarificationResolverResult | null {
165
- const normalizedMessage = normalize(input.userMessage);
166
- if (!normalizedMessage) return null;
167
-
168
- const lowerMessage = normalizedMessage.toLowerCase();
169
-
170
- const hasExistingCue = containsAnyCue(
171
- lowerMessage,
172
- DIRECTIONAL_EXISTING_CUES,
173
- );
174
- const hasCandidateCue = containsAnyCue(
175
- lowerMessage,
176
- DIRECTIONAL_CANDIDATE_CUES,
177
- );
178
- const hasMergeCue = containsAnyCue(lowerMessage, MERGE_CUES);
179
-
180
- // When multiple cue categories match, delegate to LLM to avoid misclassification
181
- const matchCount = [hasExistingCue, hasCandidateCue, hasMergeCue].filter(
182
- Boolean,
183
- ).length;
184
- if (matchCount > 1) return null;
185
-
186
- if (hasMergeCue) {
187
- return {
188
- resolution: "merge",
189
- strategy: "heuristic",
190
- resolvedStatement: buildMergedStatement(input),
191
- explanation: "User response includes merge cues.",
192
- };
193
- }
194
-
195
- if (hasExistingCue) {
196
- return {
197
- resolution: "keep_existing",
198
- strategy: "heuristic",
199
- resolvedStatement: null,
200
- explanation: "User response explicitly points to existing/old statement.",
201
- };
202
- }
203
-
204
- if (hasCandidateCue) {
205
- return {
206
- resolution: "keep_candidate",
207
- strategy: "heuristic",
208
- resolvedStatement: null,
209
- explanation:
210
- "User response explicitly points to candidate/new statement.",
211
- };
212
- }
213
-
214
- const messageTokens = tokenize(normalizedMessage);
215
- const existingOverlap = overlapScore(
216
- messageTokens,
217
- tokenize(input.existingStatement),
218
- );
219
- const candidateOverlap = overlapScore(
220
- messageTokens,
221
- tokenize(input.candidateStatement),
222
- );
223
-
224
- if (existingOverlap >= 2 && existingOverlap >= candidateOverlap + 1) {
225
- return {
226
- resolution: "keep_existing",
227
- strategy: "heuristic",
228
- resolvedStatement: null,
229
- explanation:
230
- "User response overlaps more with existing statement details.",
231
- };
232
- }
233
-
234
- if (candidateOverlap >= 2 && candidateOverlap >= existingOverlap + 1) {
235
- return {
236
- resolution: "keep_candidate",
237
- strategy: "heuristic",
238
- resolvedStatement: null,
239
- explanation:
240
- "User response overlaps more with candidate statement details.",
241
- };
242
- }
243
-
244
- if (existingOverlap > 0 && candidateOverlap > 0) {
245
- return {
246
- resolution: "merge",
247
- strategy: "heuristic",
248
- resolvedStatement: buildMergedStatement(input),
249
- explanation: "User response overlaps with both statements.",
250
- };
251
- }
252
-
253
- return null;
254
- }
255
-
256
- async function resolveWithLlm(
257
- input: ClarificationResolverInput,
258
- options: { model?: string; modelIntent: ModelIntent; timeoutMs: number },
259
- ): Promise<ClarificationResolverResult> {
260
- const provider = getConfiguredProvider()!;
261
- const userPrompt = [
262
- "You are resolving a memory clarification response.",
263
- "",
264
- `Existing statement: ${input.existingStatement}`,
265
- `Candidate statement: ${input.candidateStatement}`,
266
- `User clarification: ${input.userMessage}`,
267
- ].join("\n");
268
-
269
- const { signal, cleanup } = createTimeout(options.timeoutMs);
270
-
271
- try {
272
- const response = await provider.sendMessage(
273
- [userMessage(userPrompt)],
274
- [
275
- {
276
- name: "resolve_conflict_clarification",
277
- description:
278
- "Resolve a pending memory contradiction using user clarification.",
279
- input_schema: {
280
- type: "object" as const,
281
- properties: {
282
- resolution: {
283
- type: "string",
284
- enum: [
285
- "keep_existing",
286
- "keep_candidate",
287
- "merge",
288
- "still_unclear",
289
- ],
290
- },
291
- resolved_statement: {
292
- type: "string",
293
- description: "Required only when resolution is merge.",
294
- },
295
- explanation: {
296
- type: "string",
297
- description: "One short rationale for the classification.",
298
- },
299
- },
300
- required: ["resolution", "explanation"],
301
- },
302
- },
303
- ],
304
- [
305
- "Classify the user clarification for conflicting memory statements.",
306
- "Return exactly one resolution:",
307
- "- keep_existing",
308
- "- keep_candidate",
309
- "- merge",
310
- "- still_unclear",
311
- ].join("\n"),
312
- {
313
- config: {
314
- ...(options.model
315
- ? { model: options.model }
316
- : { modelIntent: options.modelIntent }),
317
- max_tokens: 256,
318
- tool_choice: {
319
- type: "tool" as const,
320
- name: "resolve_conflict_clarification",
321
- },
322
- },
323
- signal,
324
- },
325
- );
326
- cleanup();
327
-
328
- const toolBlock = extractToolUse(response);
329
- if (!toolBlock) {
330
- throw new Error("No tool_use block in clarification resolver response.");
331
- }
332
-
333
- const parsed = toolBlock.input as {
334
- resolution?: string;
335
- resolved_statement?: string;
336
- explanation?: string;
337
- };
338
-
339
- if (!isResolution(parsed.resolution)) {
340
- throw new Error(
341
- `Invalid clarification resolution: ${String(parsed.resolution)}`,
342
- );
343
- }
344
-
345
- const resolvedStatement =
346
- parsed.resolution === "merge"
347
- ? normalize(parsed.resolved_statement ?? buildMergedStatement(input)) ||
348
- buildMergedStatement(input)
349
- : null;
350
-
351
- return {
352
- resolution: parsed.resolution,
353
- strategy: "llm",
354
- resolvedStatement,
355
- explanation: truncate(
356
- normalize(parsed.explanation ?? "Resolved via LLM fallback."),
357
- 500,
358
- "",
359
- ),
360
- };
361
- } catch (err) {
362
- cleanup();
363
- if (signal.aborted) {
364
- throw new Error("clarification_resolver_timeout");
365
- }
366
- throw err;
367
- }
368
- }
369
-
370
- function isResolution(
371
- value: string | undefined,
372
- ): value is ClarificationResolution {
373
- return (
374
- value === "keep_existing" ||
375
- value === "keep_candidate" ||
376
- value === "merge" ||
377
- value === "still_unclear"
378
- );
379
- }
380
-
381
- function containsAnyCue(input: string, cues: readonly string[]): boolean {
382
- return cues.some((cue) => new RegExp(`\\b${cue}\\b`).test(input));
383
- }
384
-
385
- function overlapScore(left: Set<string>, right: Set<string>): number {
386
- let score = 0;
387
- for (const token of left) {
388
- if (right.has(token)) score += 1;
389
- }
390
- return score;
391
- }
392
-
393
- function tokenize(input: string): Set<string> {
394
- const words = input
395
- .toLowerCase()
396
- .split(/[^a-z0-9]+/g)
397
- .map((word) => word.trim())
398
- .filter((word) => word.length >= 4 && !STOP_WORDS.has(word));
399
- return new Set(words);
400
- }
401
-
402
- function normalize(input: string): string {
403
- return input.replace(/\s+/g, " ").trim();
404
- }
405
-
406
- function buildMergedStatement(input: ClarificationResolverInput): string {
407
- const normalizedUserMessage = normalize(input.userMessage);
408
- if (
409
- normalizedUserMessage.length >= 8 &&
410
- normalizedUserMessage.length <= 320
411
- ) {
412
- return normalizedUserMessage;
413
- }
414
- const existing = truncate(normalize(input.existingStatement), 140, "");
415
- const candidate = truncate(normalize(input.candidateStatement), 140, "");
416
- return truncate(`Merged clarification: ${existing}; ${candidate}`, 320, "");
417
- }
@@ -1,205 +0,0 @@
1
- /**
2
- * Shared conflict intent helpers used by both interactive conflict gating and
3
- * background conflict resolution jobs.
4
- */
5
-
6
- export interface ConflictStatementPair {
7
- existingStatement: string;
8
- candidateStatement: string;
9
- }
10
-
11
- export function computeConflictRelevance(
12
- userMessage: string,
13
- conflict: ConflictStatementPair,
14
- ): number {
15
- const queryTokens = tokenizeForConflictRelevance(userMessage);
16
- if (queryTokens.size === 0) return 0;
17
- const existingTokens = tokenizeForConflictRelevance(
18
- conflict.existingStatement,
19
- );
20
- const candidateTokens = tokenizeForConflictRelevance(
21
- conflict.candidateStatement,
22
- );
23
- return Math.max(
24
- overlapRatio(queryTokens, existingTokens),
25
- overlapRatio(queryTokens, candidateTokens),
26
- );
27
- }
28
-
29
- const NOISE_TOKENS = new Set([
30
- "http",
31
- "https",
32
- "github",
33
- "gitlab",
34
- "www",
35
- "com",
36
- "org",
37
- ]);
38
-
39
- // Matches full URLs (http(s)://...) and scheme-less bare domain URLs for known
40
- // code hosting sites (e.g. github.com/org/repo/pull/123) so embedded path
41
- // segments like "pull", "issue", "ticket" don't leak into relevance scoring.
42
- // The scheme-less branch is restricted to known hosting domains to avoid
43
- // stripping dotted identifiers like "index.ts/runtime".
44
- const URL_PATTERN =
45
- /https?:\/\/[^\s)>\]]+|(?:github|gitlab|bitbucket)\.(?:com|org|io)\/[^\s)>\]]*/gi;
46
-
47
- export function tokenizeForConflictRelevance(input: string): Set<string> {
48
- const stripped = input.replace(URL_PATTERN, " ");
49
- const tokens = stripped
50
- .toLowerCase()
51
- .split(/[^a-z0-9]+/g)
52
- .map((token) => token.trim())
53
- .filter((token) => token.length >= 4)
54
- .filter((token) => !/^\d+$/.test(token))
55
- .filter((token) => !NOISE_TOKENS.has(token));
56
- return new Set(tokens);
57
- }
58
-
59
- export function overlapRatio(left: Set<string>, right: Set<string>): number {
60
- if (left.size === 0 || right.size === 0) return 0;
61
- let overlap = 0;
62
- for (const token of left) {
63
- if (right.has(token)) overlap += 1;
64
- }
65
- return overlap / Math.max(left.size, right.size);
66
- }
67
-
68
- /**
69
- * Tokenize for statement-to-statement coherence checking.
70
- * Uses a lower minimum token length (>= 3) than `tokenizeForConflictRelevance`
71
- * to preserve short technical terms like "vim", "css", "git", "npm", etc.
72
- */
73
- function tokenizeForCoherence(input: string): Set<string> {
74
- const stripped = input.replace(URL_PATTERN, " ");
75
- const tokens = stripped
76
- .toLowerCase()
77
- .split(/[^a-z0-9]+/g)
78
- .map((token) => token.trim())
79
- .filter((token) => token.length >= 3)
80
- .filter((token) => !/^\d+$/.test(token))
81
- .filter((token) => !NOISE_TOKENS.has(token));
82
- return new Set(tokens);
83
- }
84
-
85
- /**
86
- * Check whether two conflict statements have topical coherence.
87
- * Returns true if the statements share at least one meaningful token.
88
- * Uses a permissive tokenizer (>= 3 chars) to avoid dropping short
89
- * technical terms like "vim", "css", "git", etc.
90
- */
91
- export function areStatementsCoherent(
92
- statementA: string,
93
- statementB: string,
94
- ): boolean {
95
- const tokensA = tokenizeForCoherence(statementA);
96
- const tokensB = tokenizeForCoherence(statementB);
97
- return overlapRatio(tokensA, tokensB) > 0;
98
- }
99
-
100
- // Action verbs that signal the user is making a deliberate choice.
101
- const ACTION_CUES = new Set([
102
- "keep",
103
- "use",
104
- "prefer",
105
- "go",
106
- "pick",
107
- "choose",
108
- "take",
109
- "want",
110
- "select",
111
- ]);
112
-
113
- // Directional/merge cue words mirrored from clarification-resolver.ts heuristics.
114
- const DIRECTIONAL_CUES = new Set([
115
- "existing",
116
- "old",
117
- "previous",
118
- "first",
119
- "earlier",
120
- "original",
121
- "candidate",
122
- "new",
123
- "latest",
124
- "second",
125
- "updated",
126
- "instead",
127
- "replace",
128
- "both",
129
- "merge",
130
- "combine",
131
- "together",
132
- "either",
133
- "mix",
134
- "option",
135
- "former",
136
- "latter",
137
- ]);
138
-
139
- const MAX_REPLY_WORD_COUNT = 12;
140
-
141
- // Direction-only matches (no action verb) must be very short to avoid
142
- // false positives from unrelated statements that happen to contain
143
- // common words like "new", "old", "option", etc.
144
- const MAX_DIRECTION_ONLY_WORD_COUNT = 4;
145
-
146
- // Messages starting with a question word are unlikely to be clarification
147
- // replies even when they lack a trailing question mark.
148
- const QUESTION_WORD_PREFIXES = new Set([
149
- "what",
150
- "how",
151
- "why",
152
- "where",
153
- "when",
154
- "which",
155
- "who",
156
- "whom",
157
- "whose",
158
- ]);
159
-
160
- /**
161
- * Determines whether a user message looks like a deliberate clarification
162
- * reply (e.g. "keep the new one", "both", "option B").
163
- */
164
- export function looksLikeClarificationReply(userMessage: string): boolean {
165
- const trimmed = userMessage.trim();
166
- if (trimmed.endsWith("?")) return false;
167
-
168
- const words = trimmed.toLowerCase().split(/\s+/).filter(Boolean);
169
- if (words.length === 0 || words.length > MAX_REPLY_WORD_COUNT) return false;
170
-
171
- const normalized = words.map((w) => w.replace(/[^a-z]/g, ""));
172
-
173
- // Reject messages that start with a question word (even without '?').
174
- // Match exact question words or contractions (e.g. "what's", "where's"),
175
- // but not words that merely share a prefix (e.g. "whichever", "however").
176
- const firstWord = words[0];
177
- const firstNorm = normalized[0];
178
- for (const qw of QUESTION_WORD_PREFIXES) {
179
- if (
180
- firstNorm === qw ||
181
- (firstWord.startsWith(qw) &&
182
- "'\u2018\u2019".includes(firstWord[qw.length]))
183
- )
184
- return false;
185
- }
186
-
187
- const hasAction = normalized.some((w) => ACTION_CUES.has(w));
188
- const hasDirection = normalized.some((w) => DIRECTIONAL_CUES.has(w));
189
-
190
- if (hasAction) return true;
191
- if (hasDirection) return words.length <= MAX_DIRECTION_ONLY_WORD_COUNT;
192
- return false;
193
- }
194
-
195
- /**
196
- * Conflict resolution requires explicit clarification intent with non-zero
197
- * topical overlap with the conflict statements.
198
- */
199
- export function shouldAttemptConflictResolution(input: {
200
- clarificationReply: boolean;
201
- relevance: number;
202
- }): boolean {
203
- if (!input.clarificationReply) return false;
204
- return input.relevance > 0;
205
- }