You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
returns exactly one hit, and it is an unrelated English word in a CLI prompt string (lib/connector-tool-prompt.ts:218, "Re-capture, wait, and resend."). There is no mail transport, no provider integration, and no Gmail send call.
The connector says so too — scopes: { read: true, write: false } (lib/connectors/connected-accounts.ts:91), which is currently accurate.
Scope
Declaration changes on GMAIL_CONNECTOR:
scopes.write → true. Required for the tool to be reachable at all: assertScope refuses a write-scoped tool on a write:false connector before any handler runs —
// lib/connectors/client.ts:84-87functionassertScope(opts?: TokenOpts): void{// A read-only connector (no write scope) can never satisfy a write request.if(opts?.scope==="write"&&!connector.scopes.write){
Flipping it is also what brings send under the #90 per-instance write-consent gate, so this makes send consent-gated, not ungated.
add https://www.googleapis.com/auth/gmail.send to oauth.scopes. gmail.send is sufficient to reply in-thread — pass threadId and set In-Reply-To/References. gmail.modify is NOT needed and must not be requested.
Tools (both scope: "write", mutates: true):
gmail_reply — reply to a message id, in its thread, with optional attachments taken from instance file ids
gmail_send — a new message to explicit recipients
Both build RFC-2822 MIME and POST base64url to /messages/send. Threading is the part to get right: correct In-Reply-To and References from the parent's Message-ID, plus threadId on the request body, or the reply lands as a detached message in the recipient's client.
The migration — this is the real work
Adding a scope invalidates nothing stored, but every already-connected user holds a refresh token granted under gmail.readonly alone. Google will mint access tokens from it happily; the send call then fails at the API with an insufficient-scopes 403. gmailErrorReason (lib/gmail.ts:185) already digs Google's real message out, which is why a 403 here says "insufficient scopes" instead of a bare status — that groundwork is done.
Required handling:
persist the granted scope set at connect time, so "can this connection send?" is answerable without making a failing API call
GET /v1/email/status reports canSend
gmail_reply/gmail_send refuse up front with "Gmail is connected but was authorised for reading only — reconnect Gmail to grant send access", never a raw 403
reconnecting must re-prompt Google for consent (prompt=consent) or the user silently gets the old grant back
Safety
Sending mail as the owner is among the most consequential things an agent on this platform can do. Non-negotiable:
permissions.email remains a hard precondition, as with every Gmail tool
Part of the Gmail epic. This is the one that needs a new OAuth scope, and therefore the one with a migration.
Why
PAGS cannot send an email by any route. Verified by exhaustion, not by reading one file:
returns exactly one hit, and it is an unrelated English word in a CLI prompt string (
lib/connector-tool-prompt.ts:218, "Re-capture, wait, and resend."). There is no mail transport, no provider integration, and no Gmail send call.The connector says so too —
scopes: { read: true, write: false }(lib/connectors/connected-accounts.ts:91), which is currently accurate.Scope
Declaration changes on
GMAIL_CONNECTOR:scopes.write→true. Required for the tool to be reachable at all:assertScoperefuses a write-scoped tool on awrite:falseconnector before any handler runs —Flipping it is also what brings send under the #90 per-instance write-consent gate, so this makes send consent-gated, not ungated.
https://www.googleapis.com/auth/gmail.sendtooauth.scopes.gmail.sendis sufficient to reply in-thread — passthreadIdand setIn-Reply-To/References.gmail.modifyis NOT needed and must not be requested.Tools (both
scope: "write",mutates: true):gmail_reply— reply to a message id, in its thread, with optional attachments taken from instance file idsgmail_send— a new message to explicit recipientsBoth build RFC-2822 MIME and POST base64url to
/messages/send. Threading is the part to get right: correctIn-Reply-ToandReferencesfrom the parent'sMessage-ID, plusthreadIdon the request body, or the reply lands as a detached message in the recipient's client.The migration — this is the real work
Adding a scope invalidates nothing stored, but every already-connected user holds a refresh token granted under
gmail.readonlyalone. Google will mint access tokens from it happily; the send call then fails at the API with an insufficient-scopes 403.gmailErrorReason(lib/gmail.ts:185) already digs Google's real message out, which is why a 403 here says "insufficient scopes" instead of a bare status — that groundwork is done.Required handling:
GET /v1/email/statusreportscanSendgmail_reply/gmail_sendrefuse up front with "Gmail is connected but was authorised for reading only — reconnect Gmail to grant send access", never a raw 403prompt=consent) or the user silently gets the old grant backSafety
Sending mail as the owner is among the most consequential things an agent on this platform can do. Non-negotiable:
permissions.emailremains a hard precondition, as with every Gmail toolgmailconnector must be granted for the instance ([connectors] Connector consent + write-scope safety + admin visibility #90) — this is what thescopes.writeflip enablesDone when