@vellumai/assistant 0.5.15 → 0.6.0

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 (503) hide show
  1. package/ARCHITECTURE.md +3 -3
  2. package/Dockerfile +0 -3
  3. package/docs/architecture/integrations.md +15 -14
  4. package/knip.json +4 -1
  5. package/openapi.yaml +670 -122
  6. package/package.json +1 -1
  7. package/src/__tests__/actor-token-service.test.ts +68 -0
  8. package/src/__tests__/agent-loop.test.ts +0 -32
  9. package/src/__tests__/always-loaded-tools-guard.test.ts +2 -2
  10. package/src/__tests__/anthropic-provider.test.ts +57 -3
  11. package/src/__tests__/app-compiler.test.ts +120 -0
  12. package/src/__tests__/assistant-feature-flags-integration.test.ts +5 -377
  13. package/src/__tests__/call-conversation-messages.test.ts +2 -6
  14. package/src/__tests__/call-domain.test.ts +2 -6
  15. package/src/__tests__/call-pointer-messages.test.ts +2 -14
  16. package/src/__tests__/call-recovery.test.ts +2 -6
  17. package/src/__tests__/call-routes-http.test.ts +2 -6
  18. package/src/__tests__/call-store.test.ts +2 -6
  19. package/src/__tests__/cancel-resolves-conversation-key.test.ts +2 -6
  20. package/src/__tests__/canonical-guardian-store.test.ts +2 -6
  21. package/src/__tests__/ces-rpc-credential-backend.test.ts +4 -1
  22. package/src/__tests__/channel-delivery-store.test.ts +2 -6
  23. package/src/__tests__/channel-retry-sweep.test.ts +2 -6
  24. package/src/__tests__/checker.test.ts +84 -3
  25. package/src/__tests__/clawhub.test.ts +54 -24
  26. package/src/__tests__/cli-command-risk-guard.test.ts +108 -6
  27. package/src/__tests__/cli-memory.test.ts +377 -0
  28. package/src/__tests__/computer-use-skill-manifest-regression.test.ts +12 -2
  29. package/src/__tests__/config-schema.test.ts +1 -3
  30. package/src/__tests__/config-set-platform-guard.test.ts +302 -0
  31. package/src/__tests__/config-watcher-feature-flags.test.ts +211 -0
  32. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +2 -6
  33. package/src/__tests__/contacts-tools.test.ts +31 -0
  34. package/src/__tests__/context-overflow-reducer.test.ts +86 -0
  35. package/src/__tests__/context-token-estimator.test.ts +175 -10
  36. package/src/__tests__/conversation-agent-loop-overflow.test.ts +9 -0
  37. package/src/__tests__/conversation-agent-loop.test.ts +9 -0
  38. package/src/__tests__/conversation-attachments.test.ts +2 -6
  39. package/src/__tests__/conversation-attention-store.test.ts +2 -6
  40. package/src/__tests__/conversation-clear-safety.test.ts +2 -6
  41. package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +4 -10
  42. package/src/__tests__/conversation-disk-view-integration.test.ts +2 -6
  43. package/src/__tests__/conversation-disk-view.test.ts +2 -6
  44. package/src/__tests__/conversation-error.test.ts +33 -2
  45. package/src/__tests__/conversation-fork-crud.test.ts +2 -6
  46. package/src/__tests__/conversation-history-web-search.test.ts +5 -0
  47. package/src/__tests__/conversation-load-history-repair.test.ts +5 -1
  48. package/src/__tests__/conversation-media-retry.test.ts +91 -0
  49. package/src/__tests__/conversation-runtime-assembly.test.ts +7 -4
  50. package/src/__tests__/conversation-slash-commands.test.ts +2 -6
  51. package/src/__tests__/conversation-starter-routes.test.ts +20 -11
  52. package/src/__tests__/conversation-store.test.ts +2 -6
  53. package/src/__tests__/conversation-usage.test.ts +3 -6
  54. package/src/__tests__/conversation-wipe.test.ts +11 -408
  55. package/src/__tests__/credential-execution-feature-gates.test.ts +3 -3
  56. package/src/__tests__/credential-execution-shell-lockdown.test.ts +2 -2
  57. package/src/__tests__/credential-security-e2e.test.ts +6 -1
  58. package/src/__tests__/docker-signing-key-bootstrap.test.ts +7 -73
  59. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +6 -7
  60. package/src/__tests__/followup-tools.test.ts +2 -6
  61. package/src/__tests__/graph-extraction-event-date.test.ts +186 -0
  62. package/src/__tests__/guardian-action-conversation-turn.test.ts +2 -6
  63. package/src/__tests__/guardian-action-followup-executor.test.ts +2 -6
  64. package/src/__tests__/guardian-action-followup-store.test.ts +2 -6
  65. package/src/__tests__/guardian-action-grant-mint-consume.test.ts +2 -6
  66. package/src/__tests__/guardian-action-late-reply.test.ts +2 -6
  67. package/src/__tests__/guardian-action-store.test.ts +2 -6
  68. package/src/__tests__/guardian-binding-drift-heal.test.ts +2 -6
  69. package/src/__tests__/guardian-decision-primitive-canonical.test.ts +8 -8
  70. package/src/__tests__/guardian-dispatch.test.ts +2 -6
  71. package/src/__tests__/guardian-grant-minting.test.ts +2 -14
  72. package/src/__tests__/guardian-principal-id-roundtrip.test.ts +2 -6
  73. package/src/__tests__/guardian-routing-invariants.test.ts +343 -6
  74. package/src/__tests__/guardian-routing-state.test.ts +2 -6
  75. package/src/__tests__/guardian-verification-voice-binding.test.ts +2 -6
  76. package/src/__tests__/heartbeat-service.test.ts +1 -3
  77. package/src/__tests__/inbound-invite-redemption.test.ts +2 -6
  78. package/src/__tests__/injection-block.test.ts +154 -0
  79. package/src/__tests__/install-meta.test.ts +506 -0
  80. package/src/__tests__/install-skill-routing.test.ts +292 -0
  81. package/src/__tests__/intent-routing.test.ts +6 -18
  82. package/src/__tests__/invite-redemption-service.test.ts +2 -6
  83. package/src/__tests__/invite-routes-http.test.ts +2 -6
  84. package/src/__tests__/jobs-store-qdrant-breaker.test.ts +2 -14
  85. package/src/__tests__/list-messages-attachments.test.ts +2 -6
  86. package/src/__tests__/llm-context-route-provider.test.ts +2 -6
  87. package/src/__tests__/llm-request-log-turn-query.test.ts +2 -6
  88. package/src/__tests__/llm-usage-store.test.ts +2 -6
  89. package/src/__tests__/log-export-workspace.test.ts +4 -34
  90. package/src/__tests__/managed-skill-lifecycle.test.ts +7 -37
  91. package/src/__tests__/managed-store.test.ts +40 -21
  92. package/src/__tests__/memory-jobs-worker-backoff.test.ts +2 -8
  93. package/src/__tests__/memory-recall-log-store.test.ts +2 -6
  94. package/src/__tests__/memory-upsert-concurrency.test.ts +4 -112
  95. package/src/__tests__/messaging-send-tool.test.ts +6 -6
  96. package/src/__tests__/migration-cross-version-compatibility.test.ts +1 -29
  97. package/src/__tests__/migration-export-http.test.ts +3 -34
  98. package/src/__tests__/migration-import-commit-http.test.ts +1 -29
  99. package/src/__tests__/migration-import-preflight-http.test.ts +3 -34
  100. package/src/__tests__/no-domain-routing-in-prompt-guard.test.ts +2 -1
  101. package/src/__tests__/non-member-access-request.test.ts +2 -6
  102. package/src/__tests__/notification-guardian-path.test.ts +2 -6
  103. package/src/__tests__/oauth-apps-routes.test.ts +120 -10
  104. package/src/__tests__/oauth-cli.test.ts +364 -2
  105. package/src/__tests__/oauth-connect-orchestrator.test.ts +709 -0
  106. package/src/__tests__/oauth-provider-serializer.test.ts +2 -1
  107. package/src/__tests__/oauth-provider-visibility.test.ts +149 -0
  108. package/src/__tests__/oauth-providers-routes.test.ts +5 -2
  109. package/src/__tests__/oauth-store.test.ts +0 -5
  110. package/src/__tests__/oauth2-gateway-transport.test.ts +18 -3
  111. package/src/__tests__/outlook-attachments.test.ts +301 -0
  112. package/src/__tests__/outlook-automation-tools.test.ts +425 -0
  113. package/src/__tests__/outlook-categories.test.ts +212 -0
  114. package/src/__tests__/outlook-client-automation.test.ts +246 -0
  115. package/src/__tests__/outlook-compose-tools.test.ts +325 -0
  116. package/src/__tests__/outlook-declutter-tools.test.ts +585 -0
  117. package/src/__tests__/outlook-email-watcher.test.ts +322 -0
  118. package/src/__tests__/outlook-follow-up.test.ts +196 -0
  119. package/src/__tests__/outlook-messaging-provider.test.ts +1071 -0
  120. package/src/__tests__/outlook-trash.test.ts +77 -0
  121. package/src/__tests__/outlook-unsubscribe.test.ts +250 -0
  122. package/src/__tests__/path-policy.test.ts +2 -17
  123. package/src/__tests__/permission-types.test.ts +0 -1
  124. package/src/__tests__/platform-callback-registration.test.ts +7 -11
  125. package/src/__tests__/playbook-execution.test.ts +76 -80
  126. package/src/__tests__/playbook-tools.test.ts +5 -7
  127. package/src/__tests__/provider-commit-message-generator.test.ts +0 -1
  128. package/src/__tests__/provider-error-scenarios.test.ts +21 -2
  129. package/src/__tests__/qdrant-manager.test.ts +68 -21
  130. package/src/__tests__/rebuild-index-graph-nodes.test.ts +273 -0
  131. package/src/__tests__/registry.test.ts +2 -2
  132. package/src/__tests__/require-fresh-approval.test.ts +64 -3
  133. package/src/__tests__/runtime-events-sse-parity.test.ts +2 -6
  134. package/src/__tests__/runtime-events-sse.test.ts +2 -6
  135. package/src/__tests__/sandbox-diagnostics.test.ts +20 -29
  136. package/src/__tests__/scaffold-managed-skill-tool.test.ts +2 -10
  137. package/src/__tests__/schedule-store.test.ts +2 -6
  138. package/src/__tests__/schedule-tools.test.ts +2 -6
  139. package/src/__tests__/scheduler-recurrence.test.ts +1 -5
  140. package/src/__tests__/scoped-approval-grants.test.ts +2 -6
  141. package/src/__tests__/scoped-grant-security-matrix.test.ts +2 -6
  142. package/src/__tests__/search-skills-unified.test.ts +421 -0
  143. package/src/__tests__/secret-allowlist.test.ts +20 -35
  144. package/src/__tests__/secret-onetime-send.test.ts +2 -0
  145. package/src/__tests__/send-endpoint-busy.test.ts +2 -6
  146. package/src/__tests__/sequence-store.test.ts +2 -6
  147. package/src/__tests__/server-history-render.test.ts +2 -6
  148. package/src/__tests__/shell-credential-ref.test.ts +0 -5
  149. package/src/__tests__/skill-feature-flags-integration.test.ts +38 -31
  150. package/src/__tests__/skill-feature-flags.test.ts +6 -6
  151. package/src/__tests__/skill-load-feature-flag.test.ts +13 -54
  152. package/src/__tests__/skill-load-inline-command.test.ts +3 -65
  153. package/src/__tests__/skill-load-inline-includes.test.ts +3 -65
  154. package/src/__tests__/skill-load-tool.test.ts +3 -67
  155. package/src/__tests__/skill-memory.test.ts +480 -195
  156. package/src/__tests__/skills-uninstall.test.ts +2 -2
  157. package/src/__tests__/skills.test.ts +23 -50
  158. package/src/__tests__/slack-channel-config.test.ts +2 -21
  159. package/src/__tests__/slack-inbound-verification.test.ts +2 -6
  160. package/src/__tests__/starter-bundle.test.ts +2 -8
  161. package/src/__tests__/stt-hints.test.ts +7 -2
  162. package/src/__tests__/system-prompt.test.ts +25 -45
  163. package/src/__tests__/task-compiler.test.ts +2 -27
  164. package/src/__tests__/task-management-tools.test.ts +2 -27
  165. package/src/__tests__/task-memory-cleanup.test.ts +173 -250
  166. package/src/__tests__/task-runner.test.ts +2 -27
  167. package/src/__tests__/task-scheduler.test.ts +2 -27
  168. package/src/__tests__/terminal-tools.test.ts +1 -17
  169. package/src/__tests__/test-preload.ts +3 -0
  170. package/src/__tests__/token-estimator-accuracy.benchmark.test.ts +0 -79
  171. package/src/__tests__/tool-approval-handler.test.ts +4 -27
  172. package/src/__tests__/tool-execution-abort-cleanup.test.ts +2 -11
  173. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +1 -25
  174. package/src/__tests__/tool-executor-lifecycle-events.test.ts +0 -1
  175. package/src/__tests__/tool-executor.test.ts +0 -1
  176. package/src/__tests__/tool-grant-request-escalation.test.ts +4 -27
  177. package/src/__tests__/tool-preview-lifecycle.test.ts +0 -20
  178. package/src/__tests__/tool-side-effects-slack-dm.test.ts +276 -0
  179. package/src/__tests__/trust-store.test.ts +10 -42
  180. package/src/__tests__/trusted-contact-approval-notifier.test.ts +1 -30
  181. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +3 -27
  182. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +2 -28
  183. package/src/__tests__/trusted-contact-multichannel.test.ts +2 -28
  184. package/src/__tests__/trusted-contact-verification.test.ts +2 -28
  185. package/src/__tests__/turn-boundary-resolution.test.ts +2 -34
  186. package/src/__tests__/twilio-provider.test.ts +0 -16
  187. package/src/__tests__/twilio-routes-twiml.test.ts +7 -12
  188. package/src/__tests__/twilio-routes.test.ts +0 -24
  189. package/src/__tests__/update-bulletin.test.ts +17 -89
  190. package/src/__tests__/usage-cache-backfill-migration.test.ts +1 -26
  191. package/src/__tests__/usage-routes.test.ts +2 -27
  192. package/src/__tests__/user-reference.test.ts +1 -5
  193. package/src/__tests__/vbundle-pax-and-symlink.test.ts +4 -34
  194. package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +2 -53
  195. package/src/__tests__/verification-control-plane-policy.test.ts +0 -2
  196. package/src/__tests__/voice-invite-redemption.test.ts +2 -27
  197. package/src/__tests__/voice-scoped-grant-consumer.test.ts +2 -30
  198. package/src/__tests__/voice-session-bridge.test.ts +2 -27
  199. package/src/__tests__/volume-security-guard.test.ts +2 -0
  200. package/src/__tests__/workspace-lifecycle.test.ts +29 -1
  201. package/src/__tests__/workspace-migration-009-backfill-conversation-disk-view.test.ts +4 -29
  202. package/src/__tests__/workspace-migration-012-rename-conversation-disk-view-dirs.test.ts +2 -2
  203. package/src/__tests__/workspace-migration-013-repair-conversation-disk-view.test.ts +4 -29
  204. package/src/__tests__/workspace-migration-026-backfill-install-meta.test.ts +558 -0
  205. package/src/__tests__/workspace-migration-down-functions.test.ts +0 -6
  206. package/src/__tests__/workspace-policy.test.ts +1 -1
  207. package/src/acp/client-handler.ts +1 -2
  208. package/src/agent/attachments.ts +7 -2
  209. package/src/agent/image-optimize.ts +165 -0
  210. package/src/agent/loop.ts +1 -15
  211. package/src/bundler/app-compiler.ts +179 -2
  212. package/src/bundler/package-resolver.ts +3 -5
  213. package/src/cli/__tests__/notifications.test.ts +1 -24
  214. package/src/cli/cli-memory.ts +179 -0
  215. package/src/cli/commands/avatar.ts +3 -3
  216. package/src/cli/commands/config.ts +26 -13
  217. package/src/cli/commands/doctor.ts +2 -2
  218. package/src/cli/commands/memory.ts +41 -55
  219. package/src/cli/commands/oauth/__tests__/connect.test.ts +2 -2
  220. package/src/cli/commands/oauth/__tests__/disconnect.test.ts +2 -2
  221. package/src/cli/commands/oauth/__tests__/mode.test.ts +8 -1
  222. package/src/cli/commands/oauth/__tests__/providers-update.test.ts +1 -1
  223. package/src/cli/commands/oauth/__tests__/status.test.ts +2 -2
  224. package/src/cli/commands/oauth/connect.ts +26 -6
  225. package/src/cli/commands/oauth/mode.ts +7 -0
  226. package/src/cli/commands/oauth/providers.ts +49 -42
  227. package/src/cli/commands/oauth/shared.ts +39 -3
  228. package/src/cli/commands/platform/__tests__/connect.test.ts +3 -49
  229. package/src/cli/commands/platform/__tests__/disconnect.test.ts +3 -49
  230. package/src/cli/commands/platform/__tests__/status.test.ts +5 -55
  231. package/src/cli/commands/platform/index.ts +16 -16
  232. package/src/cli/commands/skills.ts +88 -16
  233. package/src/cli/commands/trust.ts +2 -2
  234. package/src/cli/lib/daemon-credential-client.ts +2 -3
  235. package/src/config/bundled-skills/acp/TOOLS.json +1 -1
  236. package/src/config/bundled-skills/computer-use/TOOLS.json +7 -7
  237. package/src/config/bundled-skills/contacts/SKILL.md +0 -1
  238. package/src/config/bundled-skills/contacts/TOOLS.json +0 -8
  239. package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +0 -4
  240. package/src/config/bundled-skills/gmail/SKILL.md +2 -10
  241. package/src/config/bundled-skills/google-calendar/SKILL.md +1 -9
  242. package/src/config/bundled-skills/messaging/SKILL.md +26 -19
  243. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +40 -33
  244. package/src/config/bundled-skills/outlook/SKILL.md +189 -0
  245. package/src/config/bundled-skills/outlook/TOOLS.json +530 -0
  246. package/src/config/bundled-skills/outlook/tools/outlook-attachments.ts +85 -0
  247. package/src/config/bundled-skills/outlook/tools/outlook-categories.ts +77 -0
  248. package/src/config/bundled-skills/outlook/tools/outlook-draft.ts +84 -0
  249. package/src/config/bundled-skills/outlook/tools/outlook-follow-up.ts +94 -0
  250. package/src/config/bundled-skills/outlook/tools/outlook-forward.ts +49 -0
  251. package/src/config/bundled-skills/outlook/tools/outlook-outreach-scan.ts +237 -0
  252. package/src/config/bundled-skills/outlook/tools/outlook-rules.ts +161 -0
  253. package/src/config/bundled-skills/outlook/tools/outlook-send-draft.ts +32 -0
  254. package/src/config/bundled-skills/outlook/tools/outlook-sender-digest.ts +272 -0
  255. package/src/config/bundled-skills/outlook/tools/outlook-trash.ts +29 -0
  256. package/src/config/bundled-skills/outlook/tools/outlook-unsubscribe.ts +129 -0
  257. package/src/config/bundled-skills/outlook/tools/outlook-vacation.ts +87 -0
  258. package/src/config/bundled-skills/outlook/tools/shared.ts +20 -0
  259. package/src/config/bundled-skills/outlook-calendar/SKILL.md +51 -0
  260. package/src/config/bundled-skills/outlook-calendar/TOOLS.json +221 -0
  261. package/src/config/bundled-skills/outlook-calendar/calendar-client.ts +252 -0
  262. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-check-availability.ts +53 -0
  263. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-create-event.ts +74 -0
  264. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-get-event.ts +18 -0
  265. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-list-events.ts +46 -0
  266. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-rsvp.ts +36 -0
  267. package/src/config/bundled-skills/outlook-calendar/tools/shared.ts +17 -0
  268. package/src/config/bundled-skills/outlook-calendar/types.ts +120 -0
  269. package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +47 -40
  270. package/src/config/bundled-skills/playbooks/tools/playbook-delete.ts +16 -29
  271. package/src/config/bundled-skills/playbooks/tools/playbook-list.ts +16 -18
  272. package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +39 -47
  273. package/src/config/bundled-skills/settings/TOOLS.json +3 -3
  274. package/src/config/bundled-skills/slack/SKILL.md +1 -7
  275. package/src/config/bundled-tool-registry.ts +56 -4
  276. package/src/config/env-registry.ts +15 -8
  277. package/src/config/feature-flag-registry.json +29 -116
  278. package/src/config/loader.ts +4 -0
  279. package/src/config/schemas/platform.ts +8 -0
  280. package/src/config/schemas/security.ts +0 -6
  281. package/src/config/schemas/services.ts +8 -0
  282. package/src/config/schemas/timeouts.ts +1 -1
  283. package/src/config/skills.ts +18 -7
  284. package/src/context/token-estimator.ts +25 -18
  285. package/src/context/window-manager.ts +32 -9
  286. package/src/credential-execution/approval-bridge.ts +0 -1
  287. package/src/credential-execution/process-manager.ts +3 -1
  288. package/src/daemon/config-watcher.ts +51 -0
  289. package/src/daemon/context-overflow-reducer.ts +46 -2
  290. package/src/daemon/conversation-agent-loop-handlers.ts +123 -82
  291. package/src/daemon/conversation-agent-loop.ts +99 -63
  292. package/src/daemon/conversation-error.ts +31 -8
  293. package/src/daemon/conversation-lifecycle.ts +33 -0
  294. package/src/daemon/conversation-media-retry.ts +85 -7
  295. package/src/daemon/conversation-notifiers.ts +4 -1
  296. package/src/daemon/conversation-process.ts +1 -0
  297. package/src/daemon/conversation-runtime-assembly.ts +5 -0
  298. package/src/daemon/conversation-usage.ts +1 -0
  299. package/src/daemon/conversation.ts +41 -2
  300. package/src/daemon/daemon-control.ts +8 -2
  301. package/src/daemon/handlers/shared.ts +22 -12
  302. package/src/daemon/handlers/skills.ts +423 -201
  303. package/src/daemon/lifecycle.ts +52 -4
  304. package/src/daemon/main.ts +5 -1
  305. package/src/daemon/message-types/conversations.ts +5 -1
  306. package/src/daemon/message-types/messages.ts +3 -1
  307. package/src/daemon/message-types/skills.ts +97 -36
  308. package/src/daemon/providers-setup.ts +7 -0
  309. package/src/daemon/server.ts +35 -22
  310. package/src/daemon/tool-side-effects.ts +27 -5
  311. package/src/events/domain-events.ts +1 -2
  312. package/src/heartbeat/heartbeat-service.ts +1 -0
  313. package/src/hooks/cli.ts +2 -2
  314. package/src/hooks/runner.ts +15 -38
  315. package/src/inbound/platform-callback-registration.ts +14 -14
  316. package/src/memory/admin.ts +11 -45
  317. package/src/memory/conversation-bootstrap.ts +2 -0
  318. package/src/memory/conversation-crud.ts +242 -348
  319. package/src/memory/conversation-group-migration.ts +157 -0
  320. package/src/memory/conversation-queries.ts +4 -2
  321. package/src/memory/db-init.ts +39 -3
  322. package/src/memory/embed.ts +73 -0
  323. package/src/memory/embedding-backend.ts +8 -14
  324. package/src/memory/embedding-runtime-manager.ts +12 -114
  325. package/src/memory/fingerprint.ts +2 -2
  326. package/src/memory/graph/bootstrap.ts +512 -0
  327. package/src/memory/graph/capability-seed.ts +297 -0
  328. package/src/memory/graph/consolidation.ts +691 -0
  329. package/src/memory/graph/conversation-graph-memory.ts +630 -0
  330. package/src/memory/graph/decay.test.ts +208 -0
  331. package/src/memory/graph/decay.ts +195 -0
  332. package/src/memory/graph/extraction-job.ts +69 -0
  333. package/src/memory/graph/extraction.test.ts +936 -0
  334. package/src/memory/graph/extraction.ts +1254 -0
  335. package/src/memory/graph/graph-search.ts +266 -0
  336. package/src/memory/graph/image-ref-utils.ts +29 -0
  337. package/src/memory/graph/injection.test.ts +513 -0
  338. package/src/memory/graph/injection.ts +439 -0
  339. package/src/memory/graph/inspect.ts +534 -0
  340. package/src/memory/graph/narrative.ts +267 -0
  341. package/src/memory/graph/pattern-scan.ts +269 -0
  342. package/src/memory/graph/retriever.ts +1008 -0
  343. package/src/memory/graph/scoring.test.ts +548 -0
  344. package/src/memory/graph/scoring.ts +232 -0
  345. package/src/memory/graph/serendipity.ts +65 -0
  346. package/src/memory/graph/store.test.ts +1050 -0
  347. package/src/memory/graph/store.ts +699 -0
  348. package/src/memory/graph/tool-handlers.ts +426 -0
  349. package/src/memory/graph/tools.ts +141 -0
  350. package/src/memory/graph/triggers.test.ts +487 -0
  351. package/src/memory/graph/triggers.ts +223 -0
  352. package/src/memory/graph/types.ts +271 -0
  353. package/src/memory/group-crud.ts +191 -0
  354. package/src/memory/indexer.ts +37 -19
  355. package/src/memory/job-handlers/cleanup.ts +0 -53
  356. package/src/memory/job-handlers/conversation-starters.ts +91 -53
  357. package/src/memory/job-handlers/embedding.test.ts +3 -27
  358. package/src/memory/job-handlers/embedding.ts +5 -31
  359. package/src/memory/job-handlers/index-maintenance.ts +23 -11
  360. package/src/memory/job-handlers/summarization.ts +32 -17
  361. package/src/memory/job-utils.ts +1 -1
  362. package/src/memory/jobs-store.ts +50 -70
  363. package/src/memory/jobs-worker.ts +147 -112
  364. package/src/memory/llm-usage-store.ts +35 -2
  365. package/src/memory/message-content.ts +1 -0
  366. package/src/memory/migrations/201-oauth-providers-feature-flag.ts +11 -0
  367. package/src/memory/migrations/202-drop-callback-transport-column.ts +13 -0
  368. package/src/memory/migrations/202-memory-graph-tables.ts +130 -0
  369. package/src/memory/migrations/203-drop-memory-items-tables.ts +23 -0
  370. package/src/memory/migrations/204-rename-memory-graph-type-values.ts +46 -0
  371. package/src/memory/migrations/205-memory-graph-image-refs.ts +11 -0
  372. package/src/memory/migrations/index.ts +6 -0
  373. package/src/memory/migrations/registry.ts +8 -0
  374. package/src/memory/qdrant-client.ts +44 -17
  375. package/src/memory/qdrant-manager.ts +26 -5
  376. package/src/memory/schema/index.ts +1 -0
  377. package/src/memory/schema/memory-graph.ts +139 -0
  378. package/src/memory/schema/oauth.ts +1 -1
  379. package/src/memory/search/semantic.ts +47 -91
  380. package/src/memory/slack-thread-store.ts +17 -0
  381. package/src/memory/task-memory-cleanup.ts +28 -50
  382. package/src/messaging/providers/outlook/adapter.ts +200 -0
  383. package/src/messaging/providers/outlook/client.ts +610 -0
  384. package/src/messaging/providers/outlook/types.ts +201 -0
  385. package/src/notifications/adapters/macos.ts +1 -0
  386. package/src/notifications/adapters/slack.ts +1 -1
  387. package/src/notifications/copy-composer.ts +9 -0
  388. package/src/notifications/signal.ts +16 -0
  389. package/src/oauth/__tests__/identity-verifier.test.ts +1 -1
  390. package/src/oauth/connect-orchestrator.ts +10 -3
  391. package/src/oauth/oauth-store.ts +10 -11
  392. package/src/oauth/provider-serializer.ts +3 -0
  393. package/src/oauth/provider-visibility.ts +16 -0
  394. package/src/oauth/seed-providers.ts +50 -17
  395. package/src/permissions/checker.ts +62 -9
  396. package/src/permissions/defaults.ts +4 -4
  397. package/src/permissions/types.ts +2 -4
  398. package/src/permissions/workspace-policy.ts +1 -1
  399. package/src/playbooks/playbook-compiler.ts +19 -18
  400. package/src/playbooks/types.ts +4 -3
  401. package/src/prompts/system-prompt.ts +6 -93
  402. package/src/prompts/templates/UPDATES.md +6 -0
  403. package/src/providers/anthropic/client.ts +47 -19
  404. package/src/providers/gemini/client.ts +1 -1
  405. package/src/providers/openai/client.ts +1 -1
  406. package/src/providers/registry.ts +1 -1
  407. package/src/providers/retry.ts +19 -3
  408. package/src/runtime/actor-trust-resolver.ts +5 -1
  409. package/src/runtime/auth/__tests__/credential-service.test.ts +1 -27
  410. package/src/runtime/auth/__tests__/token-service.test.ts +1 -25
  411. package/src/runtime/auth/route-policy.ts +7 -4
  412. package/src/runtime/guardian-reply-router.ts +10 -2
  413. package/src/runtime/http-server.ts +23 -3
  414. package/src/runtime/middleware/auth.ts +20 -0
  415. package/src/runtime/routes/attachment-routes.test.ts +106 -0
  416. package/src/runtime/routes/attachment-routes.ts +106 -16
  417. package/src/runtime/routes/brain-graph-routes.ts +21 -22
  418. package/src/runtime/routes/btw-routes.ts +8 -0
  419. package/src/runtime/routes/conversation-management-routes.ts +2 -0
  420. package/src/runtime/routes/conversation-query-routes.ts +2 -58
  421. package/src/runtime/routes/conversation-starter-routes.ts +2 -2
  422. package/src/runtime/routes/debug-routes.ts +1 -1
  423. package/src/runtime/routes/global-search-routes.ts +21 -19
  424. package/src/runtime/routes/group-routes.ts +207 -0
  425. package/src/runtime/routes/guardian-action-routes.ts +21 -10
  426. package/src/runtime/routes/guardian-bootstrap-routes.ts +23 -19
  427. package/src/runtime/routes/inbound-message-handler.ts +19 -0
  428. package/src/runtime/routes/inbound-stages/background-dispatch.ts +43 -2
  429. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.test.ts +292 -0
  430. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +207 -0
  431. package/src/runtime/routes/memory-item-routes.test.ts +2 -31
  432. package/src/runtime/routes/memory-item-routes.ts +385 -341
  433. package/src/runtime/routes/oauth-apps.ts +18 -1
  434. package/src/runtime/routes/oauth-providers.ts +13 -1
  435. package/src/runtime/routes/schedule-routes.ts +2 -0
  436. package/src/runtime/routes/settings-routes.ts +1 -0
  437. package/src/runtime/routes/skills-routes.ts +103 -37
  438. package/src/runtime/routes/usage-routes.ts +19 -2
  439. package/src/runtime/routes/work-items-routes.test.ts +2 -27
  440. package/src/runtime/routes/workspace-routes.test.ts +3 -27
  441. package/src/schedule/scheduler.ts +8 -1
  442. package/src/security/oauth2.ts +1 -1
  443. package/src/security/secret-allowlist.ts +4 -4
  444. package/src/security/secure-keys.ts +4 -8
  445. package/src/shared/provider-env-vars.ts +19 -0
  446. package/src/skills/catalog-cache.ts +5 -0
  447. package/src/skills/catalog-install.ts +15 -14
  448. package/src/skills/clawhub.ts +134 -154
  449. package/src/skills/install-meta.ts +208 -0
  450. package/src/skills/managed-store.ts +27 -16
  451. package/src/skills/skill-memory.ts +210 -96
  452. package/src/skills/skillssh-registry.ts +19 -17
  453. package/src/tasks/task-runner.ts +3 -1
  454. package/src/telemetry/usage-telemetry-reporter.test.ts +3 -5
  455. package/src/tools/browser/runtime-check.ts +3 -1
  456. package/src/tools/memory/register.ts +63 -46
  457. package/src/tools/permission-checker.ts +7 -19
  458. package/src/tools/shared/filesystem/image-read.ts +22 -85
  459. package/src/tools/skills/skill-script-runner.ts +1 -1
  460. package/src/tools/terminal/safe-env.ts +1 -0
  461. package/src/tools/tool-manifest.ts +3 -3
  462. package/src/util/browser.ts +25 -10
  463. package/src/util/bun-runtime.ts +172 -0
  464. package/src/util/device-id.ts +3 -65
  465. package/src/watcher/providers/outlook-calendar.ts +343 -0
  466. package/src/watcher/providers/outlook.ts +198 -0
  467. package/src/workspace/git-service.ts +27 -6
  468. package/src/workspace/migrations/025-remove-oauth-app-setup-skills.ts +76 -0
  469. package/src/workspace/migrations/026-backfill-install-meta.ts +325 -0
  470. package/src/workspace/migrations/027-remove-orphaned-optimized-images-cache.ts +42 -0
  471. package/src/workspace/migrations/registry.ts +6 -0
  472. package/src/__tests__/context-memory-e2e.test.ts +0 -415
  473. package/src/__tests__/journal-context.test.ts +0 -268
  474. package/src/__tests__/memory-context-benchmark.benchmark.test.ts +0 -297
  475. package/src/__tests__/memory-lifecycle-e2e.test.ts +0 -459
  476. package/src/__tests__/memory-query-builder.test.ts +0 -59
  477. package/src/__tests__/memory-recall-quality.test.ts +0 -1046
  478. package/src/__tests__/memory-regressions.experimental.test.ts +0 -629
  479. package/src/__tests__/memory-regressions.test.ts +0 -3696
  480. package/src/__tests__/memory-retrieval.benchmark.test.ts +0 -295
  481. package/src/daemon/conversation-memory.ts +0 -207
  482. package/src/memory/conversation-starters-cadence.ts +0 -74
  483. package/src/memory/items-extractor.ts +0 -860
  484. package/src/memory/job-handlers/batch-extraction.ts +0 -741
  485. package/src/memory/job-handlers/extraction.ts +0 -40
  486. package/src/memory/job-handlers/journal-carry-forward.test.ts +0 -383
  487. package/src/memory/job-handlers/journal-carry-forward.ts +0 -255
  488. package/src/memory/journal-memory.ts +0 -224
  489. package/src/memory/query-builder.ts +0 -47
  490. package/src/memory/query-expansion.ts +0 -83
  491. package/src/memory/retriever.test.ts +0 -1590
  492. package/src/memory/retriever.ts +0 -1323
  493. package/src/memory/search/formatting.test.ts +0 -140
  494. package/src/memory/search/formatting.ts +0 -262
  495. package/src/memory/search/mmr.ts +0 -136
  496. package/src/memory/search/ranking.ts +0 -15
  497. package/src/memory/search/staleness.ts +0 -40
  498. package/src/memory/search/tier-classifier.ts +0 -18
  499. package/src/memory/search/types.ts +0 -121
  500. package/src/prompts/journal-context.ts +0 -156
  501. package/src/tools/memory/definitions.ts +0 -69
  502. package/src/tools/memory/handlers.test.ts +0 -590
  503. package/src/tools/memory/handlers.ts +0 -434
