@vellumai/assistant 0.5.16 → 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 (407) hide show
  1. package/ARCHITECTURE.md +1 -1
  2. package/Dockerfile +0 -3
  3. package/knip.json +2 -1
  4. package/openapi.yaml +660 -80
  5. package/package.json +1 -1
  6. package/src/__tests__/actor-token-service.test.ts +68 -0
  7. package/src/__tests__/agent-loop.test.ts +0 -32
  8. package/src/__tests__/always-loaded-tools-guard.test.ts +2 -2
  9. package/src/__tests__/anthropic-provider.test.ts +57 -3
  10. package/src/__tests__/app-compiler.test.ts +120 -0
  11. package/src/__tests__/assistant-feature-flags-integration.test.ts +2 -2
  12. package/src/__tests__/call-conversation-messages.test.ts +2 -6
  13. package/src/__tests__/call-domain.test.ts +2 -6
  14. package/src/__tests__/call-pointer-messages.test.ts +2 -14
  15. package/src/__tests__/call-recovery.test.ts +2 -6
  16. package/src/__tests__/call-routes-http.test.ts +2 -6
  17. package/src/__tests__/call-store.test.ts +2 -6
  18. package/src/__tests__/cancel-resolves-conversation-key.test.ts +2 -6
  19. package/src/__tests__/canonical-guardian-store.test.ts +2 -6
  20. package/src/__tests__/channel-delivery-store.test.ts +2 -6
  21. package/src/__tests__/channel-retry-sweep.test.ts +2 -6
  22. package/src/__tests__/checker.test.ts +25 -3
  23. package/src/__tests__/clawhub.test.ts +54 -24
  24. package/src/__tests__/cli-command-risk-guard.test.ts +14 -0
  25. package/src/__tests__/cli-memory.test.ts +74 -69
  26. package/src/__tests__/config-schema.test.ts +1 -1
  27. package/src/__tests__/config-set-platform-guard.test.ts +302 -0
  28. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +2 -6
  29. package/src/__tests__/contacts-tools.test.ts +31 -0
  30. package/src/__tests__/context-overflow-reducer.test.ts +86 -0
  31. package/src/__tests__/context-token-estimator.test.ts +175 -10
  32. package/src/__tests__/conversation-agent-loop-overflow.test.ts +9 -0
  33. package/src/__tests__/conversation-agent-loop.test.ts +9 -0
  34. package/src/__tests__/conversation-attachments.test.ts +2 -6
  35. package/src/__tests__/conversation-attention-store.test.ts +2 -6
  36. package/src/__tests__/conversation-clear-safety.test.ts +2 -6
  37. package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +4 -10
  38. package/src/__tests__/conversation-disk-view-integration.test.ts +2 -6
  39. package/src/__tests__/conversation-disk-view.test.ts +2 -6
  40. package/src/__tests__/conversation-error.test.ts +33 -2
  41. package/src/__tests__/conversation-fork-crud.test.ts +2 -6
  42. package/src/__tests__/conversation-history-web-search.test.ts +5 -0
  43. package/src/__tests__/conversation-load-history-repair.test.ts +5 -1
  44. package/src/__tests__/conversation-media-retry.test.ts +91 -0
  45. package/src/__tests__/conversation-starter-routes.test.ts +20 -11
  46. package/src/__tests__/conversation-store.test.ts +2 -6
  47. package/src/__tests__/conversation-usage.test.ts +2 -6
  48. package/src/__tests__/conversation-wipe.test.ts +11 -408
  49. package/src/__tests__/credential-execution-feature-gates.test.ts +3 -3
  50. package/src/__tests__/credential-execution-shell-lockdown.test.ts +2 -2
  51. package/src/__tests__/credential-security-e2e.test.ts +2 -0
  52. package/src/__tests__/followup-tools.test.ts +2 -6
  53. package/src/__tests__/graph-extraction-event-date.test.ts +186 -0
  54. package/src/__tests__/guardian-action-conversation-turn.test.ts +2 -6
  55. package/src/__tests__/guardian-action-followup-executor.test.ts +2 -6
  56. package/src/__tests__/guardian-action-followup-store.test.ts +2 -6
  57. package/src/__tests__/guardian-action-grant-mint-consume.test.ts +2 -6
  58. package/src/__tests__/guardian-action-late-reply.test.ts +2 -6
  59. package/src/__tests__/guardian-action-store.test.ts +2 -6
  60. package/src/__tests__/guardian-binding-drift-heal.test.ts +2 -6
  61. package/src/__tests__/guardian-decision-primitive-canonical.test.ts +8 -8
  62. package/src/__tests__/guardian-dispatch.test.ts +2 -6
  63. package/src/__tests__/guardian-grant-minting.test.ts +2 -14
  64. package/src/__tests__/guardian-principal-id-roundtrip.test.ts +2 -6
  65. package/src/__tests__/guardian-routing-invariants.test.ts +192 -6
  66. package/src/__tests__/guardian-routing-state.test.ts +2 -6
  67. package/src/__tests__/guardian-verification-voice-binding.test.ts +2 -6
  68. package/src/__tests__/inbound-invite-redemption.test.ts +2 -6
  69. package/src/__tests__/injection-block.test.ts +154 -0
  70. package/src/__tests__/install-meta.test.ts +506 -0
  71. package/src/__tests__/install-skill-routing.test.ts +292 -0
  72. package/src/__tests__/invite-redemption-service.test.ts +2 -6
  73. package/src/__tests__/invite-routes-http.test.ts +2 -6
  74. package/src/__tests__/jobs-store-qdrant-breaker.test.ts +2 -14
  75. package/src/__tests__/list-messages-attachments.test.ts +2 -6
  76. package/src/__tests__/llm-context-route-provider.test.ts +2 -6
  77. package/src/__tests__/llm-request-log-turn-query.test.ts +2 -6
  78. package/src/__tests__/llm-usage-store.test.ts +2 -6
  79. package/src/__tests__/log-export-workspace.test.ts +2 -6
  80. package/src/__tests__/managed-store.test.ts +38 -11
  81. package/src/__tests__/memory-jobs-worker-backoff.test.ts +2 -8
  82. package/src/__tests__/memory-recall-log-store.test.ts +2 -6
  83. package/src/__tests__/memory-upsert-concurrency.test.ts +4 -112
  84. package/src/__tests__/non-member-access-request.test.ts +2 -6
  85. package/src/__tests__/notification-guardian-path.test.ts +2 -6
  86. package/src/__tests__/oauth-cli.test.ts +364 -2
  87. package/src/__tests__/oauth2-gateway-transport.test.ts +18 -3
  88. package/src/__tests__/outlook-attachments.test.ts +301 -0
  89. package/src/__tests__/outlook-automation-tools.test.ts +425 -0
  90. package/src/__tests__/outlook-categories.test.ts +212 -0
  91. package/src/__tests__/outlook-client-automation.test.ts +246 -0
  92. package/src/__tests__/outlook-compose-tools.test.ts +325 -0
  93. package/src/__tests__/outlook-declutter-tools.test.ts +585 -0
  94. package/src/__tests__/outlook-email-watcher.test.ts +322 -0
  95. package/src/__tests__/outlook-follow-up.test.ts +196 -0
  96. package/src/__tests__/outlook-messaging-provider.test.ts +498 -3
  97. package/src/__tests__/outlook-trash.test.ts +77 -0
  98. package/src/__tests__/outlook-unsubscribe.test.ts +250 -0
  99. package/src/__tests__/platform-callback-registration.test.ts +4 -4
  100. package/src/__tests__/playbook-execution.test.ts +76 -80
  101. package/src/__tests__/playbook-tools.test.ts +5 -7
  102. package/src/__tests__/provider-error-scenarios.test.ts +21 -0
  103. package/src/__tests__/rebuild-index-graph-nodes.test.ts +273 -0
  104. package/src/__tests__/registry.test.ts +2 -2
  105. package/src/__tests__/require-fresh-approval.test.ts +64 -2
  106. package/src/__tests__/runtime-events-sse-parity.test.ts +2 -6
  107. package/src/__tests__/runtime-events-sse.test.ts +2 -6
  108. package/src/__tests__/schedule-store.test.ts +2 -6
  109. package/src/__tests__/schedule-tools.test.ts +2 -6
  110. package/src/__tests__/scheduler-recurrence.test.ts +1 -5
  111. package/src/__tests__/scoped-approval-grants.test.ts +2 -6
  112. package/src/__tests__/scoped-grant-security-matrix.test.ts +2 -6
  113. package/src/__tests__/search-skills-unified.test.ts +421 -0
  114. package/src/__tests__/secret-onetime-send.test.ts +2 -0
  115. package/src/__tests__/send-endpoint-busy.test.ts +2 -6
  116. package/src/__tests__/sequence-store.test.ts +2 -6
  117. package/src/__tests__/server-history-render.test.ts +2 -6
  118. package/src/__tests__/skill-feature-flags-integration.test.ts +38 -31
  119. package/src/__tests__/skill-feature-flags.test.ts +6 -6
  120. package/src/__tests__/skill-load-feature-flag.test.ts +11 -11
  121. package/src/__tests__/skill-memory.test.ts +140 -98
  122. package/src/__tests__/skills-uninstall.test.ts +2 -2
  123. package/src/__tests__/skills.test.ts +1 -1
  124. package/src/__tests__/slack-inbound-verification.test.ts +2 -6
  125. package/src/__tests__/task-compiler.test.ts +2 -6
  126. package/src/__tests__/task-management-tools.test.ts +2 -6
  127. package/src/__tests__/task-memory-cleanup.test.ts +173 -229
  128. package/src/__tests__/task-runner.test.ts +2 -6
  129. package/src/__tests__/task-scheduler.test.ts +2 -6
  130. package/src/__tests__/test-preload.ts +3 -0
  131. package/src/__tests__/tool-approval-handler.test.ts +2 -6
  132. package/src/__tests__/tool-grant-request-escalation.test.ts +2 -6
  133. package/src/__tests__/tool-side-effects-slack-dm.test.ts +276 -0
  134. package/src/__tests__/trust-store.test.ts +1 -1
  135. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +2 -6
  136. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +2 -6
  137. package/src/__tests__/trusted-contact-multichannel.test.ts +2 -6
  138. package/src/__tests__/trusted-contact-verification.test.ts +2 -6
  139. package/src/__tests__/turn-boundary-resolution.test.ts +2 -6
  140. package/src/__tests__/usage-cache-backfill-migration.test.ts +1 -6
  141. package/src/__tests__/usage-routes.test.ts +2 -6
  142. package/src/__tests__/verification-control-plane-policy.test.ts +0 -2
  143. package/src/__tests__/voice-invite-redemption.test.ts +2 -6
  144. package/src/__tests__/voice-scoped-grant-consumer.test.ts +2 -6
  145. package/src/__tests__/voice-session-bridge.test.ts +2 -6
  146. package/src/__tests__/volume-security-guard.test.ts +2 -0
  147. package/src/__tests__/workspace-lifecycle.test.ts +29 -1
  148. package/src/__tests__/workspace-migration-009-backfill-conversation-disk-view.test.ts +2 -6
  149. package/src/__tests__/workspace-migration-013-repair-conversation-disk-view.test.ts +2 -6
  150. package/src/__tests__/workspace-migration-026-backfill-install-meta.test.ts +558 -0
  151. package/src/__tests__/workspace-policy.test.ts +1 -1
  152. package/src/agent/attachments.ts +7 -2
  153. package/src/agent/image-optimize.ts +165 -0
  154. package/src/agent/loop.ts +1 -15
  155. package/src/bundler/app-compiler.ts +179 -2
  156. package/src/bundler/package-resolver.ts +3 -5
  157. package/src/cli/__tests__/notifications.test.ts +1 -2
  158. package/src/cli/cli-memory.ts +67 -64
  159. package/src/cli/commands/avatar.ts +3 -3
  160. package/src/cli/commands/config.ts +26 -13
  161. package/src/cli/commands/doctor.ts +2 -2
  162. package/src/cli/commands/memory.ts +41 -55
  163. package/src/cli/commands/oauth/__tests__/connect.test.ts +2 -2
  164. package/src/cli/commands/oauth/__tests__/disconnect.test.ts +2 -2
  165. package/src/cli/commands/oauth/__tests__/mode.test.ts +8 -1
  166. package/src/cli/commands/oauth/__tests__/status.test.ts +2 -2
  167. package/src/cli/commands/oauth/connect.ts +11 -6
  168. package/src/cli/commands/oauth/mode.ts +7 -0
  169. package/src/cli/commands/oauth/shared.ts +39 -3
  170. package/src/cli/commands/platform/__tests__/connect.test.ts +1 -1
  171. package/src/cli/commands/platform/__tests__/disconnect.test.ts +1 -1
  172. package/src/cli/commands/platform/__tests__/status.test.ts +5 -5
  173. package/src/cli/commands/platform/index.ts +16 -16
  174. package/src/cli/commands/skills.ts +88 -16
  175. package/src/cli/commands/trust.ts +2 -2
  176. package/src/cli/lib/daemon-credential-client.ts +2 -3
  177. package/src/config/bundled-skills/acp/TOOLS.json +1 -1
  178. package/src/config/bundled-skills/contacts/SKILL.md +0 -1
  179. package/src/config/bundled-skills/contacts/TOOLS.json +0 -8
  180. package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +0 -4
  181. package/src/config/bundled-skills/gmail/SKILL.md +2 -10
  182. package/src/config/bundled-skills/google-calendar/SKILL.md +1 -9
  183. package/src/config/bundled-skills/messaging/SKILL.md +10 -18
  184. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +40 -33
  185. package/src/config/bundled-skills/outlook/SKILL.md +189 -0
  186. package/src/config/bundled-skills/outlook/TOOLS.json +530 -0
  187. package/src/config/bundled-skills/outlook/tools/outlook-attachments.ts +85 -0
  188. package/src/config/bundled-skills/outlook/tools/outlook-categories.ts +77 -0
  189. package/src/config/bundled-skills/outlook/tools/outlook-draft.ts +84 -0
  190. package/src/config/bundled-skills/outlook/tools/outlook-follow-up.ts +94 -0
  191. package/src/config/bundled-skills/outlook/tools/outlook-forward.ts +49 -0
  192. package/src/config/bundled-skills/outlook/tools/outlook-outreach-scan.ts +237 -0
  193. package/src/config/bundled-skills/outlook/tools/outlook-rules.ts +161 -0
  194. package/src/config/bundled-skills/outlook/tools/outlook-send-draft.ts +32 -0
  195. package/src/config/bundled-skills/outlook/tools/outlook-sender-digest.ts +272 -0
  196. package/src/config/bundled-skills/outlook/tools/outlook-trash.ts +29 -0
  197. package/src/config/bundled-skills/outlook/tools/outlook-unsubscribe.ts +129 -0
  198. package/src/config/bundled-skills/outlook/tools/outlook-vacation.ts +87 -0
  199. package/src/config/bundled-skills/outlook/tools/shared.ts +20 -0
  200. package/src/config/bundled-skills/outlook-calendar/SKILL.md +51 -0
  201. package/src/config/bundled-skills/outlook-calendar/TOOLS.json +221 -0
  202. package/src/config/bundled-skills/outlook-calendar/calendar-client.ts +252 -0
  203. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-check-availability.ts +53 -0
  204. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-create-event.ts +74 -0
  205. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-get-event.ts +18 -0
  206. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-list-events.ts +46 -0
  207. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-rsvp.ts +36 -0
  208. package/src/config/bundled-skills/outlook-calendar/tools/shared.ts +17 -0
  209. package/src/config/bundled-skills/outlook-calendar/types.ts +120 -0
  210. package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +47 -40
  211. package/src/config/bundled-skills/playbooks/tools/playbook-delete.ts +16 -29
  212. package/src/config/bundled-skills/playbooks/tools/playbook-list.ts +16 -18
  213. package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +39 -47
  214. package/src/config/bundled-skills/slack/SKILL.md +1 -7
  215. package/src/config/bundled-tool-registry.ts +56 -4
  216. package/src/config/env-registry.ts +15 -8
  217. package/src/config/feature-flag-registry.json +21 -124
  218. package/src/config/schemas/platform.ts +8 -0
  219. package/src/config/schemas/timeouts.ts +1 -1
  220. package/src/config/skills.ts +18 -7
  221. package/src/context/token-estimator.ts +25 -18
  222. package/src/context/window-manager.ts +6 -2
  223. package/src/credential-execution/process-manager.ts +3 -1
  224. package/src/daemon/context-overflow-reducer.ts +46 -2
  225. package/src/daemon/conversation-agent-loop-handlers.ts +123 -82
  226. package/src/daemon/conversation-agent-loop.ts +96 -61
  227. package/src/daemon/conversation-error.ts +31 -8
  228. package/src/daemon/conversation-lifecycle.ts +33 -0
  229. package/src/daemon/conversation-media-retry.ts +85 -7
  230. package/src/daemon/conversation-notifiers.ts +4 -1
  231. package/src/daemon/conversation-runtime-assembly.ts +5 -0
  232. package/src/daemon/conversation.ts +41 -2
  233. package/src/daemon/daemon-control.ts +8 -2
  234. package/src/daemon/handlers/shared.ts +22 -12
  235. package/src/daemon/handlers/skills.ts +416 -202
  236. package/src/daemon/lifecycle.ts +40 -1
  237. package/src/daemon/main.ts +5 -1
  238. package/src/daemon/message-types/conversations.ts +4 -1
  239. package/src/daemon/message-types/messages.ts +3 -1
  240. package/src/daemon/message-types/skills.ts +97 -36
  241. package/src/daemon/providers-setup.ts +5 -0
  242. package/src/daemon/server.ts +11 -2
  243. package/src/daemon/tool-side-effects.ts +27 -5
  244. package/src/heartbeat/heartbeat-service.ts +1 -0
  245. package/src/hooks/cli.ts +2 -2
  246. package/src/hooks/runner.ts +15 -38
  247. package/src/inbound/platform-callback-registration.ts +14 -14
  248. package/src/memory/admin.ts +11 -45
  249. package/src/memory/conversation-bootstrap.ts +2 -0
  250. package/src/memory/conversation-crud.ts +242 -348
  251. package/src/memory/conversation-group-migration.ts +157 -0
  252. package/src/memory/conversation-queries.ts +4 -2
  253. package/src/memory/db-init.ts +30 -3
  254. package/src/memory/embed.ts +73 -0
  255. package/src/memory/embedding-backend.ts +8 -14
  256. package/src/memory/embedding-runtime-manager.ts +12 -114
  257. package/src/memory/fingerprint.ts +2 -2
  258. package/src/memory/graph/bootstrap.ts +512 -0
  259. package/src/memory/graph/capability-seed.ts +297 -0
  260. package/src/memory/graph/consolidation.ts +691 -0
  261. package/src/memory/graph/conversation-graph-memory.ts +630 -0
  262. package/src/memory/graph/decay.test.ts +208 -0
  263. package/src/memory/graph/decay.ts +195 -0
  264. package/src/memory/graph/extraction-job.ts +69 -0
  265. package/src/memory/graph/extraction.test.ts +936 -0
  266. package/src/memory/graph/extraction.ts +1254 -0
  267. package/src/memory/graph/graph-search.ts +266 -0
  268. package/src/memory/graph/image-ref-utils.ts +29 -0
  269. package/src/memory/graph/injection.test.ts +513 -0
  270. package/src/memory/graph/injection.ts +439 -0
  271. package/src/memory/graph/inspect.ts +534 -0
  272. package/src/memory/graph/narrative.ts +267 -0
  273. package/src/memory/graph/pattern-scan.ts +269 -0
  274. package/src/memory/graph/retriever.ts +1008 -0
  275. package/src/memory/graph/scoring.test.ts +548 -0
  276. package/src/memory/graph/scoring.ts +232 -0
  277. package/src/memory/graph/serendipity.ts +65 -0
  278. package/src/memory/graph/store.test.ts +1050 -0
  279. package/src/memory/graph/store.ts +699 -0
  280. package/src/memory/graph/tool-handlers.ts +426 -0
  281. package/src/memory/graph/tools.ts +141 -0
  282. package/src/memory/graph/triggers.test.ts +487 -0
  283. package/src/memory/graph/triggers.ts +223 -0
  284. package/src/memory/graph/types.ts +271 -0
  285. package/src/memory/group-crud.ts +191 -0
  286. package/src/memory/indexer.ts +37 -19
  287. package/src/memory/job-handlers/cleanup.ts +0 -53
  288. package/src/memory/job-handlers/conversation-starters.ts +91 -53
  289. package/src/memory/job-handlers/embedding.ts +5 -31
  290. package/src/memory/job-handlers/index-maintenance.ts +23 -11
  291. package/src/memory/job-handlers/summarization.ts +32 -17
  292. package/src/memory/job-utils.ts +1 -1
  293. package/src/memory/jobs-store.ts +50 -70
  294. package/src/memory/jobs-worker.ts +147 -112
  295. package/src/memory/message-content.ts +1 -0
  296. package/src/memory/migrations/202-memory-graph-tables.ts +130 -0
  297. package/src/memory/migrations/203-drop-memory-items-tables.ts +23 -0
  298. package/src/memory/migrations/204-rename-memory-graph-type-values.ts +46 -0
  299. package/src/memory/migrations/205-memory-graph-image-refs.ts +11 -0
  300. package/src/memory/migrations/index.ts +4 -0
  301. package/src/memory/migrations/registry.ts +8 -0
  302. package/src/memory/qdrant-client.ts +44 -17
  303. package/src/memory/schema/index.ts +1 -0
  304. package/src/memory/schema/memory-graph.ts +139 -0
  305. package/src/memory/search/semantic.ts +47 -91
  306. package/src/memory/task-memory-cleanup.ts +28 -50
  307. package/src/messaging/providers/outlook/adapter.ts +8 -1
  308. package/src/messaging/providers/outlook/client.ts +299 -0
  309. package/src/messaging/providers/outlook/types.ts +118 -0
  310. package/src/notifications/adapters/macos.ts +1 -0
  311. package/src/notifications/copy-composer.ts +9 -0
  312. package/src/notifications/signal.ts +16 -0
  313. package/src/oauth/seed-providers.ts +2 -1
  314. package/src/permissions/checker.ts +24 -3
  315. package/src/permissions/defaults.ts +4 -4
  316. package/src/permissions/workspace-policy.ts +1 -1
  317. package/src/playbooks/playbook-compiler.ts +19 -18
  318. package/src/playbooks/types.ts +4 -3
  319. package/src/prompts/system-prompt.ts +3 -29
  320. package/src/providers/anthropic/client.ts +47 -19
  321. package/src/providers/gemini/client.ts +1 -1
  322. package/src/providers/openai/client.ts +1 -1
  323. package/src/providers/registry.ts +1 -1
  324. package/src/providers/retry.ts +19 -3
  325. package/src/runtime/actor-trust-resolver.ts +5 -1
  326. package/src/runtime/auth/route-policy.ts +7 -0
  327. package/src/runtime/guardian-reply-router.ts +5 -1
  328. package/src/runtime/http-server.ts +23 -3
  329. package/src/runtime/middleware/auth.ts +20 -0
  330. package/src/runtime/routes/attachment-routes.test.ts +106 -0
  331. package/src/runtime/routes/attachment-routes.ts +106 -16
  332. package/src/runtime/routes/brain-graph-routes.ts +21 -22
  333. package/src/runtime/routes/btw-routes.ts +8 -0
  334. package/src/runtime/routes/conversation-management-routes.ts +2 -0
  335. package/src/runtime/routes/conversation-starter-routes.ts +2 -2
  336. package/src/runtime/routes/debug-routes.ts +1 -1
  337. package/src/runtime/routes/global-search-routes.ts +21 -19
  338. package/src/runtime/routes/group-routes.ts +207 -0
  339. package/src/runtime/routes/guardian-action-routes.ts +21 -10
  340. package/src/runtime/routes/guardian-bootstrap-routes.ts +23 -19
  341. package/src/runtime/routes/inbound-message-handler.ts +19 -0
  342. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.test.ts +292 -0
  343. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +207 -0
  344. package/src/runtime/routes/memory-item-routes.test.ts +2 -14
  345. package/src/runtime/routes/memory-item-routes.ts +341 -388
  346. package/src/runtime/routes/schedule-routes.ts +2 -0
  347. package/src/runtime/routes/skills-routes.ts +103 -37
  348. package/src/runtime/routes/work-items-routes.test.ts +2 -6
  349. package/src/schedule/scheduler.ts +8 -1
  350. package/src/security/oauth2.ts +1 -1
  351. package/src/security/secure-keys.ts +4 -8
  352. package/src/shared/provider-env-vars.ts +19 -0
  353. package/src/skills/catalog-cache.ts +5 -0
  354. package/src/skills/catalog-install.ts +15 -14
  355. package/src/skills/clawhub.ts +134 -154
  356. package/src/skills/install-meta.ts +208 -0
  357. package/src/skills/managed-store.ts +27 -16
  358. package/src/skills/skill-memory.ts +152 -77
  359. package/src/skills/skillssh-registry.ts +19 -17
  360. package/src/tasks/task-runner.ts +3 -1
  361. package/src/telemetry/usage-telemetry-reporter.test.ts +3 -5
  362. package/src/tools/browser/runtime-check.ts +3 -1
  363. package/src/tools/memory/register.ts +63 -46
  364. package/src/tools/permission-checker.ts +7 -1
  365. package/src/tools/shared/filesystem/image-read.ts +22 -85
  366. package/src/tools/terminal/safe-env.ts +1 -0
  367. package/src/tools/tool-manifest.ts +3 -3
  368. package/src/util/browser.ts +25 -10
  369. package/src/util/bun-runtime.ts +172 -0
  370. package/src/watcher/providers/outlook-calendar.ts +343 -0
  371. package/src/watcher/providers/outlook.ts +198 -0
  372. package/src/workspace/migrations/025-remove-oauth-app-setup-skills.ts +76 -0
  373. package/src/workspace/migrations/026-backfill-install-meta.ts +325 -0
  374. package/src/workspace/migrations/027-remove-orphaned-optimized-images-cache.ts +42 -0
  375. package/src/workspace/migrations/registry.ts +6 -0
  376. package/src/__tests__/context-memory-e2e.test.ts +0 -415
  377. package/src/__tests__/journal-context.test.ts +0 -268
  378. package/src/__tests__/memory-context-benchmark.benchmark.test.ts +0 -297
  379. package/src/__tests__/memory-lifecycle-e2e.test.ts +0 -459
  380. package/src/__tests__/memory-query-builder.test.ts +0 -59
  381. package/src/__tests__/memory-recall-quality.test.ts +0 -1046
  382. package/src/__tests__/memory-regressions.experimental.test.ts +0 -629
  383. package/src/__tests__/memory-regressions.test.ts +0 -3696
  384. package/src/__tests__/memory-retrieval.benchmark.test.ts +0 -295
  385. package/src/daemon/conversation-memory.ts +0 -207
  386. package/src/memory/conversation-starters-cadence.ts +0 -74
  387. package/src/memory/items-extractor.ts +0 -860
  388. package/src/memory/job-handlers/batch-extraction.ts +0 -753
  389. package/src/memory/job-handlers/extraction.ts +0 -40
  390. package/src/memory/job-handlers/journal-carry-forward.test.ts +0 -355
  391. package/src/memory/job-handlers/journal-carry-forward.ts +0 -255
  392. package/src/memory/journal-memory.ts +0 -224
  393. package/src/memory/query-builder.ts +0 -47
  394. package/src/memory/query-expansion.ts +0 -83
  395. package/src/memory/retriever.test.ts +0 -1592
  396. package/src/memory/retriever.ts +0 -1331
  397. package/src/memory/search/formatting.test.ts +0 -140
  398. package/src/memory/search/formatting.ts +0 -262
  399. package/src/memory/search/mmr.ts +0 -139
  400. package/src/memory/search/ranking.ts +0 -15
  401. package/src/memory/search/staleness.ts +0 -40
  402. package/src/memory/search/tier-classifier.ts +0 -18
  403. package/src/memory/search/types.ts +0 -121
  404. package/src/prompts/journal-context.ts +0 -154
  405. package/src/tools/memory/definitions.ts +0 -69
  406. package/src/tools/memory/handlers.test.ts +0 -562
  407. 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
+ }