feat(nodes,vscode): add Gmail tool node + Google user-OAuth broker (RR-1055, RR-1142)#1334
feat(nodes,vscode): add Gmail tool node + Google user-OAuth broker (RR-1055, RR-1142)#1334joshuadarron wants to merge 10 commits into
Conversation
… (RR-1055) Extends the GMAIL spec so the Gmail node can support permanent delete: a 'full' tier granting https://mail.google.com/ and the allowHardDelete flag that gates it. can_write stays scope-derived. Prereq for tool_gmail. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Full Gmail v1 surface as agent tools, modeled on tool_github and gated by the shared GMAIL resolver: - Reads/sync: message_list/get/search, thread_get/list, label_list, draft_list/get, attachment_get, history_list. - Organize (write tier): message_modify, message_batch_modify, label_apply/ remove/create/update/delete. Read-state via the UNREAD label. - Write: message_send (reply-in-thread sets In-Reply-To/References), message_trash/untrash, draft_create/update/send/delete. Send requires the send or full tier. - Permanent delete (gated): message_delete, messages_batchDelete — require allowHardDelete + the full tier; explicit id lists only, capped at 1000. Auth branches on google.authType (service account w/ domain delegation, or user OAuth). Outputs are cleaned shapes, not raw API JSON. Adds a Google SDK mock for ROCKETRIDE_MOCK and unit tests covering each function, gating, reply threading, batch caps, and the services.json field contract. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…-1055) The Google/Microsoft/Slack login button widgets rendered the raw key (e.g. ADDSOURCE.FORMSTEP.LOGINWITHGOOGLEBUTTON) — uppercased by MUI and overflowing — when the addSource.* namespace isn't loaded (as in the VS Code config panel, where the Gmail node first surfaced it). Add a defaultValue to each t() so the label is correct regardless of i18n state, and set textTransform:'none' so it renders in sentence case without clipping. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…nset
defaultValue is ignored because no i18next instance is initialized in the
webview hosts — t() returns the raw key. Detect the key echo and substitute a
literal ('Login with Google'/Microsoft/Slack, 'Authenticated') so the buttons
never render a raw i18n key. Supersedes the prior defaultValue attempt.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Make the shared "Login with Google" widget functional in the VS Code extension. OAuth is brokered by a RocketRide-hosted function (separate infra), so the engine holds no client secret and self-hosters need no Google app of their own. - shared-ui: OAUTH_ROOT_URL build-time constant (config/oauth.ts); thread oauthReturnUrl / onOpenExternal / pendingOAuthTokens through the flow context; button opens the system browser instead of an iframe (Google blocks framing); useOAuthCallbacks token-merge core reused for both the URL and message return paths. - vscode: send oauthReturnUrl in project:load; project:openExternal opens the broker in the system browser and arms a one-shot token callback; CloudAuthProvider routes the vscode://.../auth/google deep link; ProjectWebview delivers tokens to the open node-config panel. - rsbuild: define REACT_APP_OAUTH_ROOT_URL for the webview bundle. Deferred: gmail_client.py refresh mapping (lives on RR-1055 branch). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…tended tiers - Expand tool_gmail from 26 to 61 tools: add filters, settings (IMAP/POP/ vacation/forwarding/delegates/send-as/S/MIME), push notifications, HTML send with attachments, thread-level convenience ops, check_connection. - Add settings / settings_sharing access tiers and allowHardDelete gate to the shared Google scope resolver. - Add broker-aware token refresh path in gmail_client.py. - Add check_connection tool so the agent can diagnose OAuth scope gaps before hitting a 403 at API call time. - Fix LoginWithGoogleButton to pass an explicit scope param to the OAuth broker for extended Gmail tiers (send, settings, settings_sharing, full), ensuring the resulting token covers the required Google scopes. - 72 unit tests (was 20). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… JSON envelope
The LLM sometimes outputs conversational text before the required JSON object
(e.g. "I'd be happy to help! {\"type\":\"tool_call\"...}"). The previous
raw_decode call started from index 0 and failed immediately on non-JSON text.
Fix: scan for the first '{' and pass that position as the idx argument to
raw_decode, so preamble text and markdown code fences are skipped rather
than causing a parse failure. Add 12 unit tests covering clean input, preamble
text, markdown fences, trailing text, and all failure paths.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (3)
📝 WalkthroughWalkthroughAdds a new ChangesVSCode Google OAuth Deep-Link + Shared-UI Prop Threading
Gmail Tool Node (Python backend)
LangChain Envelope Parser Fix
Sequence Diagram(s)sequenceDiagram
participant User
participant LoginWithGoogleButton
participant ProjectWebview
participant ProjectProvider
participant CloudAuthProvider
participant SystemBrowser
User->>LoginWithGoogleButton: Click "Login with Google"
LoginWithGoogleButton->>ProjectWebview: postMessage project:openExternal { url }
ProjectWebview->>ProjectProvider: project:openExternal { url }
ProjectProvider->>CloudAuthProvider: setPendingGoogleOAuth(callback)
ProjectProvider->>SystemBrowser: vscode.env.openExternal(url)
SystemBrowser->>CloudAuthProvider: deep-link /auth/google?tokens=…&state=…
CloudAuthProvider->>ProjectProvider: callback(tokens, state)
ProjectProvider->>ProjectWebview: postMessage project:oauthTokens { tokens, state }
ProjectWebview->>NodeConfigPanel: pendingOAuthTokens updated via context
NodeConfigPanel->>NodeConfigPanel: applyGoogleTokens → persistOAuthTokensAndSave → clearPendingOAuthTokens
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Possibly related issues
Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
🤖 Internal: Discord sync markerAuto-managed by the Discord notification workflow. Stores the linked Discord message ID. Do not edit or delete. |
There was a problem hiding this comment.
Actionable comments posted: 12
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/shared-ui/src/components/canvas/components/panels/node-config/useOAuthCallbacks.ts (1)
45-54:⚠️ Potential issue | 🟠 Major | ⚡ Quick winParse failure continues with invalid token data.
If
JSON.parse(tokensParam)throws, the function continues buildingfullTokenObjectwith undefined values (sincetokensremains{}), resulting in auserTokenJsonlike{"access_token":undefined,...}being stored. This could corrupt the node config.Return the original formData on parse error to prevent storing invalid data.
🐛 Proposed fix
try { if (stateParam) { parsedState = JSON.parse(stateParam); service = JSON.parse((parsedState?.service as string) ?? '{}'); authType = (parsedState?.type as string) || ''; } tokens = JSON.parse(tokensParam); } catch (error) { console.error('Error parsing Google OAuth callback data:', error); + return formData; }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/shared-ui/src/components/canvas/components/panels/node-config/useOAuthCallbacks.ts` around lines 45 - 54, When JSON.parse(tokensParam) throws an error in the try-catch block, the function continues executing with tokens remaining as an empty object, causing the fullTokenObject to be populated with undefined values that get stored in the node config. Modify the catch block to return the original formData immediately when a parse error occurs, preventing the function from proceeding with invalid data. This ensures that if either stateParam or tokensParam cannot be parsed, the original formData is returned without creating a corrupted fullTokenObject with undefined token fields.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@apps/vscode/src/auth/CloudAuthProvider.ts`:
- Around line 46-47: Replace the single global `pendingGoogleOAuth` callback
slot in the CloudAuthProvider class with a state-keyed map to handle multiple
concurrent OAuth attempts safely. Instead of storing one callback function as
`pendingGoogleOAuth`, create a Map or object that keys callbacks by their OAuth
state parameter. Update the callback assignment logic (in the area around lines
106-131) to store the callback keyed by state, and update the callback
invocation to look up and retrieve the correct callback using the returned state
parameter. This ensures each OAuth flow maintains its own callback and prevents
token misrouting when multiple login attempts happen concurrently.
In `@apps/vscode/src/providers/ProjectProvider.ts`:
- Around line 480-485: The code sets a pending OAuth callback via
CloudAuthProvider.getInstance().setPendingGoogleOAuth() but does not handle
failures from the vscode.env.openExternal call. If openExternal throws an error,
the registered callback remains pending and could be triggered by an unrelated
deep-link response later. Wrap the openExternal call in a try-catch block, and
in the catch handler, clear the pending OAuth callback by calling
CloudAuthProvider.getInstance().setPendingGoogleOAuth with a null or cleared
state to prevent the stale callback from consuming future responses. Also handle
the error appropriately such as logging it or notifying the user.
- Around line 372-373: Add documentation in the apps/vscode/docs/ directory
describing the new host↔webview contract changes. Document the new
oauthReturnUrl field and the new project:openExternal and project:oauthTokens
messages, explaining their purpose and how they facilitate OAuth token exchange
between the host and webview. This documentation is required by coding
guidelines whenever extension contract modifications are made.
In `@apps/vscode/src/providers/views/Project/ProjectWebview.tsx`:
- Around line 98-104: In the project:load case handler, unconditionally set
oauthReturnUrl to either the incoming value or a cleared/reset state instead of
only updating when msg.oauthReturnUrl is truthy, which leaves stale values from
previous loads. Additionally, in the same project:load case, add a call to
setPendingOAuthTokens to reset it to an initial cleared state before accepting
new OAuth events, preventing stale token data from persisting across project
loads.
In `@nodes/src/nodes/core/google_access.py`:
- Around line 131-143: The GMAIL AccessSpec has an empty flags tuple which
prevents resolve_google_access(...) from exposing an allowHardDelete gate value,
making hard-delete behavior uncontrollable via an explicit opt-in kill switch.
Populate the flags parameter in the GMAIL AccessSpec (currently set to an empty
tuple) with the appropriate flag(s) that correspond to hard-delete operations to
enable proper safety gating for irreversible delete behavior.
In `@nodes/src/nodes/tool_gmail/gmail_client.py`:
- Around line 100-101: The oauth_server_url is being retrieved directly from the
user token payload (info.get('oauth_server_url')) and subsequently used to make
POST requests carrying the refresh_token, which creates an SSRF vulnerability
where attackers could craft tokens with malicious URLs to leak credentials.
Replace the untrusted oauth_server_url from the token with a trusted broker URL
sourced from server-side configuration or environment variables, and implement
strict allowlisting to validate the scheme and host before making any refresh
token POST requests. This applies to all locations where oauth_server_url is
extracted and used for token refresh operations, including the range around
lines 140-167.
In `@nodes/src/nodes/tool_gmail/IInstance.py`:
- Line 217: The current pattern using args.get('maxResults') or <default> treats
a value of 0 as falsy and silently defaults it instead of validating it. Replace
this pattern with an explicit check that only applies the default when the value
is None (not provided), allowing 0 to be properly validated through the min/max
clamping logic. Apply this fix to all four affected locations: the current line
in the function at line 217, plus the same pattern in thread_list (line 396),
draft_list (line 510), and history_list (line 878), ensuring consistent behavior
across all list endpoints.
In `@nodes/src/nodes/tool_gmail/requirements.txt`:
- Around line 1-4: The requirements.txt file for the tool_gmail node lists
google-auth-oauthlib and related packages without explicit version constraints
for transitive dependencies idna and protobuf that have known vulnerabilities
(idna < 3.15 and protobuf < 4.25.8/5.29.5/6.31.1). Either add explicit version
pins for idna >= 3.15 and protobuf with the safe versions (>= 4.25.8, >= 5.29.5,
or >= 6.31.1 depending on the major version) directly in the requirements.txt
file, or verify that the auto-generated constraints.txt file created when
installing via depends() pins these safe versions.
In `@nodes/test/core/test_google_access.py`:
- Around line 151-153: The test test_gmail_declares_allow_hard_delete_flag()
checks that 'allowHardDelete' is present in GMAIL.flags, but the GMAIL object
definition currently has an empty flags tuple. Locate the GMAIL definition in
the google_access.py file and add 'allowHardDelete' as a string element to its
flags tuple so that the assertion in the test passes.
In `@nodes/test/test_agent_langchain_parser.py`:
- Around line 195-199: Add a new test function called
test_tool_call_non_dict_args_wrapped to improve test coverage for the non-dict
args wrapping behavior implemented in langchain.py around lines 375-376. The
test should parse a tool_call message where the args field contains a non-dict
value (such as a string), and assert that the resulting tool_calls entry has its
args wrapped in an 'input' key dictionary (converting the string value to the
format {'input': value}). This will verify that non-dictionary args are properly
transformed by the parser.
In `@nodes/test/tool_gmail/test_gmail_integration.py`:
- Around line 261-266: The test method test_hard_delete_blocked_at_modify_tier
is calling message_permanent_delete, but the actual hard-delete method exposed
in the tool surface is message_delete. Replace the method call from
message_permanent_delete to message_delete in the inst call to ensure the test
validates the correct permission gate for the actual implemented method.
In
`@packages/shared-ui/src/components/canvas/components/rjsf-widgets/social-buttons/LoginWithGoogleButton.tsx`:
- Around line 80-111: The useCallback hook in LoginWithGoogleButton.tsx uses the
saveChanges function within the callback but fails to include it in the
dependency array, which can cause stale closures if saveChanges changes. Add
saveChanges to the dependency array alongside the existing dependencies like
formContext, serviceParam, nodeId, selectedNode, oauth2RootUrl, oauthReturnUrl,
and onOpenExternal at the end of the useCallback hook definition.
---
Outside diff comments:
In
`@packages/shared-ui/src/components/canvas/components/panels/node-config/useOAuthCallbacks.ts`:
- Around line 45-54: When JSON.parse(tokensParam) throws an error in the
try-catch block, the function continues executing with tokens remaining as an
empty object, causing the fullTokenObject to be populated with undefined values
that get stored in the node config. Modify the catch block to return the
original formData immediately when a parse error occurs, preventing the function
from proceeding with invalid data. This ensures that if either stateParam or
tokensParam cannot be parsed, the original formData is returned without creating
a corrupted fullTokenObject with undefined token fields.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 2e45f8c8-1cb7-495e-b4d0-e0984bcfaf5e
⛔ Files ignored due to path filters (1)
nodes/src/nodes/tool_gmail/gmail.svgis excluded by!**/*.svg
📒 Files selected for processing (36)
apps/vscode/rsbuild.config.mjsapps/vscode/src/auth/CloudAuthProvider.tsapps/vscode/src/providers/ProjectProvider.tsapps/vscode/src/providers/views/Project/ProjectWebview.tsxapps/vscode/src/providers/views/types.tsnodes/src/nodes/agent_langchain/langchain.pynodes/src/nodes/core/google_access.pynodes/src/nodes/tool_gmail/IGlobal.pynodes/src/nodes/tool_gmail/IInstance.pynodes/src/nodes/tool_gmail/README.mdnodes/src/nodes/tool_gmail/__init__.pynodes/src/nodes/tool_gmail/gmail_client.pynodes/src/nodes/tool_gmail/requirements.txtnodes/src/nodes/tool_gmail/services.jsonnodes/test/core/test_google_access.pynodes/test/mocks/google/oauth2/__init__.pynodes/test/mocks/google/oauth2/credentials.pynodes/test/mocks/google/oauth2/service_account.pynodes/test/mocks/googleapiclient/__init__.pynodes/test/mocks/googleapiclient/discovery.pynodes/test/test_agent_langchain_parser.pynodes/test/tool_gmail/__init__.pynodes/test/tool_gmail/test_gmail.pynodes/test/tool_gmail/test_gmail_integration.pypackages/shared-ui/rslib.config.tspackages/shared-ui/src/components/canvas/Canvas.tsxpackages/shared-ui/src/components/canvas/components/FlowContainer.tsxpackages/shared-ui/src/components/canvas/components/panels/node-config/NodeConfigPanel.tsxpackages/shared-ui/src/components/canvas/components/panels/node-config/useOAuthCallbacks.tspackages/shared-ui/src/components/canvas/components/rjsf-widgets/social-buttons/LoginWithGoogleButton.tsxpackages/shared-ui/src/components/canvas/components/rjsf-widgets/social-buttons/LoginWithMicrosoftButton.tsxpackages/shared-ui/src/components/canvas/components/rjsf-widgets/social-buttons/LoginWithSlackButton.tsxpackages/shared-ui/src/components/canvas/context/FlowProjectContext.tsxpackages/shared-ui/src/components/canvas/context/FlowProvider.tsxpackages/shared-ui/src/config/oauth.tspackages/shared-ui/src/modules/project/ProjectView.tsx
| private pendingGoogleOAuth: ((tokens: string, state: string) => void) | null = null; | ||
| private disposables: vscode.Disposable[] = []; |
There was a problem hiding this comment.
Scope pending Google OAuth callbacks by state instead of a single global slot.
The current one-slot callback can be overwritten by another panel’s login before the deep-link returns, which can misroute or drop tokens across concurrent OAuth attempts.
Proposed fix (state-keyed waiter map)
- private pendingGoogleOAuth: ((tokens: string, state: string) => void) | null = null;
+ private pendingGoogleOAuth = new Map<string, (tokens: string, state: string) => void>();
- setPendingGoogleOAuth(callback: (tokens: string, state: string) => void): void {
- this.pendingGoogleOAuth = callback;
+ setPendingGoogleOAuth(state: string, callback: (tokens: string, state: string) => void): () => void {
+ this.pendingGoogleOAuth.set(state, callback);
+ return () => this.pendingGoogleOAuth.delete(state);
}
private handleGoogleOAuth(uri: vscode.Uri): void {
const params = new URLSearchParams(uri.query);
+ const state = params.get('state') ?? '';
+ if (!state) {
+ vscode.window.showErrorMessage('Google sign-in failed: missing OAuth state.');
+ return;
+ }
- const callback = this.pendingGoogleOAuth;
- this.pendingGoogleOAuth = null;
+ const callback = this.pendingGoogleOAuth.get(state);
+ this.pendingGoogleOAuth.delete(state);
// ... existing error/tokens checks
- callback(tokens, params.get('state') ?? '');
+ callback(tokens, state);
}Also applies to: 106-131
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/vscode/src/auth/CloudAuthProvider.ts` around lines 46 - 47, Replace the
single global `pendingGoogleOAuth` callback slot in the CloudAuthProvider class
with a state-keyed map to handle multiple concurrent OAuth attempts safely.
Instead of storing one callback function as `pendingGoogleOAuth`, create a Map
or object that keys callbacks by their OAuth state parameter. Update the
callback assignment logic (in the area around lines 106-131) to store the
callback keyed by state, and update the callback invocation to look up and
retrieve the correct callback using the returned state parameter. This ensures
each OAuth flow maintains its own callback and prevents token misrouting when
multiple login attempts happen concurrently.
| oauthReturnUrl: `${vscode.env.uriScheme}://rocketride.rocketride/auth/google`, | ||
| envKeys, |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify extension docs mention newly introduced OAuth bridge contract symbols.
rg -n --iglob 'apps/vscode/docs/**' 'oauthReturnUrl|project:openExternal|project:oauthTokens|/auth/google'Repository: rocketride-org/rocketride-server
Length of output: 58
🏁 Script executed:
#!/bin/bash
# First, check if apps/vscode/docs exists and list its contents
echo "=== VS Code docs directory contents ==="
fd -t f . apps/vscode/docs/ 2>/dev/null | head -20
echo ""
echo "=== Examining ProjectProvider.ts around lines 372-373 ==="
sed -n '365,380p' apps/vscode/src/providers/ProjectProvider.ts
echo ""
echo "=== Examining ProjectProvider.ts around lines 475-488 ==="
sed -n '470,495p' apps/vscode/src/providers/ProjectProvider.tsRepository: rocketride-org/rocketride-server
Length of output: 1922
🏁 Script executed:
#!/bin/bash
# Search for these contract terms more broadly in the codebase
echo "=== Searching for oauthReturnUrl in entire apps/vscode ==="
rg -l 'oauthReturnUrl' apps/vscode/
echo ""
echo "=== Searching for project:openExternal in entire apps/vscode ==="
rg -l 'project:openExternal' apps/vscode/
echo ""
echo "=== Searching for project:oauthTokens in entire apps/vscode ==="
rg -l 'project:oauthTokens' apps/vscode/Repository: rocketride-org/rocketride-server
Length of output: 701
🏁 Script executed:
#!/bin/bash
# Look for any messaging contract or protocol documentation
echo "=== Looking for contract/protocol docs in apps/vscode/docs ==="
if [ -d "apps/vscode/docs" ]; then
find apps/vscode/docs -type f -name "*.md" -o -name "*.txt" -o -name "*.json" | xargs ls -lh
else
echo "apps/vscode/docs directory does not exist"
fi
echo ""
echo "=== Searching for 'contract' or 'oauth' mentions in any vscode docs ==="
find apps/vscode -type f \( -name "*.md" -o -name "*.txt" \) -exec grep -l -i 'contract\|oauth' {} \;Repository: rocketride-org/rocketride-server
Length of output: 444
🏁 Script executed:
#!/bin/bash
# Check the actual content of the documentation files
echo "=== apps/vscode/docs/index.md ==="
cat apps/vscode/docs/index.md
echo ""
echo "=== apps/vscode/docs/installation.md ==="
cat apps/vscode/docs/installation.md
echo ""
echo "=== apps/vscode/docs/usage.md ==="
cat apps/vscode/docs/usage.mdRepository: rocketride-org/rocketride-server
Length of output: 12774
Update VS Code extension-surface documentation for new OAuth bridge contract.
The changes at lines 372-373 and 475-488 introduce new host↔webview contract fields and messages (oauthReturnUrl, project:openExternal, project:oauthTokens). Add documentation in apps/vscode/docs/ describing these contract changes, as required by the coding guidelines for extension contract modifications.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/vscode/src/providers/ProjectProvider.ts` around lines 372 - 373, Add
documentation in the apps/vscode/docs/ directory describing the new host↔webview
contract changes. Document the new oauthReturnUrl field and the new
project:openExternal and project:oauthTokens messages, explaining their purpose
and how they facilitate OAuth token exchange between the host and webview. This
documentation is required by coding guidelines whenever extension contract
modifications are made.
Source: Coding guidelines
| if (data.url) { | ||
| CloudAuthProvider.getInstance().setPendingGoogleOAuth((tokens, state) => { | ||
| webview.postMessage({ type: 'project:oauthTokens', tokens, state }); | ||
| }); | ||
| await vscode.env.openExternal(vscode.Uri.parse(data.url as string)); | ||
| } |
There was a problem hiding this comment.
Handle browser-open failures and clear the pending OAuth callback.
If openExternal fails/throws, the registered waiter remains pending and may consume a later unrelated deep-link response.
Proposed hardening
case 'project:openExternal': {
if (data.url) {
- CloudAuthProvider.getInstance().setPendingGoogleOAuth((tokens, state) => {
+ const unregister = CloudAuthProvider.getInstance().setPendingGoogleOAuth(/* state */, (tokens, state) => {
webview.postMessage({ type: 'project:oauthTokens', tokens, state });
});
- await vscode.env.openExternal(vscode.Uri.parse(data.url as string));
+ try {
+ const opened = await vscode.env.openExternal(vscode.Uri.parse(data.url as string));
+ if (!opened) {
+ unregister();
+ vscode.window.showErrorMessage('Google sign-in could not be opened in your browser.');
+ }
+ } catch (error) {
+ unregister();
+ const msg = error instanceof Error ? error.message : String(error);
+ vscode.window.showErrorMessage(`Google sign-in failed to launch: ${msg}`);
+ }
}
break;
}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/vscode/src/providers/ProjectProvider.ts` around lines 480 - 485, The
code sets a pending OAuth callback via
CloudAuthProvider.getInstance().setPendingGoogleOAuth() but does not handle
failures from the vscode.env.openExternal call. If openExternal throws an error,
the registered callback remains pending and could be triggered by an unrelated
deep-link response later. Wrap the openExternal call in a try-catch block, and
in the catch handler, clear the pending OAuth callback by calling
CloudAuthProvider.getInstance().setPendingGoogleOAuth with a null or cleared
state to prevent the stale callback from consuming future responses. Also handle
the error appropriately such as logging it or notifying the user.
| if (msg.oauthReturnUrl) setOauthReturnUrl(msg.oauthReturnUrl); | ||
| setEnvKeys(msg.envKeys ?? []); | ||
| break; | ||
| } | ||
| case 'project:oauthTokens': | ||
| setPendingOAuthTokens({ tokens: msg.tokens, state: msg.state }); | ||
| break; |
There was a problem hiding this comment.
Reset OAuth bridge state unconditionally during project:load.
Line 98 only updates oauthReturnUrl when truthy, so an omitted/empty value leaves stale URL state from the previous load. The same load path should also clear any stale pendingOAuthTokens before accepting new OAuth events.
Suggested patch
- if (msg.oauthReturnUrl) setOauthReturnUrl(msg.oauthReturnUrl);
+ setOauthReturnUrl(msg.oauthReturnUrl);
+ setPendingOAuthTokens(undefined);
setEnvKeys(msg.envKeys ?? []);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if (msg.oauthReturnUrl) setOauthReturnUrl(msg.oauthReturnUrl); | |
| setEnvKeys(msg.envKeys ?? []); | |
| break; | |
| } | |
| case 'project:oauthTokens': | |
| setPendingOAuthTokens({ tokens: msg.tokens, state: msg.state }); | |
| break; | |
| setOauthReturnUrl(msg.oauthReturnUrl); | |
| setPendingOAuthTokens(undefined); | |
| setEnvKeys(msg.envKeys ?? []); | |
| break; | |
| } | |
| case 'project:oauthTokens': | |
| setPendingOAuthTokens({ tokens: msg.tokens, state: msg.state }); | |
| break; |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/vscode/src/providers/views/Project/ProjectWebview.tsx` around lines 98 -
104, In the project:load case handler, unconditionally set oauthReturnUrl to
either the incoming value or a cleared/reset state instead of only updating when
msg.oauthReturnUrl is truthy, which leaves stale values from previous loads.
Additionally, in the same project:load case, add a call to setPendingOAuthTokens
to reset it to an initial cleared state before accepting new OAuth events,
preventing stale token data from persisting across project loads.
| google-api-python-client | ||
| google-auth | ||
| google-auth-oauthlib | ||
| google-auth-httplib2 |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Direct node requirements:"
cat nodes/src/nodes/tool_gmail/requirements.txt
echo
echo "Search repo constraints/locks/manifests for protobuf/idna pins:"
fd -i 'constraints*.txt|requirements*.txt|pyproject.toml|poetry.lock|uv.lock|Pipfile.lock' | while read -r f; do
echo "--- $f"
rg -n '\bidna\b|\bprotobuf\b' "$f" || true
done
echo
echo "If pins are absent or below patched ranges, update constraints and rerun OSV scan."Repository: rocketride-org/rocketride-server
Length of output: 5911
🏁 Script executed:
#!/bin/bash
# Check the main nodes requirements for version pins
echo "=== nodes/src/nodes/requirements.txt (full content) ==="
cat nodes/src/nodes/requirements.txt
echo
echo "=== llm_gemini/requirements.txt (to see protobuf pin) ==="
cat nodes/src/nodes/llm_gemini/requirements.txt | head -15
echo
echo "=== milvus/requirements.txt (to see protobuf pin) ==="
cat nodes/src/nodes/milvus/requirements.txt | head -15
echo
echo "=== Check for root-level constraint/lock files ==="
ls -la | grep -E 'constraints|lock|pyproject|Pipfile|poetry|uv.lock' || echo "No constraint files in root"
echo
echo "=== Check if there's a uv.lock or poetry.lock ==="
find . -maxdepth 2 -name '*.lock' -o -name 'uv.lock' 2>/dev/null | head -10Repository: rocketride-org/rocketride-server
Length of output: 1408
🏁 Script executed:
#!/bin/bash
# Check pyproject.toml for dependency management
echo "=== Root pyproject.toml ==="
cat pyproject.toml
echo
echo "=== Check what google-auth* packages require ==="
# Search repo for any google-auth dependency specifications
rg 'google-auth' nodes/src/nodes/tool_gmail/ -A 2 -B 2
echo
echo "=== Check how nodes are installed (depends function) ==="
rg 'def depends|depends\(' --max-count=20 -B 2 -A 2 | head -50Repository: rocketride-org/rocketride-server
Length of output: 8041
🏁 Script executed:
#!/bin/bash
# Find the depends() function implementation
echo "=== Looking for depends() function definition ==="
rg 'def depends\(' -B 2 -A 10 | head -60
echo
echo "=== Check if this is a pip-based install or other mechanism ==="
fd -i 'conftest.py|__init__.py' nodes/src/nodes/ | xargs grep -l 'depends' 2>/dev/null || echo "Not found in conftest"
echo
echo "=== Search for depends in main Python files ==="
rg 'from.*depends|import.*depends' --max-count=20
echo
echo "=== Check if google-auth packages pull idna/protobuf ==="
# This would be transitive - let's check the Google client library docs
echo "Searching for any documentation or comments about dependencies:"
rg 'google-auth|google-api-python-client' nodes/src/nodes/tool_gmail/ -B 2 -A 2Repository: rocketride-org/rocketride-server
Length of output: 15754
🏁 Script executed:
#!/bin/bash
# Check the depends.py implementation for constraint handling
echo "=== depends.py implementation (constraint handling) ==="
head -100 packages/server/engine-lib/rocketlib-python/lib/depends.py
echo
echo "=== Search for constraint files ==="
find . -name '*constraint*' -o -name '*requirements*' -type f | grep -E '\.(txt|txt.lock|in)$' | head -20
echo
echo "=== Check for any constraint file patterns ==="
fd -i 'constraint' --type f | head -20
echo
echo "=== Look for pip constraints usage ==="
rg -i 'pip.*constraint|--constraint' --max-count=10
echo
echo "=== Check depends.py for constraint loading ==="
grep -n 'constraint' packages/server/engine-lib/rocketlib-python/lib/depends.py | head -20Repository: rocketride-org/rocketride-server
Length of output: 6341
🏁 Script executed:
#!/bin/bash
# Get more of depends.py to understand requirements concatenation
echo "=== depends.py - concatenated requirements logic ==="
sed -n '300,350p' packages/server/engine-lib/rocketlib-python/lib/depends.py
echo
echo "=== depends.py - ensure_constraints full implementation ==="
sed -n '708,750p' packages/server/engine-lib/rocketlib-python/lib/depends.py
echo
echo "=== Check what engine_cache_dir returns ==="
grep -n 'def engine_cache_dir\|engine_cache_dir =' packages/server/engine-lib/rocketlib-python/lib/depends.py | head -5
sed -n '250,290p' packages/server/engine-lib/rocketlib-python/lib/depends.py
echo
echo "=== Look for concatenated requirements ==="
grep -n 'concatenat' packages/server/engine-lib/rocketlib-python/lib/depends.pyRepository: rocketride-org/rocketride-server
Length of output: 4527
🌐 Web query:
google-auth google-auth-oauthlib transitive dependencies idna protobuf versions
💡 Result:
Summary (as of 2026-06-22): 1) Direct dependencies (google-auth-oauthlib -> google-auth) - google-auth-oauthlib declares a dependency on google-auth with an upper bound and exclusions for known-incompatible google-auth versions. In the current upstream setup.py in google-auth-oauthlib, the dependency is: "google-auth>=2.15.0,<3.0.0,!=2.43.0,!=2.44.0,!=2.45.0" (and requests-oauthlib>=0.7.0). [1] 2) google-auth-oauthlib transitive dependency: idna - google-auth-oauthlib’s dependencies pull in requests, and requests depends on idna. For example, a dependency tree shown in a real-world pip resolution conflict indicates idna 3.10 is installed as a transitive dependency of requests (idna v3.10). [2] 3) google-auth transitive dependency: protobuf (indirect, via google-auth users / Google API clients) - google-auth itself is not a protobuf library, but many Google API client libraries that depend on google-auth typically depend on protobuf. A Google developer page listing Python client-lib dependency versions for the Google Ads API shows protobuf versions co-occurring with google-auth-oauthlib versions (e.g., protobuf 4.25.x and 5.26.x/5.27.x/5.28.x are listed alongside google-auth-oauthlib releases). This reflects that protobuf is a dependency in the overall client library stack that uses google-auth/google-auth-oauthlib. [3] 4) Concrete version skew warning involving protobuf at runtime - A reported issue demonstrates protobuf runtime/gencode skew detection (example: gencode version older than the protobuf runtime), with installed protobuf and resulting warning. This is useful context for why you must pay attention to protobuf versions in dependency resolution. [4] What you likely want to do (actionable guidance) - Pin google-auth-oauthlib (and/or google-auth) and use pip’s resolver on a clean environment; the upstream constraints already try to avoid known google-auth incompatibilities. [1] - If you observe protobuf runtime/gencode warnings or grpc/other packages complaining, resolve protobuf version conflicts across the whole graph (especially if grpcio/grpcio-tools are involved). [4] Limitations of this research (important) - The provided authoritative sources above do not give a single authoritative “the transitive dependency list of google-auth and google-auth-oauthlib is exactly: idna==X, protobuf==Y”. Instead, protobuf/idna appear in: (a) the dependency graph of packages that depend on google-auth(/oauthlib) and (b) a dependency conflict report. [3][2] If you tell me your exact versions (pip freeze output, or your target google-cloud/google-ads library versions), I can derive the exact idna/protobuf versions that will be selected under those constraints and explain why.
Citations:
- 1: googleapis/google-auth-library-python-oauthlib@088a597
- 2: pip dependency conflicts with google-api-auth frappe/frappe#28426
- 3: https://developers.google.com/google-ads/api/docs/client-libs/python/dependencies
- 4: Protobuf version skew a2aproject/a2a-python#173
🌐 Web query:
idna protobuf CVE security advisory vulnerable versions 2024 2025 2026
💡 Result:
Key result: I did not find evidence of an “idna protobuf” CVE advisory that combines both libraries into a single vulnerability; instead, I found separate, related DoS/validation issues affecting (1) the Python idna library and (2) the protobuf Python implementation. Below are the concrete, source-backed vulnerable version ranges and advisories I found for 2024–2026. 1) Python idna library (CVE-2024-3651, fixed; incomplete remediation later leads to bypass) - Advisory (idna, pip): GHSA-jjg7-2v4v-x38h. - Affected versions: idna < 3.7. - Patched versions: 3.7. - Description: specially crafted inputs to idna.encode could consume significant resources (DoS), and mitigation is improved input rejection/length handling. [1] 2) Python idna library bypass / continuation (CVE-2026-45409) - Advisory (idna, pip): GHSA-65pc-fj4g-8rjx. - Affected versions: idna < 3.15. [2] - Patched versions: 3.15. [2] - Description: crafted inputs can bypass the earlier CVE-2024-3651 remediation; it abuses valid_contexto before length validation, causing significant resource consumption; mitigation includes upgrading to 3.15 and (as noted in multiple sources) enforcing max domain length of 253 before calling idna.encode. [2][3] 3) Protobuf Python pure-Python backend (CVE-2025-4565) - Advisory: GHSA-8qvm-5x2c-j2w7. - Scope/variant: affects protobuf-python when using the pure-Python backend (PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION=python or pure-Python wheels); not the default C++ backend. [4] - Severity: High potential DoS due to unbounded recursion hitting Python recursion limit when parsing attacker-controlled crafted protobuf structures. [4] - Affected versions: <4.25.8, <5.29.5, <6.31.1. [4] - Patched versions: 4.25.8, 5.29.5, 6.31.1. [4] Notes / how to use this for dependency remediation - If your stack includes Python idna and protobuf (especially protobuf with pure-Python parsing enabled), the practical action is: upgrade idna to >=3.15 (to address CVE-2026-45409) and upgrade protobuf-python to at least one of the patched lines (4.25.8 / 5.29.5 / 6.31.1 depending on your current major series). [2][4] If you meant a specific CVE that explicitly mentions both idna and protobuf together (or a specific language/ecosystem, e.g., Go “x/net/idna” inside a protobuf service), tell me the CVE/CWE or the exact packages/versions you’re targeting and I’ll re-run the search narrowly for that combined reference.
Citations:
- 1: GHSA-jjg7-2v4v-x38h
- 2: GHSA-65pc-fj4g-8rjx
- 3: https://security-tracker.debian.org/tracker/CVE-2026-45409
- 4: GHSA-8qvm-5x2c-j2w7
Verify that auto-generated constraints pin patched idna/protobuf versions, or add explicit pins.
The google-auth-oauthlib dependency pulls idna transitively (via requests), and both idna and protobuf have known vulnerabilities:
idna< 3.15 (CVE-2026-45409, GHSA-65pc-fj4g-8rjx)protobuf< 4.25.8 / 5.29.5 / 6.31.1 (CVE-2025-4565, GHSA-8qvm-5x2c-j2w7)
Since this node installs via depends() without explicit version constraints, confirm the auto-generated constraints.txt pins safe versions or explicitly constrain these packages in the requirements file.
🧰 Tools
🪛 OSV Scanner (2.4.0)
[MEDIUM] 1-1: idna 3.9.0: undefined
(PYSEC-2026-215)
[MEDIUM] 1-1: idna 3.9.0: Internationalized Domain Names in Applications (IDNA): Specially crafted inputs to idna.encode() can bypass CVE-2024-3651 fix
[HIGH] 1-1: protobuf 4.25.9: protobuf affected by a JSON recursion depth bypass
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@nodes/src/nodes/tool_gmail/requirements.txt` around lines 1 - 4, The
requirements.txt file for the tool_gmail node lists google-auth-oauthlib and
related packages without explicit version constraints for transitive
dependencies idna and protobuf that have known vulnerabilities (idna < 3.15 and
protobuf < 4.25.8/5.29.5/6.31.1). Either add explicit version pins for idna >=
3.15 and protobuf with the safe versions (>= 4.25.8, >= 5.29.5, or >= 6.31.1
depending on the major version) directly in the requirements.txt file, or verify
that the auto-generated constraints.txt file created when installing via
depends() pins these safe versions.
Source: Linters/SAST tools
| def test_tool_call_args_defaults_to_empty_dict(): | ||
| raw = '{"type":"tool_call","name":"srv.ping"}' | ||
| msg = _parse(raw) | ||
| assert msg is not None | ||
| assert msg.tool_calls[0]['args'] == {} |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial | 💤 Low value
Optional: Consider adding a test case for non-dict args wrapping.
The parser wraps non-dict args values in an input key (langchain.py lines 375-376), but there's no explicit test for this. While this is pre-existing behavior (not part of the current parser fix), a test case would improve coverage.
Example test:
def test_tool_call_non_dict_args_wrapped():
raw = '{"type":"tool_call","name":"srv.echo","args":"hello"}'
msg = _parse(raw)
assert msg is not None
assert msg.tool_calls[0]['args'] == {'input': 'hello'}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@nodes/test/test_agent_langchain_parser.py` around lines 195 - 199, Add a new
test function called test_tool_call_non_dict_args_wrapped to improve test
coverage for the non-dict args wrapping behavior implemented in langchain.py
around lines 375-376. The test should parse a tool_call message where the args
field contains a non-dict value (such as a string), and assert that the
resulting tool_calls entry has its args wrapped in an 'input' key dictionary
(converting the string value to the format {'input': value}). This will verify
that non-dictionary args are properly transformed by the parser.
| def test_hard_delete_blocked_at_modify_tier(self): | ||
| """message_delete should raise without the full tier.""" | ||
| inst = make_live_inst('modify') | ||
| with pytest.raises((ValueError, PermissionError)): | ||
| inst.message_permanent_delete({'id': 'fake-id-that-doesnt-exist'}) | ||
|
|
There was a problem hiding this comment.
Use the implemented hard-delete method name in the gate test.
This test calls message_permanent_delete, while the tool surface used elsewhere in this PR is message_delete; as written, the gate test can fail for the wrong reason and miss the actual permission check.
Proposed fix
class TestHardDeleteGate:
def test_hard_delete_blocked_at_modify_tier(self):
"""message_delete should raise without the full tier."""
inst = make_live_inst('modify')
- with pytest.raises((ValueError, PermissionError)):
- inst.message_permanent_delete({'id': 'fake-id-that-doesnt-exist'})
+ with pytest.raises(ga.GoogleAccessError):
+ inst.message_delete({'id': 'fake-id-that-doesnt-exist'})📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| def test_hard_delete_blocked_at_modify_tier(self): | |
| """message_delete should raise without the full tier.""" | |
| inst = make_live_inst('modify') | |
| with pytest.raises((ValueError, PermissionError)): | |
| inst.message_permanent_delete({'id': 'fake-id-that-doesnt-exist'}) | |
| def test_hard_delete_blocked_at_modify_tier(self): | |
| """message_delete should raise without the full tier.""" | |
| inst = make_live_inst('modify') | |
| with pytest.raises(ga.GoogleAccessError): | |
| inst.message_delete({'id': 'fake-id-that-doesnt-exist'}) |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@nodes/test/tool_gmail/test_gmail_integration.py` around lines 261 - 266, The
test method test_hard_delete_blocked_at_modify_tier is calling
message_permanent_delete, but the actual hard-delete method exposed in the tool
surface is message_delete. Replace the method call from message_permanent_delete
to message_delete in the inst call to ensure the test validates the correct
permission gate for the actual implemented method.
| // Tell the broker where to return tokens. Web hosts redirect back to the | ||
| // current page; hosts that can't receive a web redirect (VS Code) supply | ||
| // a deep link via oauthReturnUrl that they intercept out-of-band. | ||
| url.searchParams.set('baseURL', oauthReturnUrl || window.location.href); | ||
|
|
||
| // For Gmail tiers beyond the broker's default (modify), pass the required | ||
| // scopes explicitly so the broker requests the right Google consent. Keys | ||
| // mirror google_access.py GMAIL.scopes. readonly/modify are omitted because | ||
| // the broker handles them by default; non-Gmail services whose access field | ||
| // uses different values (e.g. 'write') won't match any key here. | ||
| const _G = 'https://www.googleapis.com/auth'; | ||
| const GMAIL_EXTENDED_SCOPES: Record<string, string[]> = { | ||
| send: [`${_G}/gmail.modify`, `${_G}/gmail.send`], | ||
| settings: [`${_G}/gmail.modify`, `${_G}/gmail.settings.basic`], | ||
| settings_sharing: [`${_G}/gmail.modify`, `${_G}/gmail.settings.basic`, `${_G}/gmail.settings.sharing`], | ||
| full: ['https://mail.google.com/'], | ||
| }; | ||
| const accessTier = selectedNode?.data?.formData?.parameters?.access as string | undefined; | ||
| const tierScopes = accessTier ? GMAIL_EXTENDED_SCOPES[accessTier] : undefined; | ||
| if (tierScopes?.length) { | ||
| url.searchParams.set('scope', tierScopes.join(' ')); | ||
| } | ||
|
|
||
| const targetUrl = url.toString(); | ||
| // Use onOpenLink callback for embedded hosts (e.g., VSCode), otherwise do a full-page redirect | ||
| if (onOpenLink) onOpenLink(targetUrl); | ||
| // VS Code (onOpenExternal) opens the system browser — Google's consent | ||
| // screen refuses to render in an embedded iframe — and delivers tokens | ||
| // back via pendingOAuthTokens. Web hosts do a full-page redirect. | ||
| if (onOpenExternal) onOpenExternal(targetUrl); | ||
| else window.location.href = targetUrl; | ||
|
|
||
| // eslint-disable-next-line react-hooks/exhaustive-deps, @typescript-eslint/no-explicit-any | ||
| }, [(props as any).formContext, serviceParam, nodeId, selectedNode, oauth2RootUrl, onOpenLink]); | ||
| }, [(props as any).formContext, serviceParam, nodeId, selectedNode, oauth2RootUrl, oauthReturnUrl, onOpenExternal]); |
There was a problem hiding this comment.
Missing saveChanges in useCallback dependency array.
The callback uses saveChanges (line 57) but it's not included in the dependency array (line 111). If saveChanges reference changes, the callback will use a stale closure.
🔧 Proposed fix
- }, [(props as any).formContext, serviceParam, nodeId, selectedNode, oauth2RootUrl, oauthReturnUrl, onOpenExternal]);
+ }, [saveChanges, (props as any).formContext, serviceParam, nodeId, selectedNode, oauth2RootUrl, oauthReturnUrl, onOpenExternal]);🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@packages/shared-ui/src/components/canvas/components/rjsf-widgets/social-buttons/LoginWithGoogleButton.tsx`
around lines 80 - 111, The useCallback hook in LoginWithGoogleButton.tsx uses
the saveChanges function within the callback but fails to include it in the
dependency array, which can cause stale closures if saveChanges changes. Add
saveChanges to the dependency array alongside the existing dependencies like
formContext, serviceParam, nodeId, selectedNode, oauth2RootUrl, oauthReturnUrl,
and onOpenExternal at the end of the useCallback hook definition.
1. Restore allowHardDelete flag to GMAIL AccessSpec (google_access.py was accidentally changed to flags=() which broke test_gmail_declares_allow_hard_delete_flag and silently disabled the hard-delete gate in IInstance.py). Also add gmail.allowHardDelete config field to services.json so the spec flag is user-configurable. 2. Fix sys.modules stub pollution in test_agent_langchain_parser.py. The previous _mod() helper unconditionally overwrote real engine modules (rocketlib, ai.common.schema, etc.) in CI where they are already present, breaking 30+ unrelated tests. Now stubs are installed only when the module is absent (_mod_if_absent), and engine stubs are cleaned up after langchain.py is loaded so they cannot shadow real modules for other tests. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Summary
tool_gmailnode exposing 61 Gmail v1 API operations as agent tools: read/search/organize mail, labels, drafts, filters, send (incl. HTML + attachments + in-thread replies), settings (IMAP/POP/vacation/forwarding/delegates/send-as/S-MIME), push notifications, and gated hard-delete.settings/settings_sharingaccess tiers andallowHardDeletegate to the shared Google scope resolver.LoginWithGoogleButtonto pass an explicitscopeparam to the OAuth broker for extended Gmail tiers (send,settings,settings_sharing,full) so the resulting token actually covers the required Google scopes.agent_langchainJSON envelope parser to tolerate conversational preamble text the LLM sometimes outputs before the JSON object (scan for{beforeraw_decode).Consolidates and supersedes PR #1333 (RR-1055) and PR #1192 (RR-1142).
Test plan
pytest nodes/test/tool_gmail/test_gmail.py)pytest nodes/test/test_agent_langchain_parser.py)settingstier → Login with Google → confirm broker OAuth URL includesgmail.settings.basicscope → run a filter-create pipeline end-to-end without "403 scope" or "unparseable output" errorsCloses RR-1055
Closes RR-1142
🤖 Generated with Claude Code
Summary by CodeRabbit
Release Notes
New Features
Bug Fixes
Tests