@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
@@ -19,7 +19,7 @@ import {
19
19
  routeConnection,
20
20
  stripQueryString,
21
21
  } from "../../../outbound-proxy/index.js";
22
- import { getSecureKey } from "../../../security/secure-keys.js";
22
+ import { getSecureKeyAsync } from "../../../security/secure-keys.js";
23
23
  import { getLogger } from "../../../util/logger.js";
24
24
  import { silentlyWithLog } from "../../../util/silently.js";
25
25
  import {
@@ -97,10 +97,10 @@ const acquireLocks = new Map<string, Promise<ProxySession>>();
97
97
  * Handles optional composition with a second credential and value transforms.
98
98
  * Returns null if any referenced credential cannot be resolved.
99
99
  */
100
- function buildInjectedValue(
100
+ async function buildInjectedValue(
101
101
  tpl: CredentialInjectionTemplate,
102
102
  primaryValue: string,
103
- ): string | null {
103
+ ): Promise<string | null> {
104
104
  let value = primaryValue;
105
105
 
106
106
  if (tpl.composeWith) {
@@ -109,7 +109,7 @@ function buildInjectedValue(
109
109
  tpl.composeWith.field,
110
110
  );
111
111
  if (!composed) return null;
112
- const composedValue = getSecureKey(composed.storageKey);
112
+ const composedValue = await getSecureKeyAsync(composed.storageKey);
113
113
  if (!composedValue) return null;
114
114
  value = `${value}${tpl.composeWith.separator}${composedValue}`;
115
115
  }
@@ -311,10 +311,10 @@ export async function startSession(
311
311
  if (tpl.injectionType === "header" && tpl.headerName) {
312
312
  const resolved = resolveById(credId);
313
313
  if (!resolved) return req.headers;
314
- const value = getSecureKey(resolved.storageKey);
314
+ const value = await getSecureKeyAsync(resolved.storageKey);
315
315
  if (!value) return req.headers;
316
316
 
317
- const headerValue = buildInjectedValue(tpl, value);
317
+ const headerValue = await buildInjectedValue(tpl, value);
318
318
  if (!headerValue) {
319
319
  log.warn(
320
320
  { host: req.hostname, credentialId: credId },
@@ -399,11 +399,11 @@ export async function startSession(
399
399
  const { credentialId, template } = decision;
400
400
  const resolved = resolveById(credentialId);
401
401
  if (!resolved) return {};
402
- const value = getSecureKey(resolved.storageKey);
402
+ const value = await getSecureKeyAsync(resolved.storageKey);
403
403
  if (!value) return {};
404
404
 
405
405
  if (template.injectionType === "header" && template.headerName) {
406
- const headerValue = buildInjectedValue(template, value);
406
+ const headerValue = await buildInjectedValue(template, value);
407
407
  if (!headerValue) {
408
408
  log.warn(
409
409
  { hostname, credentialId },
@@ -22,8 +22,15 @@ const VALID_ROUTING_INTENTS: RoutingIntent[] = [
22
22
 
23
23
  export async function executeScheduleCreate(
24
24
  input: Record<string, unknown>,
25
- _context: ToolContext,
25
+ context: ToolContext,
26
26
  ): Promise<ToolExecutionResult> {
27
+ if (context.trustClass !== "guardian") {
28
+ return {
29
+ content:
30
+ "Error: schedule_create is restricted to guardian actors because schedules execute with elevated privileges.",
31
+ isError: true,
32
+ };
33
+ }
27
34
  const name = input.name as string;
28
35
  const timezone = (input.timezone as string) ?? null;
29
36
  const message = input.message as string;
@@ -108,7 +115,7 @@ export async function executeScheduleCreate(
108
115
  });
109
116
 
110
117
  const fireDate = formatLocalDate(job.nextRunAt);
111
- const integrations = formatIntegrationSummary();
118
+ const integrations = await formatIntegrationSummary();
112
119
  return {
113
120
  content: [
114
121
  `One-shot schedule created successfully.`,
@@ -190,7 +197,7 @@ export async function executeScheduleCreate(
190
197
  : describeCronExpression(job.cronExpression);
191
198
 
192
199
  const nextRunDate = formatLocalDate(job.nextRunAt);
193
- const integrations = formatIntegrationSummary();
200
+ const integrations = await formatIntegrationSummary();
194
201
  return {
195
202
  content: [
196
203
  `Recurring schedule created successfully.`,
@@ -25,8 +25,15 @@ const VALID_ROUTING_INTENTS: RoutingIntent[] = [
25
25
 
26
26
  export async function executeScheduleUpdate(
27
27
  input: Record<string, unknown>,
28
- _context: ToolContext,
28
+ context: ToolContext,
29
29
  ): Promise<ToolExecutionResult> {
30
+ if (context.trustClass !== "guardian") {
31
+ return {
32
+ content:
33
+ "Error: schedule_update is restricted to guardian actors because schedules execute with elevated privileges.",
34
+ isError: true,
35
+ };
36
+ }
30
37
  const jobId = input.job_id as string;
31
38
  if (!jobId || typeof jobId !== "string") {
32
39
  return { content: "Error: job_id is required", isError: true };
@@ -8,7 +8,10 @@ import type { SkillSummary, SkillToolManifest } from "../../config/skills.js";
8
8
  import { loadSkillBySelector, loadSkillCatalog } from "../../config/skills.js";
9
9
  import { RiskLevel } from "../../permissions/types.js";
10
10
  import type { ToolDefinition } from "../../providers/types.js";
11
- import { autoInstallFromCatalog } from "../../skills/catalog-install.js";
11
+ import {
12
+ autoInstallFromCatalog,
13
+ resolveCatalog,
14
+ } from "../../skills/catalog-install.js";
12
15
  import {
13
16
  collectAllMissing,
14
17
  indexCatalogById,
@@ -191,15 +194,35 @@ export class SkillLoadTool implements Tool {
191
194
  catalogIndex = indexCatalogById(catalog);
192
195
 
193
196
  // Auto-install missing includes before validation (max 5 rounds for transitive deps)
197
+ // Defer catalog resolution until we confirm there are missing includes,
198
+ // then cache the result to avoid redundant network requests per dependency.
199
+ let remoteCatalog: Awaited<ReturnType<typeof resolveCatalog>> | undefined;
200
+
194
201
  const MAX_INSTALL_ROUNDS = 5;
195
202
  for (let round = 0; round < MAX_INSTALL_ROUNDS; round++) {
196
203
  const missing = collectAllMissing(skill.id, catalogIndex);
197
204
  if (missing.size === 0) break;
198
205
 
206
+ // Lazily resolve catalog on first round with missing includes
207
+ if (!remoteCatalog) {
208
+ try {
209
+ remoteCatalog = await resolveCatalog([...missing][0]);
210
+ } catch (err) {
211
+ log.warn(
212
+ { err, skillId: skill.id },
213
+ "Failed to resolve catalog for include auto-install",
214
+ );
215
+ break;
216
+ }
217
+ }
218
+
199
219
  let installedAny = false;
200
220
  for (const missingId of missing) {
201
221
  try {
202
- const installed = await autoInstallFromCatalog(missingId);
222
+ const installed = await autoInstallFromCatalog(
223
+ missingId,
224
+ remoteCatalog,
225
+ );
203
226
  if (installed) {
204
227
  log.info(
205
228
  { skillId: missingId, parentSkillId: skill.id },
@@ -28,7 +28,7 @@ export interface WatcherProvider {
28
28
  id: string;
29
29
  /** Human-readable name. */
30
30
  displayName: string;
31
- /** Credential service required (e.g. 'integration:gmail'). */
31
+ /** Credential service required (e.g. 'integration:google'). */
32
32
  requiredCredentialService: string;
33
33
 
34
34
  /**
@@ -130,7 +130,7 @@ export const githubProvider: WatcherProvider = {
130
130
  _config: Record<string, unknown>,
131
131
  _watcherKey: string,
132
132
  ): Promise<FetchResult> {
133
- const connection = resolveOAuthConnection(credentialService);
133
+ const connection = await resolveOAuthConnection(credentialService);
134
134
  const since = watermark ?? new Date().toISOString();
135
135
  const items: WatcherItem[] = [];
136
136
  let page = 1;
@@ -115,10 +115,10 @@ class HistoryExpiredError extends Error {
115
115
  export const gmailProvider: WatcherProvider = {
116
116
  id: "gmail",
117
117
  displayName: "Gmail",
118
- requiredCredentialService: "integration:gmail",
118
+ requiredCredentialService: "integration:google",
119
119
 
120
120
  async getInitialWatermark(credentialService: string): Promise<string> {
121
- const connection = resolveOAuthConnection(credentialService);
121
+ const connection = await resolveOAuthConnection(credentialService);
122
122
  const profile = await getProfile(connection);
123
123
  if (!profile.historyId) {
124
124
  throw new Error("Gmail profile did not return a historyId");
@@ -132,7 +132,7 @@ export const gmailProvider: WatcherProvider = {
132
132
  _config: Record<string, unknown>,
133
133
  _watcherKey: string,
134
134
  ): Promise<FetchResult> {
135
- const connection = resolveOAuthConnection(credentialService);
135
+ const connection = await resolveOAuthConnection(credentialService);
136
136
 
137
137
  if (!watermark) {
138
138
  // No watermark — get initial position, return no items
@@ -25,7 +25,7 @@ import type {
25
25
  const log = getLogger("watcher:google-calendar");
26
26
 
27
27
  /** The credential service — calendar shares OAuth tokens with Gmail. */
28
- const CREDENTIAL_SERVICE = "integration:gmail";
28
+ const CREDENTIAL_SERVICE = "integration:google";
29
29
 
30
30
  function eventToItem(event: CalendarEvent, eventType: string): WatcherItem {
31
31
  const start = event.start?.dateTime ?? event.start?.date ?? "";
@@ -125,7 +125,7 @@ export const googleCalendarProvider: WatcherProvider = {
125
125
  requiredCredentialService: CREDENTIAL_SERVICE,
126
126
 
127
127
  async getInitialWatermark(credentialService: string): Promise<string> {
128
- const connection = resolveOAuthConnection(credentialService);
128
+ const connection = await resolveOAuthConnection(credentialService);
129
129
 
130
130
  // Do a full sync with a narrow window to get the initial syncToken.
131
131
  // The API may paginate even for small result sets, so follow nextPageToken
@@ -157,7 +157,7 @@ export const googleCalendarProvider: WatcherProvider = {
157
157
  _config: Record<string, unknown>,
158
158
  _watcherKey: string,
159
159
  ): Promise<FetchResult> {
160
- const connection = resolveOAuthConnection(credentialService);
160
+ const connection = await resolveOAuthConnection(credentialService);
161
161
 
162
162
  if (!watermark) {
163
163
  // No watermark — paginate through to get the initial syncToken, return no items
@@ -512,7 +512,7 @@ export const linearProvider: WatcherProvider = {
512
512
  _config: Record<string, unknown>,
513
513
  watcherKey: string,
514
514
  ): Promise<FetchResult> {
515
- const connection = resolveOAuthConnection(credentialService);
515
+ const connection = await resolveOAuthConnection(credentialService);
516
516
  const since = watermark ?? new Date().toISOString();
517
517
 
518
518
  // Resolve the authenticated viewer's ID once per poll for the assigned-issues query
@@ -1,193 +0,0 @@
1
- import { beforeEach, describe, expect, mock, test } from "bun:test";
2
-
3
- let llmCallCount = 0;
4
- let llmDelayMs = 0;
5
- let llmResolution:
6
- | "keep_existing"
7
- | "keep_candidate"
8
- | "merge"
9
- | "still_unclear" = "still_unclear";
10
- let llmResolvedStatement = "";
11
- let llmExplanation = "Unclear response from user.";
12
-
13
- mock.module("../providers/provider-send-message.js", () => ({
14
- getConfiguredProvider: () => ({
15
- sendMessage: async (
16
- _messages: unknown,
17
- _tools: unknown,
18
- _system: unknown,
19
- opts?: { signal?: AbortSignal },
20
- ) => {
21
- llmCallCount += 1;
22
- if (llmDelayMs > 0) {
23
- await new Promise((resolve, reject) => {
24
- const timer = setTimeout(resolve, llmDelayMs);
25
- opts?.signal?.addEventListener("abort", () => {
26
- clearTimeout(timer);
27
- reject(new Error("Request was aborted."));
28
- });
29
- });
30
- }
31
- return {
32
- content: [
33
- {
34
- type: "tool_use" as const,
35
- id: "test-tool-use-id",
36
- name: "resolve_conflict",
37
- input: {
38
- resolution: llmResolution,
39
- resolved_statement: llmResolvedStatement,
40
- explanation: llmExplanation,
41
- },
42
- },
43
- ],
44
- model: "claude-haiku-4-5-20251001",
45
- stopReason: "tool_use",
46
- usage: { inputTokens: 0, outputTokens: 0 },
47
- };
48
- },
49
- }),
50
- createTimeout: (ms: number) => {
51
- const controller = new AbortController();
52
- const timer = setTimeout(() => controller.abort(), ms);
53
- return {
54
- signal: controller.signal,
55
- cleanup: () => clearTimeout(timer),
56
- };
57
- },
58
- extractToolUse: (response: { content: Array<{ type: string }> }) => {
59
- return response.content.find(
60
- (b: { type: string }) => b.type === "tool_use",
61
- );
62
- },
63
- userMessage: (text: string) => ({
64
- role: "user",
65
- content: [{ type: "text", text }],
66
- }),
67
- }));
68
-
69
- mock.module("../config/loader.js", () => ({
70
- getConfig: () => ({
71
- ui: {},
72
-
73
- apiKeys: {
74
- anthropic: "test-key",
75
- },
76
- }),
77
- }));
78
-
79
- import { resolveConflictClarification } from "../memory/clarification-resolver.js";
80
-
81
- beforeEach(() => {
82
- llmCallCount = 0;
83
- llmDelayMs = 0;
84
- llmResolution = "still_unclear";
85
- llmResolvedStatement = "";
86
- llmExplanation = "Unclear response from user.";
87
- });
88
-
89
- describe("resolveConflictClarification", () => {
90
- test("returns keep_existing from deterministic heuristic", async () => {
91
- const result = await resolveConflictClarification({
92
- existingStatement: "Use React for frontend work.",
93
- candidateStatement: "Use Vue for frontend work.",
94
- userMessage: "Keep the old React preference.",
95
- });
96
-
97
- expect(result.resolution).toBe("keep_existing");
98
- expect(result.strategy).toBe("heuristic");
99
- expect(llmCallCount).toBe(0);
100
- });
101
-
102
- test("returns keep_candidate from deterministic heuristic", async () => {
103
- const result = await resolveConflictClarification({
104
- existingStatement: "Use React for frontend work.",
105
- candidateStatement: "Use Vue for frontend work.",
106
- userMessage: "Use the new Vue note going forward.",
107
- });
108
-
109
- expect(result.resolution).toBe("keep_candidate");
110
- expect(result.strategy).toBe("heuristic");
111
- expect(llmCallCount).toBe(0);
112
- });
113
-
114
- test("returns merge from deterministic heuristic", async () => {
115
- const result = await resolveConflictClarification({
116
- existingStatement: "React is preferred for dashboards.",
117
- candidateStatement: "Vue is preferred for marketing pages.",
118
- userMessage:
119
- "Both are true: React for dashboards and Vue for marketing pages.",
120
- });
121
-
122
- expect(result.resolution).toBe("merge");
123
- expect(result.strategy).toBe("heuristic");
124
- expect(result.resolvedStatement).toContain("Both are true");
125
- expect(llmCallCount).toBe(0);
126
- });
127
-
128
- test("uses LLM fallback when heuristics are inconclusive", async () => {
129
- llmResolution = "still_unclear";
130
- llmExplanation = "The user message does not pick a side.";
131
-
132
- const result = await resolveConflictClarification({
133
- existingStatement: "Use React for frontend work.",
134
- candidateStatement: "Use Vue for frontend work.",
135
- userMessage: "Not sure yet.",
136
- });
137
-
138
- expect(result.resolution).toBe("still_unclear");
139
- expect(result.strategy).toBe("llm");
140
- expect(llmCallCount).toBe(1);
141
- });
142
-
143
- test("does not match cue substrings inside unrelated words", async () => {
144
- llmResolution = "keep_candidate";
145
- llmExplanation = "User wants Vue.";
146
-
147
- // "told" contains "old" as a substring but not as a whole word
148
- const result = await resolveConflictClarification({
149
- existingStatement: "Use React for frontend work.",
150
- candidateStatement: "Use Vue for frontend work.",
151
- userMessage: "I told you, use Vue.",
152
- });
153
-
154
- expect(result.resolution).toBe("keep_candidate");
155
- expect(result.strategy).toBe("llm");
156
- expect(llmCallCount).toBe(1);
157
- });
158
-
159
- test("delegates to LLM when multiple cue categories match", async () => {
160
- llmResolution = "keep_existing";
161
- llmExplanation = "User wants the old one.";
162
-
163
- // "either" is a merge cue, "old" is an existing cue — ambiguous
164
- const result = await resolveConflictClarification({
165
- existingStatement: "Use React for frontend work.",
166
- candidateStatement: "Use Vue for frontend work.",
167
- userMessage: "I don't want either, keep the old one.",
168
- });
169
-
170
- expect(result.resolution).toBe("keep_existing");
171
- expect(result.strategy).toBe("llm");
172
- expect(llmCallCount).toBe(1);
173
- });
174
-
175
- test("enforces timeout bound on LLM fallback", async () => {
176
- llmResolution = "keep_candidate";
177
- llmExplanation = "Prefer the newer statement.";
178
- llmDelayMs = 50;
179
-
180
- const result = await resolveConflictClarification(
181
- {
182
- existingStatement: "Use React for frontend work.",
183
- candidateStatement: "Use Vue for frontend work.",
184
- userMessage: "I cannot decide right now.",
185
- },
186
- { timeoutMs: 5 },
187
- );
188
-
189
- expect(result.resolution).toBe("still_unclear");
190
- expect(result.strategy).toBe("llm_timeout");
191
- expect(llmCallCount).toBe(1);
192
- });
193
- });
@@ -1,160 +0,0 @@
1
- import { describe, expect, test } from "bun:test";
2
-
3
- import {
4
- areStatementsCoherent,
5
- computeConflictRelevance,
6
- overlapRatio as _overlapRatio,
7
- tokenizeForConflictRelevance as _tokenizeForConflictRelevance,
8
- } from "../memory/conflict-intent.js";
9
-
10
- describe("tokenizeForConflictRelevance hardening", () => {
11
- test("excludes numeric-only tokens from relevance", () => {
12
- const relevance = computeConflictRelevance("Check PR 5526", {
13
- existingStatement: "Track PR 5525 for review.",
14
- candidateStatement: "Track PR 5526 for review.",
15
- });
16
- // Numeric tokens "5526" and "5525" should be excluded, so overlap is minimal
17
- expect(relevance).toBeLessThan(0.5);
18
- });
19
-
20
- test("excludes URL boilerplate tokens from relevance", () => {
21
- const relevance = computeConflictRelevance(
22
- "Check https://github.com/org/repo/pull/123",
23
- {
24
- existingStatement: "Review https://github.com/org/repo/pull/456",
25
- candidateStatement: "Review https://github.com/org/repo/pull/789",
26
- },
27
- );
28
- // URL tokens like "https", "github", "pull" should be excluded;
29
- // only real content tokens like "repo" remain, keeping relevance low
30
- expect(relevance).toBeLessThanOrEqual(0.5);
31
- });
32
-
33
- test("URL-embedded tracking tokens are stripped, standalone usage preserved", () => {
34
- // URLs containing "issue", "pull", etc. are stripped entirely before tokenizing
35
- const urlRelevance = computeConflictRelevance(
36
- "Check https://github.com/org/repo/issues/42",
37
- {
38
- existingStatement: "Review https://github.com/org/repo/issues/10",
39
- candidateStatement: "Review https://github.com/org/repo/issues/11",
40
- },
41
- );
42
- expect(urlRelevance).toBeLessThanOrEqual(0.5);
43
-
44
- // Standalone "issue" is preserved as a meaningful token
45
- const standaloneRelevance = computeConflictRelevance(
46
- "should I file an issue?",
47
- {
48
- existingStatement: "File an issue when bugs are found.",
49
- candidateStatement: "Skip filing an issue for minor bugs.",
50
- },
51
- );
52
- expect(standaloneRelevance).toBeGreaterThan(0);
53
- });
54
-
55
- test("strips scheme-less bare domain URLs from relevance", () => {
56
- const relevance = computeConflictRelevance(
57
- "Check github.com/org/repo/pull/123",
58
- {
59
- existingStatement: "Review gitlab.com/org/repo/issues/456",
60
- candidateStatement: "Review github.com/org/repo/pull/789",
61
- },
62
- );
63
- // Bare URLs should be stripped entirely; tokens like "pull", "issues"
64
- // embedded in paths must not contribute to overlap
65
- expect(relevance).toBeLessThanOrEqual(0.5);
66
- });
67
-
68
- test("preserves dotted identifiers that look like file paths", () => {
69
- const relevance = computeConflictRelevance("Use index.ts/runtime parser", {
70
- existingStatement: "Keep index.ts/runtime approach.",
71
- candidateStatement: "Switch to config.ts/runtime approach.",
72
- });
73
- // File-like identifiers should NOT be stripped as URLs
74
- expect(relevance).toBeGreaterThan(0);
75
- });
76
-
77
- test("still computes meaningful relevance for real content tokens", () => {
78
- const relevance = computeConflictRelevance(
79
- "Should I use React for frontend?",
80
- {
81
- existingStatement: "Use React for frontend work.",
82
- candidateStatement: "Use Vue for frontend work.",
83
- },
84
- );
85
- // Real content tokens like "react", "frontend" should still match
86
- expect(relevance).toBeGreaterThan(0);
87
- });
88
- });
89
-
90
- describe("statement coherence (areStatementsCoherent)", () => {
91
- test("unrelated statements are incoherent", () => {
92
- expect(
93
- areStatementsCoherent(
94
- "The default model for the summarize CLI is google/gemini-3-flash-preview.",
95
- "User's favorite color is blue.",
96
- ),
97
- ).toBe(false);
98
- });
99
-
100
- test("related statements are coherent", () => {
101
- expect(
102
- areStatementsCoherent(
103
- "User's favorite color is blue.",
104
- "User's favorite color is green.",
105
- ),
106
- ).toBe(true);
107
- });
108
-
109
- test("topically similar preferences are coherent", () => {
110
- expect(
111
- areStatementsCoherent(
112
- "Use React for frontend work.",
113
- "Use Vue for frontend work.",
114
- ),
115
- ).toBe(true);
116
- });
117
-
118
- test("completely disjoint technical topics are incoherent", () => {
119
- expect(
120
- areStatementsCoherent(
121
- "Always use PostgreSQL for database storage.",
122
- "The preferred terminal font is JetBrains Mono.",
123
- ),
124
- ).toBe(false);
125
- });
126
-
127
- test("short technical terms (3 chars) are preserved for coherence", () => {
128
- // "vim" and "css" are 3 chars — should not be filtered
129
- expect(
130
- areStatementsCoherent(
131
- "Use Vim for editing.",
132
- "Use Emacs instead of Vim.",
133
- ),
134
- ).toBe(true);
135
-
136
- expect(
137
- areStatementsCoherent(
138
- "Use CSS grid for layouts.",
139
- "Use CSS flexbox for layouts.",
140
- ),
141
- ).toBe(true);
142
-
143
- expect(
144
- areStatementsCoherent(
145
- "Use npm for installs.",
146
- "Use npm with --legacy-peer-deps.",
147
- ),
148
- ).toBe(true);
149
- });
150
-
151
- test("short terms with no shared context are still incoherent", () => {
152
- // No shared tokens at all — completely different topics
153
- expect(
154
- areStatementsCoherent(
155
- "Vim is the preferred editor.",
156
- "CSS grid handles page layouts.",
157
- ),
158
- ).toBe(false);
159
- });
160
- });