@vellumai/assistant 0.4.46 → 0.4.49
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 +7 -7
- package/README.md +2 -23
- package/docs/architecture/integrations.md +45 -41
- package/docs/architecture/keychain-broker.md +3 -3
- package/docs/architecture/security.md +5 -5
- package/docs/runbook-trusted-contacts.md +3 -8
- package/hook-templates/debug-prompt-logger/hook.json +1 -1
- package/hook-templates/debug-prompt-logger/run.sh +1 -3
- package/package.json +1 -1
- package/src/__tests__/actor-token-service.test.ts +0 -1
- package/src/__tests__/anthropic-provider.test.ts +156 -0
- package/src/__tests__/approval-cascade.test.ts +810 -0
- package/src/__tests__/approval-primitive.test.ts +0 -1
- package/src/__tests__/approval-routes-http.test.ts +2 -0
- package/src/__tests__/assistant-attachments.test.ts +12 -34
- package/src/__tests__/assistant-feature-flag-guardrails.test.ts +76 -0
- package/src/__tests__/assistant-feature-flags-integration.test.ts +0 -1
- package/src/__tests__/browser-fill-credential.test.ts +5 -2
- package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +2 -2
- package/src/__tests__/bundled-skill-retrieval-guard.test.ts +2 -1
- package/src/__tests__/channel-guardian.test.ts +0 -2
- package/src/__tests__/channel-readiness-routes.test.ts +35 -25
- package/src/__tests__/channel-readiness-service.test.ts +10 -9
- package/src/__tests__/checker.test.ts +9 -29
- package/src/__tests__/cli.test.ts +23 -0
- package/src/__tests__/computer-use-skill-manifest-regression.test.ts +1 -1
- package/src/__tests__/computer-use-tools.test.ts +2 -19
- package/src/__tests__/config-watcher.test.ts +0 -1
- package/src/__tests__/confirmation-request-guardian-bridge.test.ts +0 -1
- package/src/__tests__/context-image-dimensions.test.ts +332 -0
- package/src/__tests__/context-token-estimator.test.ts +196 -13
- package/src/__tests__/conversation-attention-store.test.ts +0 -1
- package/src/__tests__/conversation-attention-telegram.test.ts +0 -1
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +144 -0
- package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -0
- package/src/__tests__/credential-broker-browser-fill.test.ts +23 -22
- package/src/__tests__/credential-broker-server-use.test.ts +22 -21
- package/src/__tests__/credential-broker.test.ts +2 -1
- package/src/__tests__/credential-metadata-store.test.ts +239 -26
- package/src/__tests__/credential-resolve.test.ts +5 -4
- package/src/__tests__/credential-security-e2e.test.ts +8 -8
- package/src/__tests__/credential-security-invariants.test.ts +111 -7
- package/src/__tests__/credential-vault-unit.test.ts +287 -54
- package/src/__tests__/credential-vault.test.ts +406 -12
- package/src/__tests__/credentials-cli.test.ts +82 -6
- package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +0 -1
- package/src/__tests__/ephemeral-permissions.test.ts +3 -3
- package/src/__tests__/gateway-only-enforcement.test.ts +4 -2
- package/src/__tests__/gateway-only-guard.test.ts +0 -1
- package/src/__tests__/gemini-image-service.test.ts +75 -45
- package/src/__tests__/gemini-provider.test.ts +9 -6
- package/src/__tests__/guardian-action-conversation-turn.test.ts +1 -33
- package/src/__tests__/guardian-action-copy-generator.test.ts +0 -20
- package/src/__tests__/guardian-action-followup-executor.test.ts +1 -28
- package/src/__tests__/guardian-action-followup-store.test.ts +1 -1
- package/src/__tests__/guardian-action-grant-mint-consume.test.ts +0 -1
- package/src/__tests__/guardian-decision-primitive-canonical.test.ts +0 -1
- package/src/__tests__/guardian-grant-minting.test.ts +35 -0
- package/src/__tests__/guardian-routing-invariants.test.ts +0 -1
- package/src/__tests__/guardian-verification-voice-binding.test.ts +0 -1
- package/src/__tests__/handlers-user-message-approval-consumption.test.ts +0 -39
- package/src/__tests__/heartbeat-service.test.ts +0 -1
- package/src/__tests__/host-cu-proxy.test.ts +629 -0
- package/src/__tests__/host-shell-tool.test.ts +27 -15
- package/src/__tests__/http-user-message-parity.test.ts +1 -0
- package/src/__tests__/ingress-url-consistency.test.ts +14 -21
- package/src/__tests__/integration-status.test.ts +38 -25
- package/src/__tests__/intent-routing.test.ts +0 -1
- package/src/__tests__/invite-routes-http.test.ts +10 -9
- package/src/__tests__/keychain-broker-client.test.ts +11 -43
- package/src/__tests__/managed-proxy-context.test.ts +5 -3
- package/src/__tests__/media-generate-image.test.ts +63 -2
- package/src/__tests__/media-reuse-story.e2e.test.ts +7 -3
- package/src/__tests__/messaging-send-tool.test.ts +4 -6
- package/src/__tests__/notification-routing-intent.test.ts +0 -1
- package/src/__tests__/oauth-cli.test.ts +373 -14
- package/src/__tests__/oauth-provider-profiles.test.ts +9 -9
- package/src/__tests__/oauth-scope-policy.test.ts +4 -6
- package/src/__tests__/oauth-store.test.ts +756 -0
- package/src/__tests__/onboarding-starter-tasks.test.ts +0 -1
- package/src/__tests__/provider-error-scenarios.test.ts +0 -1
- package/src/__tests__/provider-fail-open-selection.test.ts +3 -1
- package/src/__tests__/provider-managed-proxy-integration.test.ts +70 -6
- package/src/__tests__/provider-streaming.benchmark.test.ts +0 -1
- package/src/__tests__/public-ingress-urls.test.ts +15 -21
- package/src/__tests__/recording-handler.test.ts +3 -4
- package/src/__tests__/registry.test.ts +2 -2
- package/src/__tests__/runtime-events-sse.test.ts +55 -7
- package/src/__tests__/schedule-store.test.ts +0 -1
- package/src/__tests__/scheduler-recurrence.test.ts +0 -1
- package/src/__tests__/schema-transforms.test.ts +226 -0
- package/src/__tests__/scoped-approval-grants.test.ts +0 -1
- package/src/__tests__/scoped-grant-security-matrix.test.ts +0 -1
- package/src/__tests__/script-proxy-injection-runtime.test.ts +23 -13
- package/src/__tests__/script-proxy-policy-runtime.test.ts +1 -1
- package/src/__tests__/script-proxy-session-manager.test.ts +1 -1
- package/src/__tests__/secret-ingress-handler.test.ts +0 -1
- package/src/__tests__/secret-onetime-send.test.ts +5 -3
- package/src/__tests__/send-endpoint-busy.test.ts +21 -6
- package/src/__tests__/sequence-store.test.ts +0 -1
- package/src/__tests__/session-init.benchmark.test.ts +4 -5
- package/src/__tests__/session-messaging-secret-redirect.test.ts +5 -4
- package/src/__tests__/skill-include-graph.test.ts +66 -0
- package/src/__tests__/skill-load-feature-flag.test.ts +0 -1
- package/src/__tests__/skill-load-tool.test.ts +149 -1
- package/src/__tests__/skill-projection-feature-flag.test.ts +0 -1
- package/src/__tests__/skills-uninstall.test.ts +3 -3
- package/src/__tests__/skills.test.ts +3 -12
- package/src/__tests__/slack-channel-config.test.ts +76 -11
- package/src/__tests__/slack-share-routes.test.ts +17 -14
- package/src/__tests__/system-prompt.test.ts +0 -1
- package/src/__tests__/telegram-bot-username-resolution.test.ts +3 -0
- package/src/__tests__/telegram-invite-adapter.test.ts +18 -22
- package/src/__tests__/terminal-tools.test.ts +4 -3
- package/src/__tests__/test-support/computer-use-skill-harness.ts +3 -2
- package/src/__tests__/tool-approval-handler.test.ts +0 -1
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +0 -1
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +0 -1
- package/src/__tests__/tool-executor-shell-integration.test.ts +0 -1
- package/src/__tests__/tool-executor.test.ts +0 -1
- package/src/__tests__/tool-grant-request-escalation.test.ts +0 -1
- package/src/__tests__/trust-store-pattern-matches.test.ts +29 -0
- package/src/__tests__/trust-store.test.ts +1 -22
- package/src/__tests__/trusted-contact-approval-notifier.test.ts +0 -1
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +0 -1
- package/src/__tests__/twilio-config.test.ts +2 -1
- package/src/__tests__/twilio-provider.test.ts +4 -2
- package/src/__tests__/twilio-routes.test.ts +5 -20
- package/src/__tests__/verification-control-plane-policy.test.ts +0 -1
- package/src/__tests__/voice-scoped-grant-consumer.test.ts +0 -1
- package/src/agent/ax-tree-compaction.test.ts +235 -0
- package/src/agent/loop.ts +76 -130
- package/src/calls/call-domain.ts +8 -10
- package/src/calls/relay-server.ts +9 -13
- package/src/calls/twilio-config.ts +4 -8
- package/src/calls/twilio-provider.ts +2 -1
- package/src/calls/twilio-rest.ts +2 -1
- package/src/calls/twilio-routes.ts +1 -2
- package/src/calls/voice-ingress-preflight.ts +1 -1
- package/src/cli/commands/browser-relay.ts +46 -15
- package/src/cli/commands/completions.ts +0 -3
- package/src/cli/commands/credentials.ts +110 -23
- package/src/cli/commands/oauth/apps.ts +255 -0
- package/src/cli/commands/oauth/connections.ts +299 -0
- package/src/cli/commands/oauth/index.ts +52 -0
- package/src/cli/commands/oauth/providers.ts +242 -0
- package/src/cli/commands/skills.ts +4 -338
- package/src/cli/program.ts +1 -5
- package/src/cli/reference.ts +1 -3
- package/src/cli.ts +3 -2
- package/src/config/assistant-feature-flags.ts +0 -3
- package/src/config/bundled-skills/_shared/CLI_RETRIEVAL_PATTERN.md +1 -1
- package/src/config/bundled-skills/claude-code/TOOLS.json +0 -4
- package/src/config/bundled-skills/computer-use/SKILL.md +3 -6
- package/src/config/bundled-skills/computer-use/TOOLS.json +22 -4
- package/src/config/bundled-skills/contacts/tools/google-contacts.ts +29 -32
- package/src/config/bundled-skills/gmail/SKILL.md +4 -4
- package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +54 -61
- package/src/config/bundled-skills/gmail/tools/gmail-attachments.ts +25 -28
- package/src/config/bundled-skills/gmail/tools/gmail-draft.ts +14 -17
- package/src/config/bundled-skills/gmail/tools/gmail-filters.ts +39 -44
- package/src/config/bundled-skills/gmail/tools/gmail-follow-up.ts +61 -58
- package/src/config/bundled-skills/gmail/tools/gmail-forward.ts +50 -49
- package/src/config/bundled-skills/gmail/tools/gmail-label.ts +11 -13
- package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +148 -146
- package/src/config/bundled-skills/gmail/tools/gmail-send-draft.ts +4 -7
- package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +175 -173
- package/src/config/bundled-skills/gmail/tools/gmail-trash.ts +4 -7
- package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +71 -76
- package/src/config/bundled-skills/gmail/tools/gmail-vacation.ts +32 -38
- package/src/config/bundled-skills/google-calendar/SKILL.md +2 -2
- package/src/config/bundled-skills/google-calendar/calendar-client.ts +90 -44
- package/src/config/bundled-skills/google-calendar/tools/calendar-check-availability.ts +9 -10
- package/src/config/bundled-skills/google-calendar/tools/calendar-create-event.ts +5 -6
- package/src/config/bundled-skills/google-calendar/tools/calendar-get-event.ts +4 -5
- package/src/config/bundled-skills/google-calendar/tools/calendar-list-events.ts +14 -15
- package/src/config/bundled-skills/google-calendar/tools/calendar-rsvp.ts +37 -37
- package/src/config/bundled-skills/google-calendar/tools/shared.ts +4 -9
- package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +24 -3
- package/src/config/bundled-skills/messaging/SKILL.md +6 -6
- package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +62 -63
- package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +15 -16
- package/src/config/bundled-skills/messaging/tools/messaging-auth-test.ts +4 -5
- package/src/config/bundled-skills/messaging/tools/messaging-list-conversations.ts +6 -7
- package/src/config/bundled-skills/messaging/tools/messaging-mark-read.ts +4 -5
- package/src/config/bundled-skills/messaging/tools/messaging-read.ts +14 -15
- package/src/config/bundled-skills/messaging/tools/messaging-search.ts +4 -5
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +128 -128
- package/src/config/bundled-skills/messaging/tools/messaging-sender-digest.ts +33 -34
- package/src/config/bundled-skills/messaging/tools/shared.ts +12 -15
- package/src/config/bundled-skills/settings/SKILL.md +1 -1
- package/src/config/bundled-skills/settings/TOOLS.json +2 -8
- package/src/config/bundled-skills/settings/tools/voice-config-update.ts +5 -33
- package/src/config/bundled-skills/slack/tools/shared.ts +4 -10
- package/src/config/bundled-skills/slack/tools/slack-add-reaction.ts +4 -5
- package/src/config/bundled-skills/slack/tools/slack-channel-details.ts +15 -16
- package/src/config/bundled-skills/slack/tools/slack-delete-message.ts +4 -5
- package/src/config/bundled-skills/slack/tools/slack-edit-message.ts +4 -5
- package/src/config/bundled-skills/slack/tools/slack-leave-channel.ts +4 -5
- package/src/config/bundled-skills/slack/tools/slack-scan-digest.ts +95 -92
- package/src/config/env-registry.ts +14 -83
- package/src/config/env.ts +11 -50
- package/src/config/feature-flag-registry.json +16 -16
- package/src/config/schema.ts +3 -1
- package/src/config/skills.ts +21 -2
- package/src/context/image-dimensions.ts +229 -0
- package/src/context/token-estimator.ts +75 -12
- package/src/context/window-manager.ts +49 -10
- package/src/daemon/assistant-attachments.ts +1 -13
- package/src/daemon/guardian-action-generators.ts +4 -5
- package/src/daemon/handlers/config-ingress.ts +8 -33
- package/src/daemon/handlers/config-slack-channel.ts +76 -56
- package/src/daemon/handlers/config-telegram.ts +53 -24
- package/src/daemon/handlers/sessions.ts +10 -24
- package/src/daemon/handlers/shared.ts +0 -130
- package/src/daemon/host-cu-proxy.ts +401 -0
- package/src/daemon/lifecycle.ts +39 -63
- package/src/daemon/message-protocol.ts +3 -0
- package/src/daemon/message-types/computer-use.ts +2 -119
- package/src/daemon/message-types/host-cu.ts +19 -0
- package/src/daemon/message-types/integrations.ts +1 -0
- package/src/daemon/message-types/messages.ts +3 -0
- package/src/daemon/server.ts +14 -21
- package/src/daemon/session-agent-loop-handlers.ts +2 -0
- package/src/daemon/session-attachments.ts +1 -2
- package/src/daemon/session-messaging.ts +3 -1
- package/src/daemon/session-slash.ts +1 -1
- package/src/daemon/session-surfaces.ts +40 -28
- package/src/daemon/session-tool-setup.ts +20 -11
- package/src/daemon/session.ts +139 -16
- package/src/daemon/tool-side-effects.ts +2 -8
- package/src/daemon/watch-handler.ts +2 -2
- package/src/email/providers/index.ts +2 -1
- package/src/events/tool-metrics-listener.ts +2 -2
- package/src/hooks/manager.ts +1 -4
- package/src/inbound/public-ingress-urls.ts +7 -7
- package/src/instrument.ts +15 -1
- package/src/logfire.ts +16 -5
- package/src/media/app-icon-generator.ts +30 -4
- package/src/media/avatar-router.ts +26 -3
- package/src/media/gemini-image-service.ts +28 -2
- package/src/memory/conversation-key-store.ts +21 -0
- package/src/memory/db-init.ts +4 -0
- package/src/memory/guardian-action-store.ts +1 -1
- package/src/memory/migrations/149-oauth-tables.ts +60 -0
- package/src/memory/migrations/index.ts +1 -0
- package/src/memory/schema/guardian.ts +1 -1
- package/src/memory/schema/index.ts +1 -0
- package/src/memory/schema/oauth.ts +65 -0
- package/src/messaging/provider.ts +19 -13
- package/src/messaging/providers/gmail/adapter.ts +40 -23
- package/src/messaging/providers/gmail/client.ts +283 -122
- package/src/messaging/providers/gmail/people-client.ts +32 -24
- package/src/messaging/providers/slack/adapter.ts +29 -19
- package/src/messaging/providers/slack/client.ts +265 -78
- package/src/messaging/providers/telegram-bot/adapter.ts +19 -18
- package/src/messaging/providers/whatsapp/adapter.ts +17 -11
- package/src/messaging/registry.ts +2 -31
- package/src/notifications/copy-composer.ts +0 -5
- package/src/notifications/signal.ts +4 -5
- package/src/oauth/byo-connection.test.ts +537 -0
- package/src/oauth/byo-connection.ts +128 -0
- package/src/oauth/connect-orchestrator.ts +139 -56
- package/src/oauth/connect-types.ts +17 -23
- package/src/oauth/connection-resolver.ts +58 -0
- package/src/oauth/connection.ts +38 -0
- package/src/oauth/manual-token-connection.ts +104 -0
- package/src/oauth/oauth-store.ts +496 -0
- package/src/oauth/platform-connection.test.ts +192 -0
- package/src/oauth/platform-connection.ts +111 -0
- package/src/oauth/provider-behaviors.ts +124 -0
- package/src/oauth/scope-policy.ts +9 -2
- package/src/oauth/seed-providers.ts +161 -0
- package/src/oauth/token-persistence.ts +74 -78
- package/src/permissions/checker.ts +8 -4
- package/src/permissions/defaults.ts +0 -1
- package/src/permissions/prompter.ts +10 -1
- package/src/permissions/trust-store.ts +13 -0
- package/src/prompts/__tests__/build-cli-reference-section.test.ts +3 -1
- package/src/prompts/system-prompt.ts +70 -45
- package/src/providers/anthropic/client.ts +133 -24
- package/src/providers/gemini/client.ts +15 -6
- package/src/providers/managed-proxy/constants.ts +2 -2
- package/src/providers/managed-proxy/context.ts +5 -1
- package/src/providers/ratelimit.ts +17 -0
- package/src/providers/registry.ts +2 -2
- package/src/providers/retry.ts +1 -27
- package/src/runtime/AGENTS.md +17 -0
- package/src/runtime/auth/route-policy.ts +0 -3
- package/src/runtime/channel-invite-transports/telegram.ts +2 -1
- package/src/runtime/channel-readiness-service.ts +168 -195
- package/src/runtime/channel-readiness-types.ts +4 -0
- package/src/runtime/channel-reply-delivery.ts +0 -40
- package/src/runtime/gateway-client.ts +0 -7
- package/src/runtime/guardian-action-conversation-turn.ts +1 -3
- package/src/runtime/guardian-action-followup-executor.ts +1 -1
- package/src/runtime/guardian-action-message-composer.ts +3 -23
- package/src/runtime/http-server.ts +17 -10
- package/src/runtime/http-types.ts +2 -3
- package/src/runtime/middleware/rate-limiter.ts +74 -20
- package/src/runtime/middleware/twilio-validation.ts +1 -11
- package/src/runtime/pending-interactions.ts +14 -12
- package/src/runtime/routes/channel-delivery-routes.ts +0 -1
- package/src/runtime/routes/channel-readiness-routes.ts +2 -0
- package/src/runtime/routes/conversation-routes.ts +73 -19
- package/src/runtime/routes/diagnostics-routes.ts +11 -9
- package/src/runtime/routes/events-routes.ts +21 -11
- package/src/runtime/routes/guardian-approval-interception.ts +20 -5
- package/src/runtime/routes/host-cu-routes.ts +97 -0
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +12 -111
- package/src/runtime/routes/integrations/slack/share.ts +6 -6
- package/src/runtime/routes/integrations/twilio.ts +6 -5
- package/src/runtime/routes/log-export-routes.ts +126 -8
- package/src/runtime/routes/secret-routes.ts +3 -2
- package/src/runtime/routes/settings-routes.ts +113 -48
- package/src/runtime/routes/surface-action-routes.ts +1 -1
- package/src/runtime/routes/watch-routes.ts +128 -0
- package/src/schedule/integration-status.ts +10 -8
- package/src/security/credential-key.ts +14 -0
- package/src/security/keychain-broker-client.ts +5 -6
- package/src/security/oauth2.ts +1 -1
- package/src/security/token-manager.ts +145 -43
- package/src/skills/catalog-install.ts +358 -0
- package/src/skills/include-graph.ts +32 -0
- package/src/telegram/bot-username.ts +2 -3
- package/src/tools/apps/definitions.ts +0 -5
- package/src/tools/assets/materialize.ts +0 -5
- package/src/tools/assets/search.ts +0 -5
- package/src/tools/browser/headless-browser.ts +1 -67
- package/src/tools/browser/network-recorder.ts +1 -1
- package/src/tools/browser/network-recording-types.ts +1 -1
- package/src/tools/claude-code/claude-code.ts +0 -5
- package/src/tools/computer-use/definitions.ts +46 -11
- package/src/tools/computer-use/registry.ts +4 -5
- package/src/tools/credentials/broker.ts +5 -4
- package/src/tools/credentials/metadata-store.ts +22 -74
- package/src/tools/credentials/resolve.ts +2 -1
- package/src/tools/credentials/vault.ts +139 -151
- package/src/tools/filesystem/edit.ts +1 -6
- package/src/tools/filesystem/read.ts +0 -5
- package/src/tools/filesystem/write.ts +1 -6
- package/src/tools/host-filesystem/edit.ts +1 -6
- package/src/tools/host-filesystem/read.ts +1 -6
- package/src/tools/host-filesystem/write.ts +1 -6
- package/src/tools/mcp/mcp-tool-factory.ts +18 -1
- package/src/tools/memory/definitions.ts +0 -5
- package/src/tools/network/web-fetch.ts +0 -5
- package/src/tools/network/web-search.ts +0 -5
- package/src/tools/registry.ts +2 -7
- package/src/tools/schema-transforms.ts +99 -0
- package/src/tools/skills/load.ts +62 -8
- package/src/tools/swarm/delegate.ts +0 -5
- package/src/tools/system/avatar-generator.ts +0 -5
- package/src/tools/ui-surface/definitions.ts +0 -15
- package/src/tools/watch/screen-watch.ts +0 -5
- package/src/tools/watch/watch-state.ts +0 -12
- package/src/util/logger.ts +7 -41
- package/src/util/platform.ts +9 -28
- package/src/version.ts +10 -0
- package/src/watcher/providers/github.ts +51 -52
- package/src/watcher/providers/gmail.ts +88 -80
- package/src/watcher/providers/google-calendar.ts +94 -86
- package/src/watcher/providers/linear.ts +87 -93
- package/src/__tests__/computer-use-session-compaction.test.ts +0 -143
- package/src/__tests__/computer-use-session-lifecycle.test.ts +0 -322
- package/src/__tests__/computer-use-session-working-dir.test.ts +0 -166
- package/src/__tests__/computer-use-skill-baseline.test.ts +0 -78
- package/src/__tests__/computer-use-skill-endstate.test.ts +0 -105
- package/src/__tests__/computer-use-skill-lifecycle-cleanup.test.ts +0 -249
- package/src/__tests__/ride-shotgun-handler.test.ts +0 -452
- package/src/cli/commands/dev.ts +0 -129
- package/src/cli/commands/map.ts +0 -391
- package/src/cli/commands/oauth.ts +0 -77
- package/src/config/bundled-skills/computer-use/tools/computer-use-request-control.ts +0 -16
- package/src/daemon/computer-use-session.ts +0 -1020
- package/src/daemon/ride-shotgun-handler.ts +0 -567
- package/src/oauth/provider-profiles.ts +0 -192
- package/src/prompts/computer-use-prompt.ts +0 -98
- package/src/runtime/routes/computer-use-routes.ts +0 -641
- package/src/runtime/telegram-streaming-delivery.test.ts +0 -597
- package/src/runtime/telegram-streaming-delivery.ts +0 -383
- package/src/tools/computer-use/request-computer-control.ts +0 -61
|
@@ -3,8 +3,7 @@ import {
|
|
|
3
3
|
listMessages,
|
|
4
4
|
} from "../../../../messaging/providers/gmail/client.js";
|
|
5
5
|
import type { GmailMessage } from "../../../../messaging/providers/gmail/types.js";
|
|
6
|
-
import {
|
|
7
|
-
import { withValidToken } from "../../../../security/token-manager.js";
|
|
6
|
+
import { resolveOAuthConnection } from "../../../../oauth/connection-resolver.js";
|
|
8
7
|
import type {
|
|
9
8
|
ToolContext,
|
|
10
9
|
ToolExecutionResult,
|
|
@@ -58,187 +57,190 @@ export async function run(
|
|
|
58
57
|
const inputPageToken = input.page_token as string | undefined;
|
|
59
58
|
|
|
60
59
|
try {
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
break;
|
|
79
|
-
}
|
|
80
|
-
const pageSize = Math.min(100, maxMessages - allMessageIds.length);
|
|
81
|
-
const listResp = await listMessages(token, query, pageSize, pageToken);
|
|
82
|
-
const ids = (listResp.messages ?? []).map((m) => m.id);
|
|
83
|
-
if (ids.length === 0) break;
|
|
84
|
-
allMessageIds.push(...ids);
|
|
85
|
-
fetchPromises.push(
|
|
86
|
-
batchGetMessages(
|
|
87
|
-
token,
|
|
88
|
-
ids,
|
|
89
|
-
"metadata",
|
|
90
|
-
metadataHeaders,
|
|
91
|
-
"id,internalDate,payload/headers",
|
|
92
|
-
),
|
|
93
|
-
);
|
|
94
|
-
pageToken = listResp.nextPageToken ?? undefined;
|
|
95
|
-
if (!pageToken) break;
|
|
60
|
+
const connection = resolveOAuthConnection("integration:gmail");
|
|
61
|
+
// Pipeline: fire metadata fetches for each page of IDs as they arrive,
|
|
62
|
+
// overlapping fetch latency with pagination latency
|
|
63
|
+
const allMessageIds: string[] = [];
|
|
64
|
+
const fetchPromises: Promise<GmailMessage[]>[] = [];
|
|
65
|
+
let pageToken: string | undefined = inputPageToken;
|
|
66
|
+
let truncated = false;
|
|
67
|
+
let timeBudgetExceeded = false;
|
|
68
|
+
const metadataHeaders = ["From", "List-Unsubscribe", "Subject", "Date"];
|
|
69
|
+
const startTime = Date.now();
|
|
70
|
+
const TIME_BUDGET_MS = 90_000;
|
|
71
|
+
|
|
72
|
+
while (allMessageIds.length < maxMessages) {
|
|
73
|
+
if (Date.now() - startTime > TIME_BUDGET_MS) {
|
|
74
|
+
timeBudgetExceeded = true;
|
|
75
|
+
truncated = true;
|
|
76
|
+
break;
|
|
96
77
|
}
|
|
78
|
+
const pageSize = Math.min(100, maxMessages - allMessageIds.length);
|
|
79
|
+
const listResp = await listMessages(
|
|
80
|
+
connection,
|
|
81
|
+
query,
|
|
82
|
+
pageSize,
|
|
83
|
+
pageToken,
|
|
84
|
+
);
|
|
85
|
+
const ids = (listResp.messages ?? []).map((m) => m.id);
|
|
86
|
+
if (ids.length === 0) break;
|
|
87
|
+
allMessageIds.push(...ids);
|
|
88
|
+
fetchPromises.push(
|
|
89
|
+
batchGetMessages(
|
|
90
|
+
connection,
|
|
91
|
+
ids,
|
|
92
|
+
"metadata",
|
|
93
|
+
metadataHeaders,
|
|
94
|
+
"id,internalDate,payload/headers",
|
|
95
|
+
),
|
|
96
|
+
);
|
|
97
|
+
pageToken = listResp.nextPageToken ?? undefined;
|
|
98
|
+
if (!pageToken) break;
|
|
99
|
+
}
|
|
97
100
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
+
// If we stopped because we hit the cap but there were still more pages, flag truncation
|
|
102
|
+
if (allMessageIds.length >= maxMessages && pageToken) {
|
|
103
|
+
truncated = true;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (allMessageIds.length === 0) {
|
|
107
|
+
return ok(
|
|
108
|
+
JSON.stringify({
|
|
109
|
+
senders: [],
|
|
110
|
+
total_scanned: 0,
|
|
111
|
+
message:
|
|
112
|
+
"No emails found matching the query. Try broadening the search (e.g. remove category filter or extend date range).",
|
|
113
|
+
}),
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const messages = (await Promise.all(fetchPromises)).flat();
|
|
118
|
+
|
|
119
|
+
// Group by sender email
|
|
120
|
+
const senderMap = new Map<string, SenderAggregation>();
|
|
121
|
+
|
|
122
|
+
for (const msg of messages) {
|
|
123
|
+
const headers = msg.payload?.headers ?? [];
|
|
124
|
+
const fromHeader =
|
|
125
|
+
headers.find((h) => h.name.toLowerCase() === "from")?.value ?? "";
|
|
126
|
+
const subject =
|
|
127
|
+
headers.find((h) => h.name.toLowerCase() === "subject")?.value ?? "";
|
|
128
|
+
const dateStr =
|
|
129
|
+
headers.find((h) => h.name.toLowerCase() === "date")?.value ?? "";
|
|
130
|
+
const listUnsub = headers.find(
|
|
131
|
+
(h) => h.name.toLowerCase() === "list-unsubscribe",
|
|
132
|
+
)?.value;
|
|
133
|
+
|
|
134
|
+
const { displayName, email } = parseFrom(fromHeader);
|
|
135
|
+
if (!email) continue;
|
|
136
|
+
|
|
137
|
+
let agg = senderMap.get(email);
|
|
138
|
+
if (!agg) {
|
|
139
|
+
agg = {
|
|
140
|
+
displayName,
|
|
141
|
+
email,
|
|
142
|
+
messageCount: 0,
|
|
143
|
+
hasUnsubscribe: false,
|
|
144
|
+
newestMessageId: msg.id,
|
|
145
|
+
newestUnsubscribableMessageId: null,
|
|
146
|
+
newestUnsubscribableEpoch: 0,
|
|
147
|
+
oldestDate: dateStr,
|
|
148
|
+
newestDate: dateStr,
|
|
149
|
+
messageIds: [],
|
|
150
|
+
hasMore: false,
|
|
151
|
+
sampleSubjects: [],
|
|
152
|
+
};
|
|
153
|
+
senderMap.set(email, agg);
|
|
101
154
|
}
|
|
102
155
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
156
|
+
agg.messageCount++;
|
|
157
|
+
|
|
158
|
+
if (listUnsub) agg.hasUnsubscribe = true;
|
|
159
|
+
|
|
160
|
+
// Use displayName from earliest message that has one
|
|
161
|
+
if (!agg.displayName && displayName) agg.displayName = displayName;
|
|
162
|
+
|
|
163
|
+
// Track message IDs (cap at MAX_IDS_PER_SENDER)
|
|
164
|
+
if (agg.messageIds.length < MAX_IDS_PER_SENDER) {
|
|
165
|
+
agg.messageIds.push(msg.id);
|
|
166
|
+
} else {
|
|
167
|
+
agg.hasMore = true;
|
|
112
168
|
}
|
|
113
169
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
)?.value;
|
|
130
|
-
|
|
131
|
-
const { displayName, email } = parseFrom(fromHeader);
|
|
132
|
-
if (!email) continue;
|
|
133
|
-
|
|
134
|
-
let agg = senderMap.get(email);
|
|
135
|
-
if (!agg) {
|
|
136
|
-
agg = {
|
|
137
|
-
displayName,
|
|
138
|
-
email,
|
|
139
|
-
messageCount: 0,
|
|
140
|
-
hasUnsubscribe: false,
|
|
141
|
-
newestMessageId: msg.id,
|
|
142
|
-
newestUnsubscribableMessageId: null,
|
|
143
|
-
newestUnsubscribableEpoch: 0,
|
|
144
|
-
oldestDate: dateStr,
|
|
145
|
-
newestDate: dateStr,
|
|
146
|
-
messageIds: [],
|
|
147
|
-
hasMore: false,
|
|
148
|
-
sampleSubjects: [],
|
|
149
|
-
};
|
|
150
|
-
senderMap.set(email, agg);
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
agg.messageCount++;
|
|
154
|
-
|
|
155
|
-
if (listUnsub) agg.hasUnsubscribe = true;
|
|
156
|
-
|
|
157
|
-
// Use displayName from earliest message that has one
|
|
158
|
-
if (!agg.displayName && displayName) agg.displayName = displayName;
|
|
159
|
-
|
|
160
|
-
// Track message IDs (cap at MAX_IDS_PER_SENDER)
|
|
161
|
-
if (agg.messageIds.length < MAX_IDS_PER_SENDER) {
|
|
162
|
-
agg.messageIds.push(msg.id);
|
|
163
|
-
} else {
|
|
164
|
-
agg.hasMore = true;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
// Track date range — compare using internalDate (epoch ms) for reliability
|
|
168
|
-
const msgEpoch = msg.internalDate ? Number(msg.internalDate) : 0;
|
|
169
|
-
const oldestEpoch = agg.oldestDate
|
|
170
|
-
? new Date(agg.oldestDate).getTime()
|
|
171
|
-
: Infinity;
|
|
172
|
-
const newestEpoch = agg.newestDate
|
|
173
|
-
? new Date(agg.newestDate).getTime()
|
|
174
|
-
: 0;
|
|
175
|
-
|
|
176
|
-
if (msgEpoch > 0 && msgEpoch < oldestEpoch) {
|
|
177
|
-
agg.oldestDate = dateStr || agg.oldestDate;
|
|
178
|
-
}
|
|
179
|
-
if (msgEpoch > newestEpoch) {
|
|
180
|
-
agg.newestDate = dateStr || agg.newestDate;
|
|
181
|
-
agg.newestMessageId = msg.id;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
// Track the newest message that actually has List-Unsubscribe so
|
|
185
|
-
// gmail_unsubscribe() is called with a message that carries the header
|
|
186
|
-
if (listUnsub && msgEpoch >= agg.newestUnsubscribableEpoch) {
|
|
187
|
-
agg.newestUnsubscribableMessageId = msg.id;
|
|
188
|
-
agg.newestUnsubscribableEpoch = msgEpoch;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
// Collect sample subjects
|
|
192
|
-
if (subject && agg.sampleSubjects.length < MAX_SAMPLE_SUBJECTS) {
|
|
193
|
-
agg.sampleSubjects.push(subject);
|
|
194
|
-
}
|
|
170
|
+
// Track date range — compare using internalDate (epoch ms) for reliability
|
|
171
|
+
const msgEpoch = msg.internalDate ? Number(msg.internalDate) : 0;
|
|
172
|
+
const oldestEpoch = agg.oldestDate
|
|
173
|
+
? new Date(agg.oldestDate).getTime()
|
|
174
|
+
: Infinity;
|
|
175
|
+
const newestEpoch = agg.newestDate
|
|
176
|
+
? new Date(agg.newestDate).getTime()
|
|
177
|
+
: 0;
|
|
178
|
+
|
|
179
|
+
if (msgEpoch > 0 && msgEpoch < oldestEpoch) {
|
|
180
|
+
agg.oldestDate = dateStr || agg.oldestDate;
|
|
181
|
+
}
|
|
182
|
+
if (msgEpoch > newestEpoch) {
|
|
183
|
+
agg.newestDate = dateStr || agg.newestDate;
|
|
184
|
+
agg.newestMessageId = msg.id;
|
|
195
185
|
}
|
|
196
186
|
|
|
197
|
-
//
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
.
|
|
187
|
+
// Track the newest message that actually has List-Unsubscribe so
|
|
188
|
+
// gmail_unsubscribe() is called with a message that carries the header
|
|
189
|
+
if (listUnsub && msgEpoch >= agg.newestUnsubscribableEpoch) {
|
|
190
|
+
agg.newestUnsubscribableMessageId = msg.id;
|
|
191
|
+
agg.newestUnsubscribableEpoch = msgEpoch;
|
|
192
|
+
}
|
|
201
193
|
|
|
202
|
-
|
|
194
|
+
// Collect sample subjects
|
|
195
|
+
if (subject && agg.sampleSubjects.length < MAX_SAMPLE_SUBJECTS) {
|
|
196
|
+
agg.sampleSubjects.push(subject);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Sort by message count descending, take top N
|
|
201
|
+
const sorted = [...senderMap.values()]
|
|
202
|
+
.sort((a, b) => b.messageCount - a.messageCount)
|
|
203
|
+
.slice(0, maxSenders);
|
|
204
|
+
|
|
205
|
+
const resultSenders = sorted.map((s) => ({
|
|
206
|
+
id: Buffer.from(s.email).toString("base64url"),
|
|
207
|
+
display_name: s.displayName || s.email.split("@")[0],
|
|
208
|
+
email: s.email,
|
|
209
|
+
message_count: s.messageCount,
|
|
210
|
+
has_unsubscribe: s.hasUnsubscribe,
|
|
211
|
+
// When unsubscribe is available, point to a message that carries the header
|
|
212
|
+
newest_message_id:
|
|
213
|
+
s.hasUnsubscribe && s.newestUnsubscribableMessageId
|
|
214
|
+
? s.newestUnsubscribableMessageId
|
|
215
|
+
: s.newestMessageId,
|
|
216
|
+
oldest_date: s.oldestDate,
|
|
217
|
+
newest_date: s.newestDate,
|
|
218
|
+
// Preserve original query filters so follow-up searches stay scoped
|
|
219
|
+
search_query: `from:${s.email} ${query}`,
|
|
220
|
+
sample_subjects: s.sampleSubjects,
|
|
221
|
+
}));
|
|
222
|
+
|
|
223
|
+
// Store message IDs server-side to keep them out of LLM context
|
|
224
|
+
const scanId = storeScanResult(
|
|
225
|
+
sorted.map((s) => ({
|
|
203
226
|
id: Buffer.from(s.email).toString("base64url"),
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
const scanId = storeScanResult(
|
|
222
|
-
sorted.map((s) => ({
|
|
223
|
-
id: Buffer.from(s.email).toString("base64url"),
|
|
224
|
-
messageIds: s.messageIds,
|
|
225
|
-
newestMessageId: s.newestMessageId,
|
|
226
|
-
newestUnsubscribableMessageId: s.newestUnsubscribableMessageId,
|
|
227
|
-
})),
|
|
228
|
-
);
|
|
229
|
-
|
|
230
|
-
return ok(
|
|
231
|
-
JSON.stringify({
|
|
232
|
-
scan_id: scanId,
|
|
233
|
-
senders: resultSenders,
|
|
234
|
-
total_scanned: allMessageIds.length,
|
|
235
|
-
query_used: query,
|
|
236
|
-
...(truncated ? { truncated: true } : {}),
|
|
237
|
-
...(timeBudgetExceeded ? { time_budget_exceeded: true } : {}),
|
|
238
|
-
note: `message_count reflects emails found per sender within the ${allMessageIds.length} messages scanned. Use scan_id with gmail_archive to archive messages (pass scan_id + sender_ids instead of message_ids).`,
|
|
239
|
-
}),
|
|
240
|
-
);
|
|
241
|
-
});
|
|
227
|
+
messageIds: s.messageIds,
|
|
228
|
+
newestMessageId: s.newestMessageId,
|
|
229
|
+
newestUnsubscribableMessageId: s.newestUnsubscribableMessageId,
|
|
230
|
+
})),
|
|
231
|
+
);
|
|
232
|
+
|
|
233
|
+
return ok(
|
|
234
|
+
JSON.stringify({
|
|
235
|
+
scan_id: scanId,
|
|
236
|
+
senders: resultSenders,
|
|
237
|
+
total_scanned: allMessageIds.length,
|
|
238
|
+
query_used: query,
|
|
239
|
+
...(truncated ? { truncated: true } : {}),
|
|
240
|
+
...(timeBudgetExceeded ? { time_budget_exceeded: true } : {}),
|
|
241
|
+
note: `message_count reflects emails found per sender within the ${allMessageIds.length} messages scanned. Use scan_id with gmail_archive to archive messages (pass scan_id + sender_ids instead of message_ids).`,
|
|
242
|
+
}),
|
|
243
|
+
);
|
|
242
244
|
} catch (e) {
|
|
243
245
|
return err(e instanceof Error ? e.message : String(e));
|
|
244
246
|
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { trashMessage } from "../../../../messaging/providers/gmail/client.js";
|
|
2
|
-
import {
|
|
3
|
-
import { withValidToken } from "../../../../security/token-manager.js";
|
|
2
|
+
import { resolveOAuthConnection } from "../../../../oauth/connection-resolver.js";
|
|
4
3
|
import type {
|
|
5
4
|
ToolContext,
|
|
6
5
|
ToolExecutionResult,
|
|
@@ -18,11 +17,9 @@ export async function run(
|
|
|
18
17
|
}
|
|
19
18
|
|
|
20
19
|
try {
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
return ok("Message moved to trash.");
|
|
25
|
-
});
|
|
20
|
+
const connection = resolveOAuthConnection("integration:gmail");
|
|
21
|
+
await trashMessage(connection, messageId);
|
|
22
|
+
return ok("Message moved to trash.");
|
|
26
23
|
} catch (e) {
|
|
27
24
|
return err(e instanceof Error ? e.message : String(e));
|
|
28
25
|
}
|
|
@@ -2,8 +2,7 @@ import {
|
|
|
2
2
|
getMessage,
|
|
3
3
|
sendMessage,
|
|
4
4
|
} from "../../../../messaging/providers/gmail/client.js";
|
|
5
|
-
import {
|
|
6
|
-
import { withValidToken } from "../../../../security/token-manager.js";
|
|
5
|
+
import { resolveOAuthConnection } from "../../../../oauth/connection-resolver.js";
|
|
7
6
|
import {
|
|
8
7
|
isPrivateOrLocalHost,
|
|
9
8
|
resolveHostAddresses,
|
|
@@ -32,92 +31,88 @@ export async function run(
|
|
|
32
31
|
}
|
|
33
32
|
|
|
34
33
|
try {
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
)?.value;
|
|
34
|
+
const connection = resolveOAuthConnection("integration:gmail");
|
|
35
|
+
const message = await getMessage(connection, messageId, "metadata", [
|
|
36
|
+
"List-Unsubscribe",
|
|
37
|
+
"List-Unsubscribe-Post",
|
|
38
|
+
]);
|
|
39
|
+
const headers = message.payload?.headers ?? [];
|
|
40
|
+
const unsubHeader = headers.find(
|
|
41
|
+
(h) => h.name.toLowerCase() === "list-unsubscribe",
|
|
42
|
+
)?.value;
|
|
45
43
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
44
|
+
if (!unsubHeader) {
|
|
45
|
+
return err(
|
|
46
|
+
"No List-Unsubscribe header found. Manual unsubscribe may be required.",
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const httpsMatch = unsubHeader.match(/<(https:\/\/[^>]+)>/);
|
|
51
|
+
const mailtoMatch = unsubHeader.match(/<mailto:([^>]+)>/);
|
|
52
|
+
const postHeader = headers.find(
|
|
53
|
+
(h) => h.name.toLowerCase() === "list-unsubscribe-post",
|
|
54
|
+
)?.value;
|
|
55
|
+
|
|
56
|
+
if (httpsMatch) {
|
|
57
|
+
const url = httpsMatch[1];
|
|
58
|
+
let parsed: URL;
|
|
59
|
+
let validatedAddresses: string[];
|
|
60
|
+
try {
|
|
61
|
+
parsed = new URL(url);
|
|
62
|
+
if (parsed.protocol !== "https:") {
|
|
63
|
+
return err("Unsubscribe URL must use HTTPS.");
|
|
64
|
+
}
|
|
65
|
+
if (isPrivateOrLocalHost(parsed.hostname)) {
|
|
66
|
+
return err("Unsubscribe URL points to a private or local address.");
|
|
67
|
+
}
|
|
68
|
+
const { addresses, blockedAddress } = await resolveRequestAddress(
|
|
69
|
+
parsed.hostname,
|
|
70
|
+
resolveHostAddresses,
|
|
71
|
+
false,
|
|
49
72
|
);
|
|
73
|
+
if (blockedAddress) {
|
|
74
|
+
return err("Unsubscribe URL resolves to a private or local address.");
|
|
75
|
+
}
|
|
76
|
+
if (addresses.length === 0) {
|
|
77
|
+
return err("Unable to resolve unsubscribe URL hostname.");
|
|
78
|
+
}
|
|
79
|
+
validatedAddresses = addresses;
|
|
80
|
+
} catch {
|
|
81
|
+
return err("Invalid unsubscribe URL.");
|
|
50
82
|
}
|
|
51
83
|
|
|
52
|
-
const
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
84
|
+
const method = postHeader ? "POST" : "GET";
|
|
85
|
+
const reqOpts = postHeader
|
|
86
|
+
? {
|
|
87
|
+
method: "POST" as const,
|
|
88
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
89
|
+
body: postHeader,
|
|
90
|
+
}
|
|
91
|
+
: undefined;
|
|
57
92
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
let parsed: URL;
|
|
61
|
-
let validatedAddresses: string[];
|
|
93
|
+
let lastStatus = 0;
|
|
94
|
+
for (const address of validatedAddresses) {
|
|
62
95
|
try {
|
|
63
|
-
|
|
64
|
-
if (
|
|
65
|
-
return
|
|
66
|
-
}
|
|
67
|
-
if (isPrivateOrLocalHost(parsed.hostname)) {
|
|
68
|
-
return err("Unsubscribe URL points to a private or local address.");
|
|
69
|
-
}
|
|
70
|
-
const { addresses, blockedAddress } = await resolveRequestAddress(
|
|
71
|
-
parsed.hostname,
|
|
72
|
-
resolveHostAddresses,
|
|
73
|
-
false,
|
|
74
|
-
);
|
|
75
|
-
if (blockedAddress) {
|
|
76
|
-
return err(
|
|
77
|
-
"Unsubscribe URL resolves to a private or local address.",
|
|
78
|
-
);
|
|
96
|
+
lastStatus = await pinnedHttpsRequest(parsed, address, reqOpts);
|
|
97
|
+
if (lastStatus >= 200 && lastStatus < 400) {
|
|
98
|
+
return ok(`Successfully unsubscribed via HTTPS ${method}.`);
|
|
79
99
|
}
|
|
80
|
-
if (addresses.length === 0) {
|
|
81
|
-
return err("Unable to resolve unsubscribe URL hostname.");
|
|
82
|
-
}
|
|
83
|
-
validatedAddresses = addresses;
|
|
84
100
|
} catch {
|
|
85
|
-
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
const method = postHeader ? "POST" : "GET";
|
|
89
|
-
const reqOpts = postHeader
|
|
90
|
-
? {
|
|
91
|
-
method: "POST" as const,
|
|
92
|
-
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
93
|
-
body: postHeader,
|
|
94
|
-
}
|
|
95
|
-
: undefined;
|
|
96
|
-
|
|
97
|
-
let lastStatus = 0;
|
|
98
|
-
for (const address of validatedAddresses) {
|
|
99
|
-
try {
|
|
100
|
-
lastStatus = await pinnedHttpsRequest(parsed, address, reqOpts);
|
|
101
|
-
if (lastStatus >= 200 && lastStatus < 400) {
|
|
102
|
-
return ok(`Successfully unsubscribed via HTTPS ${method}.`);
|
|
103
|
-
}
|
|
104
|
-
} catch {
|
|
105
|
-
continue;
|
|
106
|
-
}
|
|
101
|
+
continue;
|
|
107
102
|
}
|
|
108
|
-
return err(`Unsubscribe request failed: ${lastStatus}`);
|
|
109
103
|
}
|
|
104
|
+
return err(`Unsubscribe request failed: ${lastStatus}`);
|
|
105
|
+
}
|
|
110
106
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
107
|
+
if (mailtoMatch) {
|
|
108
|
+
const mailtoAddr = mailtoMatch[1].split("?")[0];
|
|
109
|
+
await sendMessage(connection, mailtoAddr, "Unsubscribe", "Unsubscribe");
|
|
110
|
+
return ok(`Unsubscribe email sent to ${mailtoAddr}.`);
|
|
111
|
+
}
|
|
116
112
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
});
|
|
113
|
+
return err(
|
|
114
|
+
"No supported unsubscribe method found (requires https: or mailto: URL).",
|
|
115
|
+
);
|
|
121
116
|
} catch (e) {
|
|
122
117
|
return err(e instanceof Error ? e.message : String(e));
|
|
123
118
|
}
|
|
@@ -3,8 +3,7 @@ import {
|
|
|
3
3
|
updateVacation,
|
|
4
4
|
} from "../../../../messaging/providers/gmail/client.js";
|
|
5
5
|
import type { GmailVacationSettings } from "../../../../messaging/providers/gmail/types.js";
|
|
6
|
-
import {
|
|
7
|
-
import { withValidToken } from "../../../../security/token-manager.js";
|
|
6
|
+
import { resolveOAuthConnection } from "../../../../oauth/connection-resolver.js";
|
|
8
7
|
import type {
|
|
9
8
|
ToolContext,
|
|
10
9
|
ToolExecutionResult,
|
|
@@ -22,48 +21,43 @@ export async function run(
|
|
|
22
21
|
}
|
|
23
22
|
|
|
24
23
|
try {
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
case "enable": {
|
|
34
|
-
const message = input.message as string;
|
|
35
|
-
if (!message)
|
|
36
|
-
return err("message is required when enabling vacation responder.");
|
|
24
|
+
const connection = resolveOAuthConnection("integration:gmail");
|
|
25
|
+
switch (action) {
|
|
26
|
+
case "get": {
|
|
27
|
+
const settings = await getVacation(connection);
|
|
28
|
+
return ok(JSON.stringify(settings, null, 2));
|
|
29
|
+
}
|
|
37
30
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
restrictToContacts:
|
|
43
|
-
(input.restrict_to_contacts as boolean) ?? false,
|
|
44
|
-
restrictToDomain: (input.restrict_to_domain as boolean) ?? false,
|
|
45
|
-
};
|
|
31
|
+
case "enable": {
|
|
32
|
+
const message = input.message as string;
|
|
33
|
+
if (!message)
|
|
34
|
+
return err("message is required when enabling vacation responder.");
|
|
46
35
|
|
|
47
|
-
|
|
48
|
-
|
|
36
|
+
const settings: GmailVacationSettings = {
|
|
37
|
+
enableAutoReply: true,
|
|
38
|
+
responseSubject: (input.subject as string) ?? "Out of Office",
|
|
39
|
+
responseBodyPlainText: message,
|
|
40
|
+
restrictToContacts: (input.restrict_to_contacts as boolean) ?? false,
|
|
41
|
+
restrictToDomain: (input.restrict_to_domain as boolean) ?? false,
|
|
42
|
+
};
|
|
49
43
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
`Vacation responder enabled.\n${JSON.stringify(updated, null, 2)}`,
|
|
53
|
-
);
|
|
54
|
-
}
|
|
44
|
+
if (input.start_time) settings.startTime = String(input.start_time);
|
|
45
|
+
if (input.end_time) settings.endTime = String(input.end_time);
|
|
55
46
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
47
|
+
const updated = await updateVacation(connection, settings);
|
|
48
|
+
return ok(
|
|
49
|
+
`Vacation responder enabled.\n${JSON.stringify(updated, null, 2)}`,
|
|
50
|
+
);
|
|
51
|
+
}
|
|
60
52
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
);
|
|
53
|
+
case "disable": {
|
|
54
|
+
await updateVacation(connection, { enableAutoReply: false });
|
|
55
|
+
return ok("Vacation responder disabled.");
|
|
65
56
|
}
|
|
66
|
-
|
|
57
|
+
|
|
58
|
+
default:
|
|
59
|
+
return err(`Unknown action "${action}". Use get, enable, or disable.`);
|
|
60
|
+
}
|
|
67
61
|
} catch (e) {
|
|
68
62
|
return err(e instanceof Error ? e.message : String(e));
|
|
69
63
|
}
|