@@ -0,0 +1,84 @@
1
+ import {
2
+ createDraft,
3
+ createReplyDraft,
4
+ patchMessage,
5
+ } from "../../../../messaging/providers/outlook/client.js";
6
+ import type { OutlookRecipient } from "../../../../messaging/providers/outlook/types.js";
7
+ import { resolveOAuthConnection } from "../../../../oauth/connection-resolver.js";
8
+ import type {
9
+ ToolContext,
10
+ ToolExecutionResult,
11
+ } from "../../../../tools/types.js";
12
+ import { err, ok } from "./shared.js";
13
+
14
+ function toRecipients(csv: string | undefined): OutlookRecipient[] | undefined {
15
+ if (!csv) return undefined;
16
+ return csv
17
+ .split(",")
18
+ .map((s) => s.trim())
19
+ .filter(Boolean)
20
+ .map((address) => ({ emailAddress: { address } }));
21
+ }
22
+
23
+ export async function run(
24
+ input: Record<string, unknown>,
25
+ _context: ToolContext,
26
+ ): Promise<ToolExecutionResult> {
27
+ const account = input.account as string | undefined;
28
+ const to = input.to as string;
29
+ const subject = input.subject as string;
30
+ const body = input.body as string;
31
+ const inReplyTo = input.in_reply_to as string | undefined;
32
+ const cc = input.cc as string | undefined;
33
+ const bcc = input.bcc as string | undefined;
34
+
35
+ if (!to) return err("to is required.");
36
+ if (!subject) return err("subject is required.");
37
+ if (!body) return err("body is required.");
38
+
39
+ try {
40
+ const connection = await resolveOAuthConnection("outlook", {
41
+ account,
42
+ });
43
+
44
+ if (inReplyTo) {
45
+ // Create a reply draft, then optionally patch recipients
46
+ const draft = await createReplyDraft(connection, inReplyTo, body);
47
+
48
+ const toList = toRecipients(to);
49
+ const ccList = toRecipients(cc);
50
+ const bccList = toRecipients(bcc);
51
+
52
+ const patch: Record<string, unknown> = {};
53
+ if (toList) patch.toRecipients = toList;
54
+ if (ccList) patch.ccRecipients = ccList;
55
+ if (bccList) patch.bccRecipients = bccList;
56
+
57
+ if (Object.keys(patch).length > 0) {
58
+ await patchMessage(connection, draft.id, patch);
59
+ }
60
+
61
+ return ok(
62
+ `Draft created (ID: ${draft.id}). It will appear in your Outlook Drafts folder. Tell me to send it when ready.`,
63
+ );
64
+ }
65
+
66
+ const toRecipientsList = toRecipients(to) ?? [];
67
+ const ccRecipientsList = toRecipients(cc);
68
+ const bccRecipientsList = toRecipients(bcc);
69
+
70
+ const draft = await createDraft(connection, {
71
+ subject,
72
+ body: { contentType: "text", content: body },
73
+ toRecipients: toRecipientsList,
74
+ ccRecipients: ccRecipientsList,
75
+ bccRecipients: bccRecipientsList,
76
+ });
77
+
78
+ return ok(
79
+ `Draft created (ID: ${draft.id}). It will appear in your Outlook Drafts folder. Tell me to send it when ready.`,
80
+ );
81
+ } catch (e) {
82
+ return err(e instanceof Error ? e.message : String(e));
83
+ }
84
+ }
@@ -0,0 +1,94 @@
1
+ import {
2
+ listMessages,
3
+ updateMessageFlag,
4
+ } from "../../../../messaging/providers/outlook/client.js";
5
+ import { resolveOAuthConnection } from "../../../../oauth/connection-resolver.js";
6
+ import type {
7
+ ToolContext,
8
+ ToolExecutionResult,
9
+ } from "../../../../tools/types.js";
10
+ import { err, ok } from "./shared.js";
11
+
12
+ export async function run(
13
+ input: Record<string, unknown>,
14
+ _context: ToolContext,
15
+ ): Promise<ToolExecutionResult> {
16
+ const account = input.account as string | undefined;
17
+ const action = input.action as string;
18
+
19
+ if (!action) {
20
+ return err("action is required (track, list, untrack, or complete).");
21
+ }
22
+
23
+ try {
24
+ const connection = await resolveOAuthConnection("outlook", {
25
+ account,
26
+ });
27
+
28
+ switch (action) {
29
+ case "track": {
30
+ const messageId = input.message_id as string;
31
+ if (!messageId) return err("message_id is required for track action.");
32
+
33
+ await updateMessageFlag(connection, messageId, {
34
+ flagStatus: "flagged",
35
+ });
36
+ return ok("Message flagged for follow-up.");
37
+ }
38
+
39
+ case "complete": {
40
+ const messageId = input.message_id as string;
41
+ if (!messageId)
42
+ return err("message_id is required for complete action.");
43
+
44
+ await updateMessageFlag(connection, messageId, {
45
+ flagStatus: "complete",
46
+ });
47
+ return ok("Follow-up marked complete.");
48
+ }
49
+
50
+ case "untrack": {
51
+ const messageId = input.message_id as string;
52
+ if (!messageId)
53
+ return err("message_id is required for untrack action.");
54
+
55
+ await updateMessageFlag(connection, messageId, {
56
+ flagStatus: "notFlagged",
57
+ });
58
+ return ok("Follow-up flag removed.");
59
+ }
60
+
61
+ case "list": {
62
+ const resp = await listMessages(connection, {
63
+ filter: "flag/flagStatus eq 'flagged'",
64
+ top: 50,
65
+ select:
66
+ "id,conversationId,subject,bodyPreview,body,from,toRecipients,receivedDateTime,isRead,hasAttachments,parentFolderId,categories,flag",
67
+ orderby: "receivedDateTime desc",
68
+ });
69
+
70
+ const messages = resp.value ?? [];
71
+ if (messages.length === 0) {
72
+ return ok("No messages are currently flagged for follow-up.");
73
+ }
74
+
75
+ const items = messages.map((m) => ({
76
+ id: m.id,
77
+ conversationId: m.conversationId,
78
+ subject: m.subject,
79
+ from: m.from?.emailAddress?.address ?? "",
80
+ date: m.receivedDateTime,
81
+ }));
82
+
83
+ return ok(JSON.stringify(items, null, 2));
84
+ }
85
+
86
+ default:
87
+ return err(
88
+ `Unknown action "${action}". Use track, list, untrack, or complete.`,
89
+ );
90
+ }
91
+ } catch (e) {
92
+ return err(e instanceof Error ? e.message : String(e));
93
+ }
94
+ }
@@ -0,0 +1,49 @@
1
+ import { createForwardDraft } from "../../../../messaging/providers/outlook/client.js";
2
+ import type { OutlookRecipient } from "../../../../messaging/providers/outlook/types.js";
3
+ import { resolveOAuthConnection } from "../../../../oauth/connection-resolver.js";
4
+ import type {
5
+ ToolContext,
6
+ ToolExecutionResult,
7
+ } from "../../../../tools/types.js";
8
+ import { err, ok } from "./shared.js";
9
+
10
+ function toRecipients(csv: string): OutlookRecipient[] {
11
+ return csv
12
+ .split(",")
13
+ .map((s) => s.trim())
14
+ .filter(Boolean)
15
+ .map((address) => ({ emailAddress: { address } }));
16
+ }
17
+
18
+ export async function run(
19
+ input: Record<string, unknown>,
20
+ _context: ToolContext,
21
+ ): Promise<ToolExecutionResult> {
22
+ const account = input.account as string | undefined;
23
+ const messageId = input.message_id as string;
24
+ const to = input.to as string;
25
+ const comment = input.comment as string | undefined;
26
+
27
+ if (!messageId) return err("message_id is required.");
28
+ if (!to) return err("to is required.");
29
+
30
+ try {
31
+ const connection = await resolveOAuthConnection("outlook", {
32
+ account,
33
+ });
34
+
35
+ const toRecipientsList = toRecipients(to);
36
+ const draft = await createForwardDraft(
37
+ connection,
38
+ messageId,
39
+ toRecipientsList,
40
+ comment,
41
+ );
42
+
43
+ return ok(
44
+ `Forward draft created (ID: ${draft.id}). Review in Outlook Drafts, then tell me to send it.`,
45
+ );
46
+ } catch (e) {
47
+ return err(e instanceof Error ? e.message : String(e));
48
+ }
49
+ }
@@ -0,0 +1,237 @@
1
+ import {
2
+ batchGetMessages,
3
+ listMessages,
4
+ } from "../../../../messaging/providers/outlook/client.js";
5
+ import type { OutlookMessage } from "../../../../messaging/providers/outlook/types.js";
6
+ import { resolveOAuthConnection } from "../../../../oauth/connection-resolver.js";
7
+ import type {
8
+ ToolContext,
9
+ ToolExecutionResult,
10
+ } from "../../../../tools/types.js";
11
+ import { storeScanResult } from "../../gmail/tools/scan-result-store.js";
12
+ import { err, ok } from "./shared.js";
13
+
14
+ const MAX_MESSAGES_CAP = 5000;
15
+ const MAX_IDS_PER_SENDER = 5000;
16
+ const MAX_SAMPLE_SUBJECTS = 3;
17
+
18
+ interface OutreachSenderAggregation {
19
+ displayName: string;
20
+ email: string;
21
+ messageCount: number;
22
+ newestMessageId: string;
23
+ oldestDate: string;
24
+ newestDate: string;
25
+ messageIds: string[];
26
+ hasMore: boolean;
27
+ sampleSubjects: string[];
28
+ }
29
+
30
+ /** Parse a time range string like "90d" or "30d" into milliseconds. */
31
+ function parseTimeRange(timeRange: string): number {
32
+ const match = timeRange.match(/^(\d+)([dhm])$/);
33
+ if (!match) return 90 * 24 * 60 * 60 * 1000; // default 90 days
34
+ const value = parseInt(match[1], 10);
35
+ switch (match[2]) {
36
+ case "d":
37
+ return value * 24 * 60 * 60 * 1000;
38
+ case "h":
39
+ return value * 60 * 60 * 1000;
40
+ case "m":
41
+ return value * 60 * 1000;
42
+ default:
43
+ return 90 * 24 * 60 * 60 * 1000;
44
+ }
45
+ }
46
+
47
+ export async function run(
48
+ input: Record<string, unknown>,
49
+ _context: ToolContext,
50
+ ): Promise<ToolExecutionResult> {
51
+ const account = input.account as string | undefined;
52
+ const maxSenders = (input.max_senders as number) ?? 30;
53
+ const timeRange = (input.time_range as string) ?? "90d";
54
+
55
+ try {
56
+ const connection = await resolveOAuthConnection("outlook", {
57
+ account,
58
+ });
59
+
60
+ // Build OData filter: inbox messages from the specified time range
61
+ const sinceDate = new Date(
62
+ Date.now() - parseTimeRange(timeRange),
63
+ ).toISOString();
64
+ const dateFilter = `receivedDateTime ge ${sinceDate}`;
65
+
66
+ const allMessageIds: string[] = [];
67
+ const fetchPromises: Promise<OutlookMessage[]>[] = [];
68
+ let skip = 0;
69
+ let truncated = false;
70
+ let timeBudgetExceeded = false;
71
+ const startTime = Date.now();
72
+ const TIME_BUDGET_MS = 90_000;
73
+
74
+ // Paginate through messages
75
+ while (allMessageIds.length < MAX_MESSAGES_CAP) {
76
+ if (Date.now() - startTime > TIME_BUDGET_MS) {
77
+ timeBudgetExceeded = true;
78
+ truncated = true;
79
+ break;
80
+ }
81
+ const pageSize = Math.min(100, MAX_MESSAGES_CAP - allMessageIds.length);
82
+
83
+ const listResp = await listMessages(connection, {
84
+ top: pageSize,
85
+ skip,
86
+ filter: dateFilter,
87
+ orderby: "receivedDateTime desc",
88
+ select: "id,from,receivedDateTime,subject",
89
+ });
90
+
91
+ const messages = listResp.value ?? [];
92
+ if (messages.length === 0) break;
93
+
94
+ const ids = messages.map((m) => m.id);
95
+ allMessageIds.push(...ids);
96
+
97
+ // Fetch internet message headers to check for List-Unsubscribe
98
+ fetchPromises.push(
99
+ batchGetMessages(
100
+ connection,
101
+ ids,
102
+ "id,from,receivedDateTime,subject,internetMessageHeaders",
103
+ ),
104
+ );
105
+
106
+ skip += messages.length;
107
+ if (messages.length < pageSize) break;
108
+ }
109
+
110
+ if (allMessageIds.length >= MAX_MESSAGES_CAP) {
111
+ truncated = true;
112
+ }
113
+
114
+ if (allMessageIds.length === 0) {
115
+ return ok(
116
+ JSON.stringify({
117
+ senders: [],
118
+ total_scanned: 0,
119
+ note: "No emails found matching the query.",
120
+ }),
121
+ );
122
+ }
123
+
124
+ const fetchedMessages = (await Promise.all(fetchPromises)).flat();
125
+
126
+ // First pass: track which senders have ANY messages with List-Unsubscribe
127
+ const sendersWithUnsubscribe = new Set<string>();
128
+ for (const msg of fetchedMessages) {
129
+ const fromEmail = msg.from?.emailAddress?.address?.toLowerCase();
130
+ if (!fromEmail) continue;
131
+ const hasUnsub = msg.internetMessageHeaders?.some(
132
+ (h) => h.name.toLowerCase() === "list-unsubscribe",
133
+ );
134
+ if (hasUnsub) sendersWithUnsubscribe.add(fromEmail);
135
+ }
136
+
137
+ // Second pass: aggregate only senders WITHOUT List-Unsubscribe
138
+ const senderMap = new Map<string, OutreachSenderAggregation>();
139
+
140
+ for (const msg of fetchedMessages) {
141
+ const fromEmail = msg.from?.emailAddress?.address?.toLowerCase();
142
+ const fromName = msg.from?.emailAddress?.name ?? "";
143
+ const subject = msg.subject ?? "";
144
+ const dateStr = msg.receivedDateTime ?? "";
145
+
146
+ if (!fromEmail) continue;
147
+ // Skip senders that have any messages with List-Unsubscribe
148
+ if (sendersWithUnsubscribe.has(fromEmail)) continue;
149
+
150
+ let agg = senderMap.get(fromEmail);
151
+ if (!agg) {
152
+ agg = {
153
+ displayName: fromName,
154
+ email: fromEmail,
155
+ messageCount: 0,
156
+ newestMessageId: msg.id,
157
+ oldestDate: dateStr,
158
+ newestDate: dateStr,
159
+ messageIds: [],
160
+ hasMore: false,
161
+ sampleSubjects: [],
162
+ };
163
+ senderMap.set(fromEmail, agg);
164
+ }
165
+
166
+ agg.messageCount++;
167
+
168
+ if (!agg.displayName && fromName) agg.displayName = fromName;
169
+
170
+ if (agg.messageIds.length < MAX_IDS_PER_SENDER) {
171
+ agg.messageIds.push(msg.id);
172
+ } else {
173
+ agg.hasMore = true;
174
+ }
175
+
176
+ // Track date range
177
+ const msgEpoch = dateStr ? new Date(dateStr).getTime() : 0;
178
+ const oldestEpoch = agg.oldestDate
179
+ ? new Date(agg.oldestDate).getTime()
180
+ : Infinity;
181
+ const newestEpoch = agg.newestDate
182
+ ? new Date(agg.newestDate).getTime()
183
+ : 0;
184
+
185
+ if (msgEpoch > 0 && msgEpoch < oldestEpoch) {
186
+ agg.oldestDate = dateStr || agg.oldestDate;
187
+ }
188
+ if (msgEpoch > newestEpoch) {
189
+ agg.newestDate = dateStr || agg.newestDate;
190
+ agg.newestMessageId = msg.id;
191
+ }
192
+
193
+ if (subject && agg.sampleSubjects.length < MAX_SAMPLE_SUBJECTS) {
194
+ agg.sampleSubjects.push(subject);
195
+ }
196
+ }
197
+
198
+ // Sort by message count desc, take top N
199
+ const sorted = [...senderMap.values()]
200
+ .sort((a, b) => b.messageCount - a.messageCount)
201
+ .slice(0, maxSenders);
202
+
203
+ const senders = sorted.map((s) => ({
204
+ id: Buffer.from(s.email).toString("base64url"),
205
+ display_name: s.displayName || s.email.split("@")[0],
206
+ email: s.email,
207
+ message_count: s.messageCount,
208
+ newest_message_id: s.newestMessageId,
209
+ oldest_date: s.oldestDate,
210
+ newest_date: s.newestDate,
211
+ sample_subjects: s.sampleSubjects,
212
+ }));
213
+
214
+ // Store message IDs server-side to keep them out of LLM context
215
+ const scanId = storeScanResult(
216
+ sorted.map((s) => ({
217
+ id: Buffer.from(s.email).toString("base64url"),
218
+ messageIds: s.messageIds,
219
+ newestMessageId: s.newestMessageId,
220
+ newestUnsubscribableMessageId: null,
221
+ })),
222
+ );
223
+
224
+ return ok(
225
+ JSON.stringify({
226
+ scan_id: scanId,
227
+ senders,
228
+ total_scanned: allMessageIds.length,
229
+ ...(truncated ? { truncated: true } : {}),
230
+ ...(timeBudgetExceeded ? { time_budget_exceeded: true } : {}),
231
+ note: "Scanned inbox for senders without List-Unsubscribe headers (potential cold outreach). Use outlook_archive and outlook_mail_rules for cleanup.",
232
+ }),
233
+ );
234
+ } catch (e) {
235
+ return err(e instanceof Error ? e.message : String(e));
236
+ }
237
+ }
@@ -0,0 +1,161 @@
1
+ import {
2
+ createMailRule,
3
+ deleteMailRule,
4
+ listMailRules,
5
+ } from "../../../../messaging/providers/outlook/client.js";
6
+ import type {
7
+ OutlookMessageRuleActions,
8
+ OutlookMessageRulePredicates,
9
+ } from "../../../../messaging/providers/outlook/types.js";
10
+ import { resolveOAuthConnection } from "../../../../oauth/connection-resolver.js";
11
+ import type {
12
+ ToolContext,
13
+ ToolExecutionResult,
14
+ } from "../../../../tools/types.js";
15
+ import { err, ok } from "./shared.js";
16
+
17
+ export async function run(
18
+ input: Record<string, unknown>,
19
+ _context: ToolContext,
20
+ ): Promise<ToolExecutionResult> {
21
+ const account = input.account as string | undefined;
22
+ const action = input.action as string;
23
+
24
+ if (!action) {
25
+ return err("action is required (list, create, or delete).");
26
+ }
27
+
28
+ try {
29
+ const connection = await resolveOAuthConnection("outlook", {
30
+ account,
31
+ });
32
+ switch (action) {
33
+ case "list": {
34
+ const resp = await listMailRules(connection);
35
+ const rules = resp.value ?? [];
36
+ if (rules.length === 0) {
37
+ return ok("No inbox rules configured.");
38
+ }
39
+ const summary = rules.map((r) => ({
40
+ id: r.id,
41
+ displayName: r.displayName,
42
+ isEnabled: r.isEnabled,
43
+ conditions: summarizeConditions(r.conditions),
44
+ actions: summarizeActions(r.actions),
45
+ }));
46
+ return ok(JSON.stringify(summary, null, 2));
47
+ }
48
+
49
+ case "create": {
50
+ const displayName = input.display_name as string;
51
+ if (!displayName)
52
+ return err("display_name is required for create action.");
53
+
54
+ const conditions: OutlookMessageRulePredicates = {};
55
+ if (input.from_contains)
56
+ conditions.senderContains = Array.isArray(input.from_contains)
57
+ ? (input.from_contains as string[])
58
+ : [input.from_contains as string];
59
+ if (input.subject_contains)
60
+ conditions.subjectContains = Array.isArray(input.subject_contains)
61
+ ? (input.subject_contains as string[])
62
+ : [input.subject_contains as string];
63
+ if (input.body_contains)
64
+ conditions.bodyContains = Array.isArray(input.body_contains)
65
+ ? (input.body_contains as string[])
66
+ : [input.body_contains as string];
67
+ if (input.has_attachment !== undefined)
68
+ conditions.hasAttachments = input.has_attachment as boolean;
69
+ if (input.importance)
70
+ conditions.importance = input.importance as "low" | "normal" | "high";
71
+
72
+ if (Object.keys(conditions).length === 0) {
73
+ return err(
74
+ "At least one condition is required (from_contains, subject_contains, body_contains, has_attachment, or importance).",
75
+ );
76
+ }
77
+
78
+ const actions: OutlookMessageRuleActions = {};
79
+ if (input.move_to_folder)
80
+ actions.moveToFolder = input.move_to_folder as string;
81
+ if (input.delete !== undefined)
82
+ actions.delete = input.delete as boolean;
83
+ if (input.mark_as_read !== undefined)
84
+ actions.markAsRead = input.mark_as_read as boolean;
85
+ if (input.mark_importance)
86
+ actions.markImportance = input.mark_importance as
87
+ | "low"
88
+ | "normal"
89
+ | "high";
90
+ if (input.forward_to) {
91
+ const emails = Array.isArray(input.forward_to)
92
+ ? (input.forward_to as string[])
93
+ : [input.forward_to as string];
94
+ actions.forwardTo = emails.map((addr) => ({
95
+ emailAddress: { address: addr },
96
+ }));
97
+ }
98
+ if (input.stop_processing !== undefined)
99
+ actions.stopProcessingRules = input.stop_processing as boolean;
100
+
101
+ const rule = await createMailRule(connection, {
102
+ displayName,
103
+ sequence: (input.sequence as number) ?? 1,
104
+ isEnabled: true,
105
+ conditions,
106
+ actions,
107
+ });
108
+ return ok(`Rule created (ID: ${rule.id}).`);
109
+ }
110
+
111
+ case "delete": {
112
+ const ruleId = input.rule_id as string;
113
+ if (!ruleId) return err("rule_id is required for delete action.");
114
+
115
+ await deleteMailRule(connection, ruleId);
116
+ return ok("Rule deleted.");
117
+ }
118
+
119
+ default:
120
+ return err(`Unknown action "${action}". Use list, create, or delete.`);
121
+ }
122
+ } catch (e) {
123
+ return err(e instanceof Error ? e.message : String(e));
124
+ }
125
+ }
126
+
127
+ function summarizeConditions(
128
+ c: OutlookMessageRulePredicates | undefined,
129
+ ): string {
130
+ if (!c) return "none";
131
+ const parts: string[] = [];
132
+ if (c.senderContains?.length)
133
+ parts.push(`sender contains: ${c.senderContains.join(", ")}`);
134
+ if (c.subjectContains?.length)
135
+ parts.push(`subject contains: ${c.subjectContains.join(", ")}`);
136
+ if (c.bodyContains?.length)
137
+ parts.push(`body contains: ${c.bodyContains.join(", ")}`);
138
+ if (c.fromAddresses?.length)
139
+ parts.push(
140
+ `from: ${c.fromAddresses.map((a) => a.emailAddress.address).join(", ")}`,
141
+ );
142
+ if (c.hasAttachments !== undefined)
143
+ parts.push(`has attachments: ${c.hasAttachments}`);
144
+ if (c.importance) parts.push(`importance: ${c.importance}`);
145
+ return parts.length > 0 ? parts.join("; ") : "none";
146
+ }
147
+
148
+ function summarizeActions(c: OutlookMessageRuleActions | undefined): string {
149
+ if (!c) return "none";
150
+ const parts: string[] = [];
151
+ if (c.moveToFolder) parts.push(`move to folder: ${c.moveToFolder}`);
152
+ if (c.delete) parts.push("delete");
153
+ if (c.markAsRead) parts.push("mark as read");
154
+ if (c.markImportance) parts.push(`mark importance: ${c.markImportance}`);
155
+ if (c.forwardTo?.length)
156
+ parts.push(
157
+ `forward to: ${c.forwardTo.map((r) => r.emailAddress.address).join(", ")}`,
158
+ );
159
+ if (c.stopProcessingRules) parts.push("stop processing rules");
160
+ return parts.length > 0 ? parts.join("; ") : "none";
161
+ }
@@ -0,0 +1,32 @@
1
+ import { sendDraft } from "../../../../messaging/providers/outlook/client.js";
2
+ import { resolveOAuthConnection } from "../../../../oauth/connection-resolver.js";
3
+ import type {
4
+ ToolContext,
5
+ ToolExecutionResult,
6
+ } from "../../../../tools/types.js";
7
+ import { err, ok } from "./shared.js";
8
+
9
+ export async function run(
10
+ input: Record<string, unknown>,
11
+ context: ToolContext,
12
+ ): Promise<ToolExecutionResult> {
13
+ const account = input.account as string | undefined;
14
+ const draftId = input.draft_id as string;
15
+ if (!draftId) return err("draft_id is required.");
16
+
17
+ if (!context.triggeredBySurfaceAction) {
18
+ return err(
19
+ "This tool requires user confirmation via a surface action. Present the draft details with a send button and wait for the user to click before proceeding.",
20
+ );
21
+ }
22
+
23
+ try {
24
+ const connection = await resolveOAuthConnection("outlook", {
25
+ account,
26
+ });
27
+ await sendDraft(connection, draftId);
28
+ return ok("Draft sent.");
29
+ } catch (e) {
30
+ return err(e instanceof Error ? e.message : String(e));
31
+ }
32
+ }