@vellumai/assistant 0.4.45 → 0.4.48
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 +6 -6
- package/docs/architecture/memory.md +1 -1
- package/docs/architecture/scheduling.md +2 -3
- package/docs/architecture/security.md +5 -5
- package/docs/trusted-contact-access.md +5 -6
- package/package.json +4 -1
- package/src/__tests__/avatar-e2e.test.ts +18 -219
- package/src/__tests__/avatar-generator.test.ts +5 -57
- package/src/__tests__/browser-fill-credential.test.ts +5 -2
- package/src/__tests__/bundled-skill-retrieval-guard.test.ts +2 -1
- package/src/__tests__/channel-readiness-routes.test.ts +20 -19
- package/src/__tests__/cli.test.ts +23 -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 +240 -18
- 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 +104 -7
- package/src/__tests__/credential-vault-unit.test.ts +22 -20
- package/src/__tests__/credential-vault.test.ts +284 -12
- package/src/__tests__/credentials-cli.test.ts +11 -6
- package/src/__tests__/gateway-only-enforcement.test.ts +4 -2
- 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-grant-minting.test.ts +35 -0
- package/src/__tests__/integration-status.test.ts +53 -21
- 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__/provider-fail-open-selection.test.ts +3 -1
- package/src/__tests__/provider-managed-proxy-integration.test.ts +70 -6
- package/src/__tests__/schedule-store.test.ts +1 -1
- package/src/__tests__/schema-transforms.test.ts +226 -0
- 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-onetime-send.test.ts +5 -3
- package/src/__tests__/session-messaging-secret-redirect.test.ts +5 -4
- package/src/__tests__/skills-uninstall.test.ts +2 -2
- package/src/__tests__/skills.test.ts +0 -9
- package/src/__tests__/slack-channel-config.test.ts +9 -8
- package/src/__tests__/slack-share-routes.test.ts +11 -6
- package/src/__tests__/telegram-bot-username-resolution.test.ts +3 -0
- 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 -4
- package/src/__tests__/verification-control-plane-policy.test.ts +1 -1
- package/src/approvals/AGENTS.md +1 -1
- package/src/calls/call-domain.ts +7 -4
- package/src/calls/twilio-config.ts +2 -1
- package/src/calls/twilio-provider.ts +2 -1
- package/src/calls/twilio-rest.ts +2 -2
- package/src/cli/commands/browser-relay.ts +40 -15
- package/src/cli/commands/credentials.ts +9 -8
- package/src/cli/commands/oauth.ts +1 -1
- package/src/cli.ts +3 -2
- package/src/config/bundled-skills/claude-code/TOOLS.json +0 -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 +70 -29
- 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 +11 -11
- package/src/config/bundled-skills/notifications/SKILL.md +1 -1
- package/src/config/bundled-skills/phone-calls/SKILL.md +5 -5
- package/src/config/bundled-skills/schedule/SKILL.md +1 -1
- package/src/config/bundled-skills/skill-management/SKILL.md +1 -1
- 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/loader.ts +6 -0
- package/src/daemon/computer-use-session.ts +7 -1
- package/src/daemon/guardian-action-generators.ts +4 -5
- package/src/daemon/handlers/config-slack-channel.ts +37 -20
- package/src/daemon/handlers/config-telegram.ts +33 -20
- package/src/daemon/lifecycle.ts +9 -1
- package/src/daemon/message-types/integrations.ts +1 -0
- package/src/daemon/ride-shotgun-handler.ts +3 -1
- package/src/daemon/session-messaging.ts +3 -1
- package/src/daemon/session-tool-setup.ts +18 -2
- package/src/daemon/session.ts +1 -1
- package/src/email/providers/index.ts +2 -1
- package/src/instrument.ts +15 -1
- package/src/media/app-icon-generator.ts +30 -4
- package/src/media/avatar-router.ts +28 -62
- package/src/media/gemini-image-service.ts +28 -2
- package/src/memory/canonical-guardian-store.ts +1 -1
- package/src/memory/guardian-action-store.ts +1 -1
- package/src/memory/schema/guardian.ts +1 -1
- package/src/messaging/provider.ts +16 -10
- package/src/messaging/providers/gmail/adapter.ts +40 -23
- package/src/messaging/providers/gmail/client.ts +203 -122
- package/src/messaging/providers/gmail/people-client.ts +26 -18
- 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 +5 -4
- package/src/messaging/providers/whatsapp/adapter.ts +6 -3
- package/src/messaging/registry.ts +2 -1
- package/src/oauth/byo-connection.test.ts +436 -0
- package/src/oauth/byo-connection.ts +112 -0
- package/src/oauth/connect-orchestrator.ts +27 -0
- package/src/oauth/connection-resolver.ts +34 -0
- package/src/oauth/connection.ts +38 -0
- package/src/oauth/platform-connection.test.ts +163 -0
- package/src/oauth/platform-connection.ts +110 -0
- package/src/oauth/provider-base-urls.ts +21 -0
- package/src/oauth/provider-profiles.ts +1 -1
- package/src/oauth/token-persistence.ts +20 -20
- package/src/permissions/checker.ts +6 -1
- package/src/prompts/system-prompt.ts +52 -15
- package/src/prompts/templates/BOOTSTRAP.md +1 -1
- 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/runtime/AGENTS.md +18 -1
- package/src/runtime/auth/route-policy.ts +1 -0
- 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/guardian-action-conversation-turn.ts +1 -3
- package/src/runtime/guardian-action-followup-executor.ts +1 -2
- package/src/runtime/guardian-action-message-composer.ts +3 -23
- package/src/runtime/http-server.ts +9 -4
- package/src/runtime/http-types.ts +0 -1
- package/src/runtime/middleware/rate-limiter.ts +74 -20
- package/src/runtime/middleware/twilio-validation.ts +1 -3
- package/src/runtime/routes/channel-readiness-routes.ts +2 -0
- package/src/runtime/routes/diagnostics-routes.ts +11 -9
- package/src/runtime/routes/guardian-approval-interception.ts +20 -5
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +71 -25
- package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +12 -5
- package/src/runtime/routes/integrations/slack/share.ts +3 -2
- package/src/runtime/routes/integrations/twilio.ts +6 -5
- package/src/runtime/routes/secret-routes.ts +3 -2
- package/src/runtime/routes/settings-routes.ts +75 -17
- package/src/runtime/telegram-streaming-delivery.test.ts +132 -0
- package/src/runtime/telegram-streaming-delivery.ts +11 -1
- package/src/schedule/integration-status.ts +5 -4
- package/src/security/credential-key.ts +170 -0
- package/src/security/token-manager.ts +36 -7
- 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/claude-code/claude-code.ts +0 -5
- package/src/tools/computer-use/request-computer-control.ts +0 -5
- package/src/tools/credentials/broker.ts +6 -4
- package/src/tools/credentials/metadata-store.ts +72 -20
- package/src/tools/credentials/resolve.ts +2 -1
- package/src/tools/credentials/vault.ts +77 -16
- 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/schema-transforms.ts +99 -0
- package/src/tools/skills/load.ts +0 -5
- package/src/tools/swarm/delegate.ts +0 -5
- package/src/tools/system/avatar-generator.ts +3 -44
- package/src/tools/ui-surface/definitions.ts +0 -15
- package/src/tools/watch/screen-watch.ts +0 -5
- 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 +93 -86
- package/src/watcher/providers/linear.ts +87 -93
- package/src/__tests__/avatar-router.test.ts +0 -149
- package/src/__tests__/managed-avatar-client.test.ts +0 -337
- package/src/config/bundled-skills/doordash/SKILL.md +0 -170
- package/src/config/bundled-skills/doordash/__tests__/doordash-client.test.ts +0 -205
- package/src/config/bundled-skills/doordash/__tests__/doordash-session.test.ts +0 -74
- package/src/config/bundled-skills/doordash/doordash-cli.ts +0 -1081
- package/src/config/bundled-skills/doordash/doordash-entry.ts +0 -22
- package/src/config/bundled-skills/doordash/lib/cart-queries.ts +0 -787
- package/src/config/bundled-skills/doordash/lib/client.ts +0 -1069
- package/src/config/bundled-skills/doordash/lib/order-queries.ts +0 -85
- package/src/config/bundled-skills/doordash/lib/queries.ts +0 -28
- package/src/config/bundled-skills/doordash/lib/query-extractor.ts +0 -94
- package/src/config/bundled-skills/doordash/lib/search-queries.ts +0 -203
- package/src/config/bundled-skills/doordash/lib/session.ts +0 -96
- package/src/config/bundled-skills/doordash/lib/shared/errors.ts +0 -61
- package/src/config/bundled-skills/doordash/lib/shared/network-recorder.ts +0 -380
- package/src/config/bundled-skills/doordash/lib/shared/platform.ts +0 -55
- package/src/config/bundled-skills/doordash/lib/shared/recording-store.ts +0 -43
- package/src/config/bundled-skills/doordash/lib/shared/recording-types.ts +0 -49
- package/src/config/bundled-skills/doordash/lib/shared/truncate.ts +0 -6
- package/src/config/bundled-skills/doordash/lib/store-queries.ts +0 -246
- package/src/config/bundled-skills/doordash/lib/types.ts +0 -367
- package/src/media/avatar-types.ts +0 -53
- package/src/media/managed-avatar-client.ts +0 -225
|
@@ -3,8 +3,7 @@ import {
|
|
|
3
3
|
searchContacts,
|
|
4
4
|
} from "../../../../messaging/providers/gmail/people-client.js";
|
|
5
5
|
import type { Person } from "../../../../messaging/providers/gmail/people-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,
|
|
@@ -44,43 +43,41 @@ export async function run(
|
|
|
44
43
|
}
|
|
45
44
|
|
|
46
45
|
try {
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
const pageToken = input.page_token as string | undefined;
|
|
46
|
+
const connection = resolveOAuthConnection("integration:gmail");
|
|
47
|
+
switch (action) {
|
|
48
|
+
case "list": {
|
|
49
|
+
const pageSize = (input.page_size as number) ?? 50;
|
|
50
|
+
const pageToken = input.page_token as string | undefined;
|
|
53
51
|
|
|
54
|
-
|
|
55
|
-
|
|
52
|
+
const resp = await listContacts(connection, pageSize, pageToken);
|
|
53
|
+
const contacts = (resp.connections ?? []).map(formatContact);
|
|
56
54
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
55
|
+
const result: Record<string, unknown> = {
|
|
56
|
+
contacts,
|
|
57
|
+
total: resp.totalPeople ?? contacts.length,
|
|
58
|
+
};
|
|
59
|
+
if (resp.nextPageToken) result.nextPageToken = resp.nextPageToken;
|
|
62
60
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
case "search": {
|
|
67
|
-
const query = input.query as string;
|
|
68
|
-
if (!query) return err("query is required for search action.");
|
|
61
|
+
return ok(JSON.stringify(result, null, 2));
|
|
62
|
+
}
|
|
69
63
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
);
|
|
64
|
+
case "search": {
|
|
65
|
+
const query = input.query as string;
|
|
66
|
+
if (!query) return err("query is required for search action.");
|
|
74
67
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
)
|
|
78
|
-
|
|
68
|
+
const resp = await searchContacts(connection, query);
|
|
69
|
+
const contacts = (resp.results ?? []).map((r) =>
|
|
70
|
+
formatContact(r.person),
|
|
71
|
+
);
|
|
79
72
|
|
|
80
|
-
|
|
81
|
-
|
|
73
|
+
return ok(
|
|
74
|
+
JSON.stringify({ contacts, total: contacts.length }, null, 2),
|
|
75
|
+
);
|
|
82
76
|
}
|
|
83
|
-
|
|
77
|
+
|
|
78
|
+
default:
|
|
79
|
+
return err(`Unknown action "${action}". Use list or search.`);
|
|
80
|
+
}
|
|
84
81
|
} catch (e) {
|
|
85
82
|
return err(e instanceof Error ? e.message : String(e));
|
|
86
83
|
}
|
|
@@ -30,11 +30,11 @@ Do not offer AgentMail as an option or mention it unless the user specifically a
|
|
|
30
30
|
### Gmail
|
|
31
31
|
|
|
32
32
|
1. **Try connecting directly first.** Call `credential_store` with `action: "oauth2_connect"` and `service: "gmail"`. The tool auto-fills Google's OAuth endpoints and looks up any previously stored client credentials — so this single call may be all that's needed.
|
|
33
|
-
2. **If it fails because no client_id is found:** The user needs to create Google Cloud OAuth credentials first. Load the **google-oauth-
|
|
34
|
-
- Call `skill_load` with `skill: "google-oauth-
|
|
33
|
+
2. **If it fails because no client_id is found:** The user needs to create Google Cloud OAuth credentials first. Load the **google-oauth-applescript** skill:
|
|
34
|
+
- Call `skill_load` with `skill: "google-oauth-applescript"` to load the dependency skill.
|
|
35
35
|
- Tell the user Gmail isn't connected yet and briefly explain what the setup involves, then use `ui_show` with `surface_type: "confirmation"` to ask for permission to start:
|
|
36
36
|
- **message:** "Ready to set up Gmail?"
|
|
37
|
-
- **detail:** "I'll open a browser
|
|
37
|
+
- **detail:** "I'll open a few pages in your browser and walk you through setting up Google Cloud credentials — creating a project, enabling APIs, and connecting your account. Takes about 5 minutes."
|
|
38
38
|
- **confirmLabel:** "Get Started"
|
|
39
39
|
- **cancelLabel:** "Not Now"
|
|
40
40
|
- If the user confirms, briefly acknowledge (e.g., "Setting up Gmail now...") and proceed with the setup guide. If they decline, acknowledge and let them know they can set it up later.
|
|
@@ -45,7 +45,7 @@ Do not offer AgentMail as an option or mention it unless the user specifically a
|
|
|
45
45
|
When a Gmail tool fails with a token or authorization error:
|
|
46
46
|
|
|
47
47
|
1. **Try to reconnect silently.** Call `credential_store` with `action: "oauth2_connect"` and `service: "gmail"`. This often resolves expired tokens automatically.
|
|
48
|
-
2. **If reconnection fails, go straight to setup.** Don't present options, ask which route the user prefers, or explain what went wrong technically. Just tell the user briefly (e.g., "Gmail needs to be reconnected — let me set that up") and immediately follow the connection setup flow for Gmail (e.g., install and load **google-oauth-
|
|
48
|
+
2. **If reconnection fails, go straight to setup.** Don't present options, ask which route the user prefers, or explain what went wrong technically. Just tell the user briefly (e.g., "Gmail needs to be reconnected — let me set that up") and immediately follow the connection setup flow for Gmail (e.g., install and load **google-oauth-applescript**). The user came to you to get something done, not to troubleshoot OAuth — make it seamless.
|
|
49
49
|
3. **Never try alternative approaches.** Don't use bash, curl, browser automation, or any workaround. If the Gmail tools can't do it, the reconnection flow is the answer.
|
|
50
50
|
4. **Never expose error details.** The user doesn't need to see error messages about tokens, OAuth, or API failures. Translate errors into plain language.
|
|
51
51
|
|
|
@@ -3,8 +3,7 @@ import {
|
|
|
3
3
|
listMessages,
|
|
4
4
|
modifyMessage,
|
|
5
5
|
} from "../../../../messaging/providers/gmail/client.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,
|
|
@@ -35,49 +34,47 @@ export async function run(
|
|
|
35
34
|
}
|
|
36
35
|
|
|
37
36
|
try {
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
let truncated = false;
|
|
37
|
+
const connection = resolveOAuthConnection("integration:gmail");
|
|
38
|
+
const allMessageIds: string[] = [];
|
|
39
|
+
let pageToken: string | undefined;
|
|
40
|
+
let truncated = false;
|
|
43
41
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
42
|
+
while (allMessageIds.length < MAX_MESSAGES) {
|
|
43
|
+
const listResp = await listMessages(
|
|
44
|
+
connection,
|
|
45
|
+
query,
|
|
46
|
+
Math.min(500, MAX_MESSAGES - allMessageIds.length),
|
|
47
|
+
pageToken,
|
|
48
|
+
);
|
|
49
|
+
const ids = (listResp.messages ?? []).map((m) => m.id);
|
|
50
|
+
if (ids.length === 0) break;
|
|
51
|
+
allMessageIds.push(...ids);
|
|
52
|
+
pageToken = listResp.nextPageToken ?? undefined;
|
|
53
|
+
if (!pageToken) break;
|
|
54
|
+
}
|
|
57
55
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
56
|
+
if (allMessageIds.length >= MAX_MESSAGES && pageToken) {
|
|
57
|
+
truncated = true;
|
|
58
|
+
}
|
|
61
59
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
60
|
+
if (allMessageIds.length === 0) {
|
|
61
|
+
return ok("No messages matched the query. Nothing archived.");
|
|
62
|
+
}
|
|
65
63
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
64
|
+
for (let i = 0; i < allMessageIds.length; i += BATCH_MODIFY_LIMIT) {
|
|
65
|
+
const chunk = allMessageIds.slice(i, i + BATCH_MODIFY_LIMIT);
|
|
66
|
+
await batchModifyMessages(connection, chunk, {
|
|
67
|
+
removeLabelIds: ["INBOX"],
|
|
68
|
+
});
|
|
69
|
+
}
|
|
72
70
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
});
|
|
71
|
+
const summary = `Archived ${allMessageIds.length} message(s) matching query: ${query}`;
|
|
72
|
+
if (truncated) {
|
|
73
|
+
return ok(
|
|
74
|
+
`${summary}\n\nNote: this operation was capped at ${MAX_MESSAGES} messages. Additional messages matching the query may remain in the inbox. Run the command again to archive more.`,
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
return ok(summary);
|
|
81
78
|
} catch (e) {
|
|
82
79
|
return err(e instanceof Error ? e.message : String(e));
|
|
83
80
|
}
|
|
@@ -106,11 +103,9 @@ export async function run(
|
|
|
106
103
|
} else if (messageId) {
|
|
107
104
|
// Single message path
|
|
108
105
|
try {
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
return ok("Message archived.");
|
|
113
|
-
});
|
|
106
|
+
const connection = resolveOAuthConnection("integration:gmail");
|
|
107
|
+
await modifyMessage(connection, messageId, { removeLabelIds: ["INBOX"] });
|
|
108
|
+
return ok("Message archived.");
|
|
114
109
|
} catch (e) {
|
|
115
110
|
return err(e instanceof Error ? e.message : String(e));
|
|
116
111
|
}
|
|
@@ -126,23 +121,21 @@ export async function run(
|
|
|
126
121
|
}
|
|
127
122
|
|
|
128
123
|
try {
|
|
129
|
-
const
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
}
|
|
124
|
+
const connection = resolveOAuthConnection("integration:gmail");
|
|
125
|
+
if (messageIds.length === 1) {
|
|
126
|
+
await modifyMessage(connection, messageIds[0], {
|
|
127
|
+
removeLabelIds: ["INBOX"],
|
|
128
|
+
});
|
|
129
|
+
return ok("Message archived.");
|
|
130
|
+
}
|
|
137
131
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
});
|
|
132
|
+
for (let i = 0; i < messageIds.length; i += BATCH_MODIFY_LIMIT) {
|
|
133
|
+
const chunk = messageIds.slice(i, i + BATCH_MODIFY_LIMIT);
|
|
134
|
+
await batchModifyMessages(connection, chunk, {
|
|
135
|
+
removeLabelIds: ["INBOX"],
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
return ok(`Archived ${messageIds.length} message(s).`);
|
|
146
139
|
} catch (e) {
|
|
147
140
|
return err(e instanceof Error ? e.message : String(e));
|
|
148
141
|
}
|
|
@@ -6,8 +6,7 @@ import {
|
|
|
6
6
|
getMessage,
|
|
7
7
|
} from "../../../../messaging/providers/gmail/client.js";
|
|
8
8
|
import type { GmailMessagePart } from "../../../../messaging/providers/gmail/types.js";
|
|
9
|
-
import {
|
|
10
|
-
import { withValidToken } from "../../../../security/token-manager.js";
|
|
9
|
+
import { resolveOAuthConnection } from "../../../../oauth/connection-resolver.js";
|
|
11
10
|
import type {
|
|
12
11
|
ToolContext,
|
|
13
12
|
ToolExecutionResult,
|
|
@@ -57,17 +56,15 @@ export async function run(
|
|
|
57
56
|
|
|
58
57
|
if (action === "list") {
|
|
59
58
|
try {
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
const attachments = collectAttachments(message.payload?.parts);
|
|
59
|
+
const connection = resolveOAuthConnection("integration:gmail");
|
|
60
|
+
const message = await getMessage(connection, messageId, "full");
|
|
61
|
+
const attachments = collectAttachments(message.payload?.parts);
|
|
64
62
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
63
|
+
if (attachments.length === 0) {
|
|
64
|
+
return ok("No attachments found on this message.");
|
|
65
|
+
}
|
|
68
66
|
|
|
69
|
-
|
|
70
|
-
});
|
|
67
|
+
return ok(JSON.stringify(attachments, null, 2));
|
|
71
68
|
} catch (e) {
|
|
72
69
|
return err(e instanceof Error ? e.message : String(e));
|
|
73
70
|
}
|
|
@@ -81,26 +78,26 @@ export async function run(
|
|
|
81
78
|
if (!filename) return err("filename is required for download.");
|
|
82
79
|
|
|
83
80
|
try {
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
|
|
81
|
+
const connection = resolveOAuthConnection("integration:gmail");
|
|
82
|
+
const attachment = await getAttachment(
|
|
83
|
+
connection,
|
|
84
|
+
messageId,
|
|
85
|
+
attachmentId,
|
|
86
|
+
);
|
|
87
87
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
88
|
+
// Gmail returns base64url; convert to standard base64 then to Buffer
|
|
89
|
+
const base64 = attachment.data.replace(/-/g, "+").replace(/_/g, "/");
|
|
90
|
+
const buffer = Buffer.from(base64, "base64");
|
|
91
91
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
92
|
+
const outputDir = context.workingDir ?? process.cwd();
|
|
93
|
+
// Sanitize filename: strip path separators to prevent traversal attacks from crafted MIME filenames
|
|
94
|
+
const safeName = basename(filename).replace(/\.\./g, "_");
|
|
95
|
+
const outputPath = resolve(outputDir, safeName);
|
|
96
|
+
if (!outputPath.startsWith(outputDir))
|
|
97
|
+
return err("Invalid filename: path traversal detected.");
|
|
98
|
+
await writeFile(outputPath, buffer);
|
|
99
99
|
|
|
100
|
-
|
|
101
|
-
`Attachment saved to ${outputPath} (${buffer.length} bytes).`,
|
|
102
|
-
);
|
|
103
|
-
});
|
|
100
|
+
return ok(`Attachment saved to ${outputPath} (${buffer.length} bytes).`);
|
|
104
101
|
} catch (e) {
|
|
105
102
|
return err(e instanceof Error ? e.message : String(e));
|
|
106
103
|
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { createDraft } 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,
|
|
@@ -23,21 +22,19 @@ export async function run(
|
|
|
23
22
|
if (!body) return err("body is required.");
|
|
24
23
|
|
|
25
24
|
try {
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
);
|
|
40
|
-
});
|
|
25
|
+
const connection = resolveOAuthConnection("integration:gmail");
|
|
26
|
+
const draft = await createDraft(
|
|
27
|
+
connection,
|
|
28
|
+
to,
|
|
29
|
+
subject,
|
|
30
|
+
body,
|
|
31
|
+
inReplyTo,
|
|
32
|
+
cc,
|
|
33
|
+
bcc,
|
|
34
|
+
);
|
|
35
|
+
return ok(
|
|
36
|
+
`Draft created (ID: ${draft.id}). It will appear in your Gmail Drafts.`,
|
|
37
|
+
);
|
|
41
38
|
} catch (e) {
|
|
42
39
|
return err(e instanceof Error ? e.message : String(e));
|
|
43
40
|
}
|
|
@@ -7,8 +7,7 @@ import type {
|
|
|
7
7
|
GmailFilterAction,
|
|
8
8
|
GmailFilterCriteria,
|
|
9
9
|
} from "../../../../messaging/providers/gmail/types.js";
|
|
10
|
-
import {
|
|
11
|
-
import { withValidToken } from "../../../../security/token-manager.js";
|
|
10
|
+
import { resolveOAuthConnection } from "../../../../oauth/connection-resolver.js";
|
|
12
11
|
import type {
|
|
13
12
|
ToolContext,
|
|
14
13
|
ToolExecutionResult,
|
|
@@ -26,57 +25,53 @@ export async function run(
|
|
|
26
25
|
}
|
|
27
26
|
|
|
28
27
|
try {
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
return ok("No filters configured.");
|
|
36
|
-
}
|
|
37
|
-
return ok(JSON.stringify(filters, null, 2));
|
|
28
|
+
const connection = resolveOAuthConnection("integration:gmail");
|
|
29
|
+
switch (action) {
|
|
30
|
+
case "list": {
|
|
31
|
+
const filters = await listFilters(connection);
|
|
32
|
+
if (filters.length === 0) {
|
|
33
|
+
return ok("No filters configured.");
|
|
38
34
|
}
|
|
35
|
+
return ok(JSON.stringify(filters, null, 2));
|
|
36
|
+
}
|
|
39
37
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
const filterAction: GmailFilterAction = {};
|
|
50
|
-
if (input.add_label_ids)
|
|
51
|
-
filterAction.addLabelIds = input.add_label_ids as string[];
|
|
52
|
-
if (input.remove_label_ids)
|
|
53
|
-
filterAction.removeLabelIds = input.remove_label_ids as string[];
|
|
54
|
-
if (input.forward) filterAction.forward = input.forward as string;
|
|
38
|
+
case "create": {
|
|
39
|
+
const criteria: GmailFilterCriteria = {};
|
|
40
|
+
if (input.from) criteria.from = input.from as string;
|
|
41
|
+
if (input.to) criteria.to = input.to as string;
|
|
42
|
+
if (input.subject) criteria.subject = input.subject as string;
|
|
43
|
+
if (input.query) criteria.query = input.query as string;
|
|
44
|
+
if (input.has_attachment !== undefined)
|
|
45
|
+
criteria.hasAttachment = input.has_attachment as boolean;
|
|
55
46
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
47
|
+
const filterAction: GmailFilterAction = {};
|
|
48
|
+
if (input.add_label_ids)
|
|
49
|
+
filterAction.addLabelIds = input.add_label_ids as string[];
|
|
50
|
+
if (input.remove_label_ids)
|
|
51
|
+
filterAction.removeLabelIds = input.remove_label_ids as string[];
|
|
52
|
+
if (input.forward) filterAction.forward = input.forward as string;
|
|
61
53
|
|
|
62
|
-
|
|
63
|
-
return
|
|
54
|
+
if (Object.keys(criteria).length === 0) {
|
|
55
|
+
return err(
|
|
56
|
+
"At least one filter criteria is required (from, to, subject, query, or has_attachment).",
|
|
57
|
+
);
|
|
64
58
|
}
|
|
65
59
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
60
|
+
const filter = await createFilter(connection, criteria, filterAction);
|
|
61
|
+
return ok(`Filter created (ID: ${filter.id}).`);
|
|
62
|
+
}
|
|
69
63
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
64
|
+
case "delete": {
|
|
65
|
+
const filterId = input.filter_id as string;
|
|
66
|
+
if (!filterId) return err("filter_id is required for delete action.");
|
|
73
67
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
`Unknown action "${action}". Use list, create, or delete.`,
|
|
77
|
-
);
|
|
68
|
+
await deleteFilter(connection, filterId);
|
|
69
|
+
return ok("Filter deleted.");
|
|
78
70
|
}
|
|
79
|
-
|
|
71
|
+
|
|
72
|
+
default:
|
|
73
|
+
return err(`Unknown action "${action}". Use list, create, or delete.`);
|
|
74
|
+
}
|
|
80
75
|
} catch (e) {
|
|
81
76
|
return err(e instanceof Error ? e.message : String(e));
|
|
82
77
|
}
|
|
@@ -5,8 +5,8 @@ import {
|
|
|
5
5
|
listMessages,
|
|
6
6
|
modifyMessage,
|
|
7
7
|
} from "../../../../messaging/providers/gmail/client.js";
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
8
|
+
import type { OAuthConnection } from "../../../../oauth/connection.js";
|
|
9
|
+
import { resolveOAuthConnection } from "../../../../oauth/connection-resolver.js";
|
|
10
10
|
import type {
|
|
11
11
|
ToolContext,
|
|
12
12
|
ToolExecutionResult,
|
|
@@ -15,12 +15,14 @@ import { err, ok } from "./shared.js";
|
|
|
15
15
|
|
|
16
16
|
const FOLLOW_UP_LABEL_NAME = "Follow-up";
|
|
17
17
|
|
|
18
|
-
async function getOrCreateFollowUpLabel(
|
|
19
|
-
|
|
18
|
+
async function getOrCreateFollowUpLabel(
|
|
19
|
+
connection: OAuthConnection,
|
|
20
|
+
): Promise<string> {
|
|
21
|
+
const labels = await listLabels(connection);
|
|
20
22
|
const existing = labels.find((l) => l.name === FOLLOW_UP_LABEL_NAME);
|
|
21
23
|
if (existing) return existing.id;
|
|
22
24
|
|
|
23
|
-
const created = await createLabel(
|
|
25
|
+
const created = await createLabel(connection, FOLLOW_UP_LABEL_NAME);
|
|
24
26
|
return created.id;
|
|
25
27
|
}
|
|
26
28
|
|
|
@@ -35,67 +37,68 @@ export async function run(
|
|
|
35
37
|
}
|
|
36
38
|
|
|
37
39
|
try {
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
if (!messageId)
|
|
44
|
-
return err("message_id is required for track action.");
|
|
40
|
+
const connection = resolveOAuthConnection("integration:gmail");
|
|
41
|
+
switch (action) {
|
|
42
|
+
case "track": {
|
|
43
|
+
const messageId = input.message_id as string;
|
|
44
|
+
if (!messageId) return err("message_id is required for track action.");
|
|
45
45
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
case "list": {
|
|
52
|
-
const labelId = await getOrCreateFollowUpLabel(token);
|
|
53
|
-
const listResp = await listMessages(token, undefined, 50, undefined, [
|
|
54
|
-
labelId,
|
|
55
|
-
]);
|
|
56
|
-
const messageIds = (listResp.messages ?? []).map((m) => m.id);
|
|
57
|
-
|
|
58
|
-
if (messageIds.length === 0) {
|
|
59
|
-
return ok("No messages are currently tracked for follow-up.");
|
|
60
|
-
}
|
|
46
|
+
const labelId = await getOrCreateFollowUpLabel(connection);
|
|
47
|
+
await modifyMessage(connection, messageId, { addLabelIds: [labelId] });
|
|
48
|
+
return ok("Message marked for follow-up.");
|
|
49
|
+
}
|
|
61
50
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
const subject =
|
|
73
|
-
headers.find((h) => h.name.toLowerCase() === "subject")?.value ??
|
|
74
|
-
"";
|
|
75
|
-
const date =
|
|
76
|
-
headers.find((h) => h.name.toLowerCase() === "date")?.value ?? "";
|
|
77
|
-
return { id: m.id, threadId: m.threadId, from, subject, date };
|
|
78
|
-
});
|
|
51
|
+
case "list": {
|
|
52
|
+
const labelId = await getOrCreateFollowUpLabel(connection);
|
|
53
|
+
const listResp = await listMessages(
|
|
54
|
+
connection,
|
|
55
|
+
undefined,
|
|
56
|
+
50,
|
|
57
|
+
undefined,
|
|
58
|
+
[labelId],
|
|
59
|
+
);
|
|
60
|
+
const messageIds = (listResp.messages ?? []).map((m) => m.id);
|
|
79
61
|
|
|
80
|
-
|
|
62
|
+
if (messageIds.length === 0) {
|
|
63
|
+
return ok("No messages are currently tracked for follow-up.");
|
|
81
64
|
}
|
|
82
65
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
66
|
+
const messages = await batchGetMessages(
|
|
67
|
+
connection,
|
|
68
|
+
messageIds,
|
|
69
|
+
"metadata",
|
|
70
|
+
["From", "Subject", "Date"],
|
|
71
|
+
);
|
|
72
|
+
const items = messages.map((m) => {
|
|
73
|
+
const headers = m.payload?.headers ?? [];
|
|
74
|
+
const from =
|
|
75
|
+
headers.find((h) => h.name.toLowerCase() === "from")?.value ?? "";
|
|
76
|
+
const subject =
|
|
77
|
+
headers.find((h) => h.name.toLowerCase() === "subject")?.value ??
|
|
78
|
+
"";
|
|
79
|
+
const date =
|
|
80
|
+
headers.find((h) => h.name.toLowerCase() === "date")?.value ?? "";
|
|
81
|
+
return { id: m.id, threadId: m.threadId, from, subject, date };
|
|
82
|
+
});
|
|
87
83
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
return ok("Follow-up tracking removed from message.");
|
|
91
|
-
}
|
|
84
|
+
return ok(JSON.stringify(items, null, 2));
|
|
85
|
+
}
|
|
92
86
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
);
|
|
87
|
+
case "untrack": {
|
|
88
|
+
const messageId = input.message_id as string;
|
|
89
|
+
if (!messageId)
|
|
90
|
+
return err("message_id is required for untrack action.");
|
|
91
|
+
|
|
92
|
+
const labelId = await getOrCreateFollowUpLabel(connection);
|
|
93
|
+
await modifyMessage(connection, messageId, {
|
|
94
|
+
removeLabelIds: [labelId],
|
|
95
|
+
});
|
|
96
|
+
return ok("Follow-up tracking removed from message.");
|
|
97
97
|
}
|
|
98
|
-
|
|
98
|
+
|
|
99
|
+
default:
|
|
100
|
+
return err(`Unknown action "${action}". Use track, list, or untrack.`);
|
|
101
|
+
}
|
|
99
102
|
} catch (e) {
|
|
100
103
|
return err(e instanceof Error ? e.message : String(e));
|
|
101
104
|
}
|