@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.
- package/ARCHITECTURE.md +1 -1
- package/Dockerfile +0 -3
- package/knip.json +2 -1
- package/openapi.yaml +660 -80
- package/package.json +1 -1
- package/src/__tests__/actor-token-service.test.ts +68 -0
- package/src/__tests__/agent-loop.test.ts +0 -32
- package/src/__tests__/always-loaded-tools-guard.test.ts +2 -2
- package/src/__tests__/anthropic-provider.test.ts +57 -3
- package/src/__tests__/app-compiler.test.ts +120 -0
- package/src/__tests__/assistant-feature-flags-integration.test.ts +2 -2
- package/src/__tests__/call-conversation-messages.test.ts +2 -6
- package/src/__tests__/call-domain.test.ts +2 -6
- package/src/__tests__/call-pointer-messages.test.ts +2 -14
- package/src/__tests__/call-recovery.test.ts +2 -6
- package/src/__tests__/call-routes-http.test.ts +2 -6
- package/src/__tests__/call-store.test.ts +2 -6
- package/src/__tests__/cancel-resolves-conversation-key.test.ts +2 -6
- package/src/__tests__/canonical-guardian-store.test.ts +2 -6
- package/src/__tests__/channel-delivery-store.test.ts +2 -6
- package/src/__tests__/channel-retry-sweep.test.ts +2 -6
- package/src/__tests__/checker.test.ts +25 -3
- package/src/__tests__/clawhub.test.ts +54 -24
- package/src/__tests__/cli-command-risk-guard.test.ts +14 -0
- package/src/__tests__/cli-memory.test.ts +74 -69
- package/src/__tests__/config-schema.test.ts +1 -1
- package/src/__tests__/config-set-platform-guard.test.ts +302 -0
- package/src/__tests__/confirmation-request-guardian-bridge.test.ts +2 -6
- package/src/__tests__/contacts-tools.test.ts +31 -0
- package/src/__tests__/context-overflow-reducer.test.ts +86 -0
- package/src/__tests__/context-token-estimator.test.ts +175 -10
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +9 -0
- package/src/__tests__/conversation-agent-loop.test.ts +9 -0
- package/src/__tests__/conversation-attachments.test.ts +2 -6
- package/src/__tests__/conversation-attention-store.test.ts +2 -6
- package/src/__tests__/conversation-clear-safety.test.ts +2 -6
- package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +4 -10
- package/src/__tests__/conversation-disk-view-integration.test.ts +2 -6
- package/src/__tests__/conversation-disk-view.test.ts +2 -6
- package/src/__tests__/conversation-error.test.ts +33 -2
- package/src/__tests__/conversation-fork-crud.test.ts +2 -6
- package/src/__tests__/conversation-history-web-search.test.ts +5 -0
- package/src/__tests__/conversation-load-history-repair.test.ts +5 -1
- package/src/__tests__/conversation-media-retry.test.ts +91 -0
- package/src/__tests__/conversation-starter-routes.test.ts +20 -11
- package/src/__tests__/conversation-store.test.ts +2 -6
- package/src/__tests__/conversation-usage.test.ts +2 -6
- package/src/__tests__/conversation-wipe.test.ts +11 -408
- package/src/__tests__/credential-execution-feature-gates.test.ts +3 -3
- package/src/__tests__/credential-execution-shell-lockdown.test.ts +2 -2
- package/src/__tests__/credential-security-e2e.test.ts +2 -0
- package/src/__tests__/followup-tools.test.ts +2 -6
- package/src/__tests__/graph-extraction-event-date.test.ts +186 -0
- package/src/__tests__/guardian-action-conversation-turn.test.ts +2 -6
- package/src/__tests__/guardian-action-followup-executor.test.ts +2 -6
- package/src/__tests__/guardian-action-followup-store.test.ts +2 -6
- package/src/__tests__/guardian-action-grant-mint-consume.test.ts +2 -6
- package/src/__tests__/guardian-action-late-reply.test.ts +2 -6
- package/src/__tests__/guardian-action-store.test.ts +2 -6
- package/src/__tests__/guardian-binding-drift-heal.test.ts +2 -6
- package/src/__tests__/guardian-decision-primitive-canonical.test.ts +8 -8
- package/src/__tests__/guardian-dispatch.test.ts +2 -6
- package/src/__tests__/guardian-grant-minting.test.ts +2 -14
- package/src/__tests__/guardian-principal-id-roundtrip.test.ts +2 -6
- package/src/__tests__/guardian-routing-invariants.test.ts +192 -6
- package/src/__tests__/guardian-routing-state.test.ts +2 -6
- package/src/__tests__/guardian-verification-voice-binding.test.ts +2 -6
- package/src/__tests__/inbound-invite-redemption.test.ts +2 -6
- package/src/__tests__/injection-block.test.ts +154 -0
- package/src/__tests__/install-meta.test.ts +506 -0
- package/src/__tests__/install-skill-routing.test.ts +292 -0
- package/src/__tests__/invite-redemption-service.test.ts +2 -6
- package/src/__tests__/invite-routes-http.test.ts +2 -6
- package/src/__tests__/jobs-store-qdrant-breaker.test.ts +2 -14
- package/src/__tests__/list-messages-attachments.test.ts +2 -6
- package/src/__tests__/llm-context-route-provider.test.ts +2 -6
- package/src/__tests__/llm-request-log-turn-query.test.ts +2 -6
- package/src/__tests__/llm-usage-store.test.ts +2 -6
- package/src/__tests__/log-export-workspace.test.ts +2 -6
- package/src/__tests__/managed-store.test.ts +38 -11
- package/src/__tests__/memory-jobs-worker-backoff.test.ts +2 -8
- package/src/__tests__/memory-recall-log-store.test.ts +2 -6
- package/src/__tests__/memory-upsert-concurrency.test.ts +4 -112
- package/src/__tests__/non-member-access-request.test.ts +2 -6
- package/src/__tests__/notification-guardian-path.test.ts +2 -6
- package/src/__tests__/oauth-cli.test.ts +364 -2
- package/src/__tests__/oauth2-gateway-transport.test.ts +18 -3
- package/src/__tests__/outlook-attachments.test.ts +301 -0
- package/src/__tests__/outlook-automation-tools.test.ts +425 -0
- package/src/__tests__/outlook-categories.test.ts +212 -0
- package/src/__tests__/outlook-client-automation.test.ts +246 -0
- package/src/__tests__/outlook-compose-tools.test.ts +325 -0
- package/src/__tests__/outlook-declutter-tools.test.ts +585 -0
- package/src/__tests__/outlook-email-watcher.test.ts +322 -0
- package/src/__tests__/outlook-follow-up.test.ts +196 -0
- package/src/__tests__/outlook-messaging-provider.test.ts +498 -3
- package/src/__tests__/outlook-trash.test.ts +77 -0
- package/src/__tests__/outlook-unsubscribe.test.ts +250 -0
- package/src/__tests__/platform-callback-registration.test.ts +4 -4
- package/src/__tests__/playbook-execution.test.ts +76 -80
- package/src/__tests__/playbook-tools.test.ts +5 -7
- package/src/__tests__/provider-error-scenarios.test.ts +21 -0
- package/src/__tests__/rebuild-index-graph-nodes.test.ts +273 -0
- package/src/__tests__/registry.test.ts +2 -2
- package/src/__tests__/require-fresh-approval.test.ts +64 -2
- package/src/__tests__/runtime-events-sse-parity.test.ts +2 -6
- package/src/__tests__/runtime-events-sse.test.ts +2 -6
- package/src/__tests__/schedule-store.test.ts +2 -6
- package/src/__tests__/schedule-tools.test.ts +2 -6
- package/src/__tests__/scheduler-recurrence.test.ts +1 -5
- package/src/__tests__/scoped-approval-grants.test.ts +2 -6
- package/src/__tests__/scoped-grant-security-matrix.test.ts +2 -6
- package/src/__tests__/search-skills-unified.test.ts +421 -0
- package/src/__tests__/secret-onetime-send.test.ts +2 -0
- package/src/__tests__/send-endpoint-busy.test.ts +2 -6
- package/src/__tests__/sequence-store.test.ts +2 -6
- package/src/__tests__/server-history-render.test.ts +2 -6
- package/src/__tests__/skill-feature-flags-integration.test.ts +38 -31
- package/src/__tests__/skill-feature-flags.test.ts +6 -6
- package/src/__tests__/skill-load-feature-flag.test.ts +11 -11
- package/src/__tests__/skill-memory.test.ts +140 -98
- package/src/__tests__/skills-uninstall.test.ts +2 -2
- package/src/__tests__/skills.test.ts +1 -1
- package/src/__tests__/slack-inbound-verification.test.ts +2 -6
- package/src/__tests__/task-compiler.test.ts +2 -6
- package/src/__tests__/task-management-tools.test.ts +2 -6
- package/src/__tests__/task-memory-cleanup.test.ts +173 -229
- package/src/__tests__/task-runner.test.ts +2 -6
- package/src/__tests__/task-scheduler.test.ts +2 -6
- package/src/__tests__/test-preload.ts +3 -0
- package/src/__tests__/tool-approval-handler.test.ts +2 -6
- package/src/__tests__/tool-grant-request-escalation.test.ts +2 -6
- package/src/__tests__/tool-side-effects-slack-dm.test.ts +276 -0
- package/src/__tests__/trust-store.test.ts +1 -1
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +2 -6
- package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +2 -6
- package/src/__tests__/trusted-contact-multichannel.test.ts +2 -6
- package/src/__tests__/trusted-contact-verification.test.ts +2 -6
- package/src/__tests__/turn-boundary-resolution.test.ts +2 -6
- package/src/__tests__/usage-cache-backfill-migration.test.ts +1 -6
- package/src/__tests__/usage-routes.test.ts +2 -6
- package/src/__tests__/verification-control-plane-policy.test.ts +0 -2
- package/src/__tests__/voice-invite-redemption.test.ts +2 -6
- package/src/__tests__/voice-scoped-grant-consumer.test.ts +2 -6
- package/src/__tests__/voice-session-bridge.test.ts +2 -6
- package/src/__tests__/volume-security-guard.test.ts +2 -0
- package/src/__tests__/workspace-lifecycle.test.ts +29 -1
- package/src/__tests__/workspace-migration-009-backfill-conversation-disk-view.test.ts +2 -6
- package/src/__tests__/workspace-migration-013-repair-conversation-disk-view.test.ts +2 -6
- package/src/__tests__/workspace-migration-026-backfill-install-meta.test.ts +558 -0
- package/src/__tests__/workspace-policy.test.ts +1 -1
- package/src/agent/attachments.ts +7 -2
- package/src/agent/image-optimize.ts +165 -0
- package/src/agent/loop.ts +1 -15
- package/src/bundler/app-compiler.ts +179 -2
- package/src/bundler/package-resolver.ts +3 -5
- package/src/cli/__tests__/notifications.test.ts +1 -2
- package/src/cli/cli-memory.ts +67 -64
- package/src/cli/commands/avatar.ts +3 -3
- package/src/cli/commands/config.ts +26 -13
- package/src/cli/commands/doctor.ts +2 -2
- package/src/cli/commands/memory.ts +41 -55
- package/src/cli/commands/oauth/__tests__/connect.test.ts +2 -2
- package/src/cli/commands/oauth/__tests__/disconnect.test.ts +2 -2
- package/src/cli/commands/oauth/__tests__/mode.test.ts +8 -1
- package/src/cli/commands/oauth/__tests__/status.test.ts +2 -2
- package/src/cli/commands/oauth/connect.ts +11 -6
- package/src/cli/commands/oauth/mode.ts +7 -0
- package/src/cli/commands/oauth/shared.ts +39 -3
- package/src/cli/commands/platform/__tests__/connect.test.ts +1 -1
- package/src/cli/commands/platform/__tests__/disconnect.test.ts +1 -1
- package/src/cli/commands/platform/__tests__/status.test.ts +5 -5
- package/src/cli/commands/platform/index.ts +16 -16
- package/src/cli/commands/skills.ts +88 -16
- package/src/cli/commands/trust.ts +2 -2
- package/src/cli/lib/daemon-credential-client.ts +2 -3
- package/src/config/bundled-skills/acp/TOOLS.json +1 -1
- package/src/config/bundled-skills/contacts/SKILL.md +0 -1
- package/src/config/bundled-skills/contacts/TOOLS.json +0 -8
- package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +0 -4
- package/src/config/bundled-skills/gmail/SKILL.md +2 -10
- package/src/config/bundled-skills/google-calendar/SKILL.md +1 -9
- package/src/config/bundled-skills/messaging/SKILL.md +10 -18
- package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +40 -33
- package/src/config/bundled-skills/outlook/SKILL.md +189 -0
- package/src/config/bundled-skills/outlook/TOOLS.json +530 -0
- package/src/config/bundled-skills/outlook/tools/outlook-attachments.ts +85 -0
- package/src/config/bundled-skills/outlook/tools/outlook-categories.ts +77 -0
- package/src/config/bundled-skills/outlook/tools/outlook-draft.ts +84 -0
- package/src/config/bundled-skills/outlook/tools/outlook-follow-up.ts +94 -0
- package/src/config/bundled-skills/outlook/tools/outlook-forward.ts +49 -0
- package/src/config/bundled-skills/outlook/tools/outlook-outreach-scan.ts +237 -0
- package/src/config/bundled-skills/outlook/tools/outlook-rules.ts +161 -0
- package/src/config/bundled-skills/outlook/tools/outlook-send-draft.ts +32 -0
- package/src/config/bundled-skills/outlook/tools/outlook-sender-digest.ts +272 -0
- package/src/config/bundled-skills/outlook/tools/outlook-trash.ts +29 -0
- package/src/config/bundled-skills/outlook/tools/outlook-unsubscribe.ts +129 -0
- package/src/config/bundled-skills/outlook/tools/outlook-vacation.ts +87 -0
- package/src/config/bundled-skills/outlook/tools/shared.ts +20 -0
- package/src/config/bundled-skills/outlook-calendar/SKILL.md +51 -0
- package/src/config/bundled-skills/outlook-calendar/TOOLS.json +221 -0
- package/src/config/bundled-skills/outlook-calendar/calendar-client.ts +252 -0
- package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-check-availability.ts +53 -0
- package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-create-event.ts +74 -0
- package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-get-event.ts +18 -0
- package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-list-events.ts +46 -0
- package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-rsvp.ts +36 -0
- package/src/config/bundled-skills/outlook-calendar/tools/shared.ts +17 -0
- package/src/config/bundled-skills/outlook-calendar/types.ts +120 -0
- package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +47 -40
- package/src/config/bundled-skills/playbooks/tools/playbook-delete.ts +16 -29
- package/src/config/bundled-skills/playbooks/tools/playbook-list.ts +16 -18
- package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +39 -47
- package/src/config/bundled-skills/slack/SKILL.md +1 -7
- package/src/config/bundled-tool-registry.ts +56 -4
- package/src/config/env-registry.ts +15 -8
- package/src/config/feature-flag-registry.json +21 -124
- package/src/config/schemas/platform.ts +8 -0
- package/src/config/schemas/timeouts.ts +1 -1
- package/src/config/skills.ts +18 -7
- package/src/context/token-estimator.ts +25 -18
- package/src/context/window-manager.ts +6 -2
- package/src/credential-execution/process-manager.ts +3 -1
- package/src/daemon/context-overflow-reducer.ts +46 -2
- package/src/daemon/conversation-agent-loop-handlers.ts +123 -82
- package/src/daemon/conversation-agent-loop.ts +96 -61
- package/src/daemon/conversation-error.ts +31 -8
- package/src/daemon/conversation-lifecycle.ts +33 -0
- package/src/daemon/conversation-media-retry.ts +85 -7
- package/src/daemon/conversation-notifiers.ts +4 -1
- package/src/daemon/conversation-runtime-assembly.ts +5 -0
- package/src/daemon/conversation.ts +41 -2
- package/src/daemon/daemon-control.ts +8 -2
- package/src/daemon/handlers/shared.ts +22 -12
- package/src/daemon/handlers/skills.ts +416 -202
- package/src/daemon/lifecycle.ts +40 -1
- package/src/daemon/main.ts +5 -1
- package/src/daemon/message-types/conversations.ts +4 -1
- package/src/daemon/message-types/messages.ts +3 -1
- package/src/daemon/message-types/skills.ts +97 -36
- package/src/daemon/providers-setup.ts +5 -0
- package/src/daemon/server.ts +11 -2
- package/src/daemon/tool-side-effects.ts +27 -5
- package/src/heartbeat/heartbeat-service.ts +1 -0
- package/src/hooks/cli.ts +2 -2
- package/src/hooks/runner.ts +15 -38
- package/src/inbound/platform-callback-registration.ts +14 -14
- package/src/memory/admin.ts +11 -45
- package/src/memory/conversation-bootstrap.ts +2 -0
- package/src/memory/conversation-crud.ts +242 -348
- package/src/memory/conversation-group-migration.ts +157 -0
- package/src/memory/conversation-queries.ts +4 -2
- package/src/memory/db-init.ts +30 -3
- package/src/memory/embed.ts +73 -0
- package/src/memory/embedding-backend.ts +8 -14
- package/src/memory/embedding-runtime-manager.ts +12 -114
- package/src/memory/fingerprint.ts +2 -2
- package/src/memory/graph/bootstrap.ts +512 -0
- package/src/memory/graph/capability-seed.ts +297 -0
- package/src/memory/graph/consolidation.ts +691 -0
- package/src/memory/graph/conversation-graph-memory.ts +630 -0
- package/src/memory/graph/decay.test.ts +208 -0
- package/src/memory/graph/decay.ts +195 -0
- package/src/memory/graph/extraction-job.ts +69 -0
- package/src/memory/graph/extraction.test.ts +936 -0
- package/src/memory/graph/extraction.ts +1254 -0
- package/src/memory/graph/graph-search.ts +266 -0
- package/src/memory/graph/image-ref-utils.ts +29 -0
- package/src/memory/graph/injection.test.ts +513 -0
- package/src/memory/graph/injection.ts +439 -0
- package/src/memory/graph/inspect.ts +534 -0
- package/src/memory/graph/narrative.ts +267 -0
- package/src/memory/graph/pattern-scan.ts +269 -0
- package/src/memory/graph/retriever.ts +1008 -0
- package/src/memory/graph/scoring.test.ts +548 -0
- package/src/memory/graph/scoring.ts +232 -0
- package/src/memory/graph/serendipity.ts +65 -0
- package/src/memory/graph/store.test.ts +1050 -0
- package/src/memory/graph/store.ts +699 -0
- package/src/memory/graph/tool-handlers.ts +426 -0
- package/src/memory/graph/tools.ts +141 -0
- package/src/memory/graph/triggers.test.ts +487 -0
- package/src/memory/graph/triggers.ts +223 -0
- package/src/memory/graph/types.ts +271 -0
- package/src/memory/group-crud.ts +191 -0
- package/src/memory/indexer.ts +37 -19
- package/src/memory/job-handlers/cleanup.ts +0 -53
- package/src/memory/job-handlers/conversation-starters.ts +91 -53
- package/src/memory/job-handlers/embedding.ts +5 -31
- package/src/memory/job-handlers/index-maintenance.ts +23 -11
- package/src/memory/job-handlers/summarization.ts +32 -17
- package/src/memory/job-utils.ts +1 -1
- package/src/memory/jobs-store.ts +50 -70
- package/src/memory/jobs-worker.ts +147 -112
- package/src/memory/message-content.ts +1 -0
- package/src/memory/migrations/202-memory-graph-tables.ts +130 -0
- package/src/memory/migrations/203-drop-memory-items-tables.ts +23 -0
- package/src/memory/migrations/204-rename-memory-graph-type-values.ts +46 -0
- package/src/memory/migrations/205-memory-graph-image-refs.ts +11 -0
- package/src/memory/migrations/index.ts +4 -0
- package/src/memory/migrations/registry.ts +8 -0
- package/src/memory/qdrant-client.ts +44 -17
- package/src/memory/schema/index.ts +1 -0
- package/src/memory/schema/memory-graph.ts +139 -0
- package/src/memory/search/semantic.ts +47 -91
- package/src/memory/task-memory-cleanup.ts +28 -50
- package/src/messaging/providers/outlook/adapter.ts +8 -1
- package/src/messaging/providers/outlook/client.ts +299 -0
- package/src/messaging/providers/outlook/types.ts +118 -0
- package/src/notifications/adapters/macos.ts +1 -0
- package/src/notifications/copy-composer.ts +9 -0
- package/src/notifications/signal.ts +16 -0
- package/src/oauth/seed-providers.ts +2 -1
- package/src/permissions/checker.ts +24 -3
- package/src/permissions/defaults.ts +4 -4
- package/src/permissions/workspace-policy.ts +1 -1
- package/src/playbooks/playbook-compiler.ts +19 -18
- package/src/playbooks/types.ts +4 -3
- package/src/prompts/system-prompt.ts +3 -29
- package/src/providers/anthropic/client.ts +47 -19
- package/src/providers/gemini/client.ts +1 -1
- package/src/providers/openai/client.ts +1 -1
- package/src/providers/registry.ts +1 -1
- package/src/providers/retry.ts +19 -3
- package/src/runtime/actor-trust-resolver.ts +5 -1
- package/src/runtime/auth/route-policy.ts +7 -0
- package/src/runtime/guardian-reply-router.ts +5 -1
- package/src/runtime/http-server.ts +23 -3
- package/src/runtime/middleware/auth.ts +20 -0
- package/src/runtime/routes/attachment-routes.test.ts +106 -0
- package/src/runtime/routes/attachment-routes.ts +106 -16
- package/src/runtime/routes/brain-graph-routes.ts +21 -22
- package/src/runtime/routes/btw-routes.ts +8 -0
- package/src/runtime/routes/conversation-management-routes.ts +2 -0
- package/src/runtime/routes/conversation-starter-routes.ts +2 -2
- package/src/runtime/routes/debug-routes.ts +1 -1
- package/src/runtime/routes/global-search-routes.ts +21 -19
- package/src/runtime/routes/group-routes.ts +207 -0
- package/src/runtime/routes/guardian-action-routes.ts +21 -10
- package/src/runtime/routes/guardian-bootstrap-routes.ts +23 -19
- package/src/runtime/routes/inbound-message-handler.ts +19 -0
- package/src/runtime/routes/inbound-stages/guardian-activation-intercept.test.ts +292 -0
- package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +207 -0
- package/src/runtime/routes/memory-item-routes.test.ts +2 -14
- package/src/runtime/routes/memory-item-routes.ts +341 -388
- package/src/runtime/routes/schedule-routes.ts +2 -0
- package/src/runtime/routes/skills-routes.ts +103 -37
- package/src/runtime/routes/work-items-routes.test.ts +2 -6
- package/src/schedule/scheduler.ts +8 -1
- package/src/security/oauth2.ts +1 -1
- package/src/security/secure-keys.ts +4 -8
- package/src/shared/provider-env-vars.ts +19 -0
- package/src/skills/catalog-cache.ts +5 -0
- package/src/skills/catalog-install.ts +15 -14
- package/src/skills/clawhub.ts +134 -154
- package/src/skills/install-meta.ts +208 -0
- package/src/skills/managed-store.ts +27 -16
- package/src/skills/skill-memory.ts +152 -77
- package/src/skills/skillssh-registry.ts +19 -17
- package/src/tasks/task-runner.ts +3 -1
- package/src/telemetry/usage-telemetry-reporter.test.ts +3 -5
- package/src/tools/browser/runtime-check.ts +3 -1
- package/src/tools/memory/register.ts +63 -46
- package/src/tools/permission-checker.ts +7 -1
- package/src/tools/shared/filesystem/image-read.ts +22 -85
- package/src/tools/terminal/safe-env.ts +1 -0
- package/src/tools/tool-manifest.ts +3 -3
- package/src/util/browser.ts +25 -10
- package/src/util/bun-runtime.ts +172 -0
- package/src/watcher/providers/outlook-calendar.ts +343 -0
- package/src/watcher/providers/outlook.ts +198 -0
- package/src/workspace/migrations/025-remove-oauth-app-setup-skills.ts +76 -0
- package/src/workspace/migrations/026-backfill-install-meta.ts +325 -0
- package/src/workspace/migrations/027-remove-orphaned-optimized-images-cache.ts +42 -0
- package/src/workspace/migrations/registry.ts +6 -0
- package/src/__tests__/context-memory-e2e.test.ts +0 -415
- package/src/__tests__/journal-context.test.ts +0 -268
- package/src/__tests__/memory-context-benchmark.benchmark.test.ts +0 -297
- package/src/__tests__/memory-lifecycle-e2e.test.ts +0 -459
- package/src/__tests__/memory-query-builder.test.ts +0 -59
- package/src/__tests__/memory-recall-quality.test.ts +0 -1046
- package/src/__tests__/memory-regressions.experimental.test.ts +0 -629
- package/src/__tests__/memory-regressions.test.ts +0 -3696
- package/src/__tests__/memory-retrieval.benchmark.test.ts +0 -295
- package/src/daemon/conversation-memory.ts +0 -207
- package/src/memory/conversation-starters-cadence.ts +0 -74
- package/src/memory/items-extractor.ts +0 -860
- package/src/memory/job-handlers/batch-extraction.ts +0 -753
- package/src/memory/job-handlers/extraction.ts +0 -40
- package/src/memory/job-handlers/journal-carry-forward.test.ts +0 -355
- package/src/memory/job-handlers/journal-carry-forward.ts +0 -255
- package/src/memory/journal-memory.ts +0 -224
- package/src/memory/query-builder.ts +0 -47
- package/src/memory/query-expansion.ts +0 -83
- package/src/memory/retriever.test.ts +0 -1592
- package/src/memory/retriever.ts +0 -1331
- package/src/memory/search/formatting.test.ts +0 -140
- package/src/memory/search/formatting.ts +0 -262
- package/src/memory/search/mmr.ts +0 -139
- package/src/memory/search/ranking.ts +0 -15
- package/src/memory/search/staleness.ts +0 -40
- package/src/memory/search/tier-classifier.ts +0 -18
- package/src/memory/search/types.ts +0 -121
- package/src/prompts/journal-context.ts +0 -154
- package/src/tools/memory/definitions.ts +0 -69
- package/src/tools/memory/handlers.test.ts +0 -562
- 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
|
+
}
|