Skip to content

feat(connectors): add admin-managed app runtime - #2068

Open
qdaxb wants to merge 4 commits into
mainfrom
feature/connector-apps
Open

feat(connectors): add admin-managed app runtime#2068
qdaxb wants to merge 4 commits into
mainfrom
feature/connector-apps

Conversation

@qdaxb

@qdaxb qdaxb commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Summary

  • add administrator-managed Connector Apps in Wegent Admin, including visibility, encrypted provider credentials, OAuth 2.0 + PKCE, and tool allowlists
  • support both upstream MCP servers and ordinary HTTP APIs; Backend maps administrator-defined HTTP operations into the same MCP tool contract
  • add a least-privilege Backend Connector Runtime and local Executor stdio MCP proxy (wegent_apps) without changing Codex
  • synchronize connected App Skills into Wework while keeping Connector configuration and authorization UI out of Wework
  • add Chinese and English architecture/deployment documentation

Security boundaries

  • secrets remain encrypted in Backend and are never returned by management APIs
  • Wework exchanges its cloud session for a 15-minute connectors:invoke JWT
  • Executor stores only that scoped token in a mode-0600 runtime file; Codex config contains no token
  • visibility and tool allowlists are enforced for discovery and direct invocation
  • HTTP paths must be relative, redirects are disabled, arguments are JSON-Schema validated, and responses are capped at 1 MB

Validation

  • Backend Connector focused tests: 21 passed
  • Frontend Connector tests: 2 passed; TypeScript, ESLint, and pre-push frontend suite passed
  • Wework: 184 files / 1842 tests passed; TypeScript and ESLint passed
  • Executor: Connector tests passed; 263 library tests passed; post-main-merge runtime send contracts 23/23 passed; pre-push lib tests, clippy, and fmt passed
  • Alembic upgrade -> downgrade -> upgrade passed on SQLite; single-head check passed
  • real Tauri E2E: Wework $ menu -> generated Connector Skill -> Codex -> wegent_apps stdio MCP -> scoped Backend Runtime -> HTTP adapter -> internal HTTP service -> assistant result
  • real E2E exposed and fixed missing WEGENT_EXECUTOR_HOME propagation to the Codex-launched MCP child
  • screenshot chain reviewed locally: App search, selected App prompt, one MCP tool invocation, and returned internal system data

Summary by CodeRabbit

  • New Features
    • Administrators can create, configure, update, and disable connector apps.
    • Supports role-based visibility, bearer credentials, OAuth authorization, and HTTP or MCP-based tools.
    • Users can browse available apps, connect or disconnect accounts, and securely invoke connected tools.
    • Connected apps now synchronize with the local executor and appear as skills in the workbench.
    • Added short-lived authorization and improved credential protection throughout connector workflows.
  • Documentation
    • Added English and Chinese developer guidance for connector app setup and deployment.

@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Important

Review skipped

No new commits to review since the last review.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 418b8ef5-c782-4cec-9b79-85bb5d43e51c

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Changes

Connector Apps and Runtime

Layer / File(s) Summary
Backend connector contracts and persistence
backend/alembic/..., backend/app/models/..., backend/app/schemas/..., backend/app/core/config.py, backend/pyproject.toml
Adds connector tables, ORM models, validation schemas, OAuth callback configuration, and JSON Schema support.
Backend catalog and authorization flows
backend/app/api/..., backend/app/services/connector_apps.py, backend/tests/api/test_connector_apps_api.py
Adds admin CRUD, user visibility and connection APIs, bearer credentials, OAuth PKCE flows, encryption, revocation, and coverage tests.
Backend runtime execution
backend/app/api/endpoints/connector_runtime.py, backend/app/services/connector_runtime.py, backend/tests/services/test_connector_runtime.py
Adds scoped JWT authentication, MCP and HTTP tool discovery/invocation, OAuth refresh, pagination, response normalization, and runtime tests.
Executor connector gateway and MCP proxy
executor/src/connector_gateway.rs, executor/src/connector_mcp.rs, executor/src/runtime_work/*, executor/tests/config_contract.rs
Adds persisted gateway authorization, local MCP JSON-RPC forwarding, runtime RPC methods, connector skill materialization, and tests.
Frontend connector administration
frontend/src/apis/admin.ts, frontend/src/features/admin/..., frontend/src/app/admin/page.tsx, frontend/src/i18n/locales/*, frontend/src/__tests__/...
Adds connector administration APIs, tab navigation, forms, localized content, and UI tests.
Cloud-to-local connector synchronization
wework/src/api/cloud/connectorApps.ts, wework/src/features/cloud-connection/..., wework/src/features/workbench/WorkbenchProvider.tsx, wework/src/types/api.ts, wework/src/components/chat/...
Synchronizes connector tokens, connected apps, local skills, and app references between cloud services and the local Executor.
Connector deployment documentation
docs/en/developer-guide/connector-apps.md, docs/zh/developer-guide/connector-apps.md
Documents connector architecture, authentication, HTTP adaptation, deployment, lifecycle, and API layers.

Verification environment isolation

Layer / File(s) Summary
AI verification environment cleanup
wework/scripts/ai-verify-environment.*
Removes inherited Executor-related environment variables and verifies isolated session defaults.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Admin
  participant Backend
  participant CloudClient
  participant LocalExecutor
  participant UpstreamConnector
  Admin->>Backend: create or update connector app
  CloudClient->>Backend: request scoped connector token
  CloudClient->>LocalExecutor: configure connector gateway
  CloudClient->>Backend: list connected connector apps
  CloudClient->>LocalExecutor: synchronize connector skills
  LocalExecutor->>Backend: request tools or invoke tool
  Backend->>UpstreamConnector: discover or execute connector tool
  UpstreamConnector-->>Backend: return tool result
  Backend-->>LocalExecutor: return normalized result
Loading

Suggested reviewers: ficohu

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 8.18% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and matches the main change: adding an admin-managed connector app runtime.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/connector-apps

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@qdaxb
qdaxb marked this pull request as ready for review July 20, 2026 06:51
@qdaxb

qdaxb commented Jul 20, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

🧹 Nitpick comments (5)
wework/scripts/ai-verify-environment.test.mjs (2)

7-15: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add WEGENT_EXECUTOR_APP_IPC_ADDR to test coverage.

The constant INHERITED_EXECUTOR_ENV_KEYS in the source file includes WEGENT_EXECUTOR_APP_IPC_ADDR, but it is currently omitted from the test's input environment. Consider adding it to ensure complete coverage of the filtered keys.

♻️ Proposed fix
       {
         PATH: '/usr/bin',
+        WEGENT_EXECUTOR_APP_IPC_ADDR: '127.0.0.1:8080',
         WEGENT_EXECUTOR_APP_IPC_ADDR_FILE: '/tmp/foreign.addr',
         WEGENT_EXECUTOR_APP_IPC_SOCKET: '/tmp/legacy.sock',
         WEGENT_EXECUTOR_BINARY: '/tmp/foreign-executor',
🤖 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 `@wework/scripts/ai-verify-environment.test.mjs` around lines 7 - 15, Update
the test input environment in the relevant ai-verify-environment test to include
WEGENT_EXECUTOR_APP_IPC_ADDR alongside the other inherited executor environment
keys, ensuring the filtering behavior covers every key listed by
INHERITED_EXECUTOR_ENV_KEYS.

37-42: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Assert that WEGENT_EXECUTOR_APP_IPC_ADDR is isolated.

Include the assertion for the missing key to complete the verification.

♻️ Proposed fix
     expect(environment.WEWORK_EXECUTOR_ISOLATION_OVERRIDE).toBe('true')
+    expect(environment.WEGENT_EXECUTOR_APP_IPC_ADDR).toBeUndefined()
     expect(environment.WEGENT_EXECUTOR_APP_IPC_ADDR_FILE).toBeUndefined()
     expect(environment.WEGENT_EXECUTOR_APP_IPC_SOCKET).toBeUndefined()
     expect(environment.WEGENT_EXECUTOR_BINARY).toBeUndefined()
🤖 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 `@wework/scripts/ai-verify-environment.test.mjs` around lines 37 - 42, Add an
expectation in the environment isolation test alongside the existing
WEGENT_EXECUTOR_* assertions to verify that
environment.WEGENT_EXECUTOR_APP_IPC_ADDR is undefined, preserving the test’s
missing-key verification pattern.
wework/src/features/cloud-connection/LocalExecutorCloudBridge.test.tsx (2)

111-113: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Update the assertion payload.

This assertion should be updated to match the inclusion of description in the sync payload.

🛠️ Proposed fix
-    expect(mocks.request).toHaveBeenCalledWith('runtime.connectors.apps.sync', {
-      apps: [{ slug: 'tickets', name: 'Tickets' }],
-    })
+    expect(mocks.request).toHaveBeenCalledWith('runtime.connectors.apps.sync', {
+      apps: [{ slug: 'tickets', name: 'Tickets', description: 'Manage tickets' }],
+    })
🤖 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 `@wework/src/features/cloud-connection/LocalExecutorCloudBridge.test.tsx`
around lines 111 - 113, Update the assertion in the LocalExecutorCloudBridge
test for the runtime.connectors.apps.sync request to include the expected
description field in the tickets app payload, while preserving the existing slug
and name values.

43-49: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add description to the test mock payload.

If you update LocalExecutorCloudBridge to include the description field in the payload, you should also update this mock data and the corresponding test assertion.

🛠️ Proposed fix
     mocks.listApps.mockResolvedValue([
       {
         id: 1,
         slug: 'tickets',
         name: 'Tickets',
+        description: 'Manage tickets',
         connection: { status: 'connected' },
       },
🤖 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 `@wework/src/features/cloud-connection/LocalExecutorCloudBridge.test.tsx`
around lines 43 - 49, Update the listApps mock in the LocalExecutorCloudBridge
test to include the description field expected by the revised payload, and
update the corresponding assertion to verify that description is propagated
correctly.
backend/app/services/connector_runtime.py (1)

449-540: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoff

Consider splitting _refresh_oauth_if_needed into smaller helpers.

The method spans ~90 lines and mixes locking/double-check, HTTP token exchange, response validation, and persistence. Extracting the token-request and token-application steps would improve readability. The double-checked with_for_update() locking itself is a solid concurrency choice.

As per coding guidelines: "Keep functions focused, preferably under 50 lines."

🤖 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 `@backend/app/services/connector_runtime.py` around lines 449 - 540, Split
_refresh_oauth_if_needed into focused helpers while preserving its existing
double-checked with_for_update locking and refresh behavior. Extract the OAuth
token request/error handling into one helper and token
validation/application/persistence into another, leaving
_refresh_oauth_if_needed responsible for eligibility checks, locking, and
orchestration; keep each function under roughly 50 lines.

Source: Coding guidelines

🤖 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 `@backend/alembic/versions/20260720_f8a9b0c1d2e3_add_connector_http_tools.py`:
- Around line 21-24: Update the http_tools column definition in the migration to
use a MySQL-compatible JSON default expression or backfill existing rows before
enforcing non-nullability, replacing the bare server_default string while
preserving the empty-array default for new records.

In `@backend/app/core/config.py`:
- Around line 227-229: Validate CONNECTOR_OAUTH_CALLBACK_BASE_URL as an absolute
HTTP(S) URL without query or fragment components before using it for OAuth
redirect construction. Require HTTPS unless running in the existing
local/development environment, and reject invalid values through the
configuration validation path.

In `@backend/app/schemas/connector.py`:
- Around line 84-114: Update
ConnectorHttpToolDefinition.validate_argument_locations so the set of path
placeholders in self.path must exactly match the names in argument_locations
whose location is "path". Reject both placeholders missing a corresponding path
argument and path arguments missing placeholders, while preserving the existing
schema-property and required-property validation.

In `@executor/src/connector_gateway.rs`:
- Around line 44-93: Update the timeout configured in ConnectorGateway::request
to align with the downstream MCP tool/session timeout of 180 seconds, or source
it from the existing configurable timeout setting if one is available. Preserve
the existing request construction and connector_gateway_unavailable error
handling.

In `@frontend/src/features/admin/components/ConnectorAppList.tsx`:
- Around line 346-368: Update the edit and disable buttons in the
ConnectorAppList action group to provide at least a 44px × 44px touch target on
mobile, using responsive sizing that may retain the existing 32px dimensions at
md and larger. Preserve their current behavior, styling, labels, and test IDs.
- Around line 687-696: Update the cancel and save button labels in the
ConnectorAppList component to use the admin translation keys at the correct
namespace, replacing the current common.cancel and common.save lookups with
t('cancel') and t('save') (or the established shared common namespace if
intentionally configured).

In `@wework/src/e2e/automation.ts`:
- Around line 208-209: Update the authToken initialization in the E2E setup to
require a non-empty VITE_WEWORK_E2E_CLOUD_AUTH_TOKEN value instead of using the
committed fallback token. Fail setup immediately with a clear configuration
error when the variable is missing, and ensure the variable is provided only
through isolated E2E build configuration.

In `@wework/src/features/cloud-connection/LocalExecutorCloudBridge.tsx`:
- Around line 84-88: Update the payload mapping in LocalExecutorCloudBridge to
include each connected app’s description alongside slug and name, matching the
runtime.connectors.apps.sync payload used by WorkbenchProvider.

---

Nitpick comments:
In `@backend/app/services/connector_runtime.py`:
- Around line 449-540: Split _refresh_oauth_if_needed into focused helpers while
preserving its existing double-checked with_for_update locking and refresh
behavior. Extract the OAuth token request/error handling into one helper and
token validation/application/persistence into another, leaving
_refresh_oauth_if_needed responsible for eligibility checks, locking, and
orchestration; keep each function under roughly 50 lines.

In `@wework/scripts/ai-verify-environment.test.mjs`:
- Around line 7-15: Update the test input environment in the relevant
ai-verify-environment test to include WEGENT_EXECUTOR_APP_IPC_ADDR alongside the
other inherited executor environment keys, ensuring the filtering behavior
covers every key listed by INHERITED_EXECUTOR_ENV_KEYS.
- Around line 37-42: Add an expectation in the environment isolation test
alongside the existing WEGENT_EXECUTOR_* assertions to verify that
environment.WEGENT_EXECUTOR_APP_IPC_ADDR is undefined, preserving the test’s
missing-key verification pattern.

In `@wework/src/features/cloud-connection/LocalExecutorCloudBridge.test.tsx`:
- Around line 111-113: Update the assertion in the LocalExecutorCloudBridge test
for the runtime.connectors.apps.sync request to include the expected description
field in the tickets app payload, while preserving the existing slug and name
values.
- Around line 43-49: Update the listApps mock in the LocalExecutorCloudBridge
test to include the description field expected by the revised payload, and
update the corresponding assertion to verify that description is propagated
correctly.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9c680d47-bd21-458a-9db7-7b15efc4e8a1

📥 Commits

Reviewing files that changed from the base of the PR and between 55e7ad8 and 1b420ef.

⛔ Files ignored due to path filters (1)
  • backend/uv.lock is excluded by !**/*.lock
📒 Files selected for processing (44)
  • backend/alembic/versions/20260716_e7f8a9b0c1d2_add_connector_apps.py
  • backend/alembic/versions/20260720_f8a9b0c1d2e3_add_connector_http_tools.py
  • backend/app/api/api.py
  • backend/app/api/endpoints/admin/connector_apps.py
  • backend/app/api/endpoints/admin/router.py
  • backend/app/api/endpoints/connector_apps.py
  • backend/app/api/endpoints/connector_runtime.py
  • backend/app/core/config.py
  • backend/app/models/__init__.py
  • backend/app/models/connector.py
  • backend/app/schemas/connector.py
  • backend/app/services/connector_apps.py
  • backend/app/services/connector_runtime.py
  • backend/pyproject.toml
  • backend/tests/api/test_connector_apps_api.py
  • backend/tests/services/test_connector_runtime.py
  • docs/en/developer-guide/connector-apps.md
  • docs/zh/developer-guide/connector-apps.md
  • executor/src/bin/wegent-executor.rs
  • executor/src/connector_gateway.rs
  • executor/src/connector_mcp.rs
  • executor/src/lib.rs
  • executor/src/runtime_work/connectors.rs
  • executor/src/runtime_work/handler.rs
  • executor/src/runtime_work/mod.rs
  • executor/tests/config_contract.rs
  • frontend/src/__tests__/features/admin/components/ConnectorAppList.test.tsx
  • frontend/src/apis/admin.ts
  • frontend/src/app/admin/page.tsx
  • frontend/src/features/admin/components/AdminTabNav.tsx
  • frontend/src/features/admin/components/ConnectorAppList.tsx
  • frontend/src/i18n/locales/en/admin.json
  • frontend/src/i18n/locales/zh-CN/admin.json
  • wework/scripts/ai-verify-environment.mjs
  • wework/scripts/ai-verify-environment.test.mjs
  • wework/src/api/cloud/connectorApps.ts
  • wework/src/components/chat/composer/composerMentionCandidates.test.ts
  • wework/src/components/chat/composer/composerMentionCandidates.ts
  • wework/src/e2e/automation.ts
  • wework/src/features/cloud-connection/LocalExecutorCloudBridge.test.tsx
  • wework/src/features/cloud-connection/LocalExecutorCloudBridge.tsx
  • wework/src/features/cloud-connection/localExecutorCloudConnection.ts
  • wework/src/features/workbench/WorkbenchProvider.tsx
  • wework/src/types/api.ts

Comment on lines +21 to +24
op.add_column(
"connector_apps",
sa.Column("http_tools", sa.JSON(), nullable=False, server_default="[]"),
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

MySQL JSON column literal DEFAULT value not allowed 8.0.13 expression default

💡 Result:

In MySQL, JSON columns can have a default value starting from version 8.0.13 [1][2][3]. Prior to this version, it was not possible to assign a non-NULL default value to a JSON column [1][4]. As of MySQL 8.0.13, you can assign a default value to a JSON column, but it must be specified as an expression, even if the value itself is a literal constant [5][4]. To achieve this, you must enclose the default value within parentheses [5][6]. Example of correct syntax: CREATE TABLE t1 ( j JSON DEFAULT (JSON_ARRAY)); Or for a JSON object: CREATE TABLE t1 ( j JSON DEFAULT ('{"key": "value"}')); If you do not use parentheses, or if you attempt to use a literal without an expression, MySQL will raise an error because JSON, BLOB, TEXT, and GEOMETRY data types do not support literal default values [5][4][6]. Note on ALTER TABLE: When using ALTER TABLE... ALTER COLUMN to set a default value, some versions of MySQL may exhibit inconsistent behavior if the expression is interpreted as a simple string literal rather than a functional expression. Using MODIFY COLUMN is generally recommended to ensure the default is correctly stored as an expression default [7].

Citations:


Use a MySQL-safe JSON default
MySQL JSON columns need an expression default (DEFAULT (...)), not a bare string literal, so server_default="[]" will break on MySQL. Use a backfill or a server default expression instead.

🤖 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 `@backend/alembic/versions/20260720_f8a9b0c1d2e3_add_connector_http_tools.py`
around lines 21 - 24, Update the http_tools column definition in the migration
to use a MySQL-compatible JSON default expression or backfill existing rows
before enforcing non-nullability, replacing the bare server_default string while
preserving the empty-array default for new records.

Comment on lines +227 to +229
# Optional public Backend root used for third-party connector OAuth callbacks.
# Configure this when Backend is behind a reverse proxy with a different origin.
CONNECTOR_OAUTH_CALLBACK_BASE_URL: str = ""

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== config.py excerpt =="
sed -n '200,250p' backend/app/core/config.py

echo
echo "== connector_apps.py excerpt =="
sed -n '1,240p' backend/app/api/endpoints/connector_apps.py

echo
echo "== dependency declarations mentioning pydantic =="
rg -n "pydantic|pydantic-settings|BaseSettings|HttpUrl|AnyUrl|HttpUrl" pyproject.toml backend/ -S

Repository: wecode-ai/Wegent

Length of output: 50372


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== config.py file stats =="
wc -l backend/app/core/config.py

echo "== connector_apps.py file stats =="
wc -l backend/app/api/endpoints/connector_apps.py

Repository: wecode-ai/Wegent

Length of output: 295


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== config class outline =="
ast-grep outline backend/app/core/config.py --view expanded | sed -n '1,220p'

echo
echo "== config validators and URL-related types =="
rg -n "field_validator|model_validator|AnyHttpUrl|HttpUrl|AnyUrl|UrlConstraints|CONNECTOR_OAUTH_CALLBACK_BASE_URL|WEWORK_AUTHORIZE_BASE_URL|SITES_API_BASE_URL" backend/app/core/config.py backend/app/schemas/site.py backend/app/schemas/connector.py -S

echo
echo "== relevant pydantic-settings usage =="
rg -n "BaseSettings|SettingsConfigDict|env_prefix|env_file" backend/app/core/config.py -S

Repository: wecode-ai/Wegent

Length of output: 13585


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path
path = Path("backend/app/core/config.py")
text = path.read_text()
for key in [
    "class Settings",
    "CONNECTOR_OAUTH_CALLBACK_BASE_URL",
    "field_validator",
    "model_validator",
    "BaseSettings",
    "SettingsConfigDict",
]:
    print(f"{key}: {text.find(key)}")
PY

Repository: wecode-ai/Wegent

Length of output: 298


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== config.py class and field definitions (selected lines) =="
nl -ba backend/app/core/config.py | sed -n '1,260p'

echo
echo "== site schema URL field types =="
nl -ba backend/app/schemas/site.py | sed -n '1,120p'

echo
echo "== connector schema types =="
nl -ba backend/app/schemas/connector.py | sed -n '1,220p'

Repository: wecode-ai/Wegent

Length of output: 255


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== config.py outline =="
ast-grep outline backend/app/core/config.py --view expanded | sed -n '1,160p'

echo
echo "== precise lines around settings class =="
nl -ba backend/app/core/config.py | sed -n '1,120p'

echo
echo "== precise lines around the OAuth callback field =="
nl -ba backend/app/core/config.py | sed -n '210,240p'

Repository: wecode-ai/Wegent

Length of output: 8524


🌐 Web query:

Pydantic v2 AnyHttpUrl query fragment allowed base url validation http https no query fragment

💡 Result:

In Pydantic v2, AnyHttpUrl and HttpUrl do not explicitly restrict the presence of query strings or URL fragments by default [1][2]. These types are designed to accept valid HTTP or HTTPS URLs, which naturally support optional components like query parameters and fragments [1][3]. When you use AnyHttpUrl or HttpUrl in a model, Pydantic parses the input URL into its constituent parts—scheme, host, port, path, query, and fragment—and makes them available as properties [1][2]. Because these components are optional parts of a standard URL, Pydantic does not reject a URL simply because it contains a query string or a fragment [1][2]. If you specifically need to disallow query strings or fragments, you must implement custom validation. You can achieve this by adding a validator to your model field that checks the query and fragment properties of the parsed URL object [1][2]. Example implementation: from pydantic import BaseModel, HttpUrl, field_validator, ValidationInfo class MyModel(BaseModel): url: HttpUrl @field_validator('url') @classmethod def validate_no_query_or_fragment(cls, v: HttpUrl) -> HttpUrl: if v.query is not None or v.fragment is not None: raise ValueError('Query strings and fragments are not allowed') return v This approach allows you to enforce stricter validation rules than the default Pydantic network types provide [1][2].

Citations:


Validate CONNECTOR_OAUTH_CALLBACK_BASE_URL

It’s concatenated directly into the OAuth redirect URI. Reject non-absolute or non-HTTP(S) values, forbid query/fragment components, and require HTTPS outside local/dev environments so a bad config can’t break OAuth or expose auth codes.

🤖 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 `@backend/app/core/config.py` around lines 227 - 229, Validate
CONNECTOR_OAUTH_CALLBACK_BASE_URL as an absolute HTTP(S) URL without query or
fragment components before using it for OAuth redirect construction. Require
HTTPS unless running in the existing local/development environment, and reject
invalid values through the configuration validation path.

Comment on lines +84 to +114
@model_validator(mode="after")
def validate_argument_locations(self) -> "ConnectorHttpToolDefinition":
properties = self.input_schema.get("properties", {})
unknown = set(self.argument_locations) - set(properties)
if unknown:
raise ValueError(
"argument_locations must reference input_schema properties: "
+ ", ".join(sorted(unknown))
)
path_arguments = {
name
for name, location in self.argument_locations.items()
if location == "path"
}
missing_placeholders = {
name for name in path_arguments if "{" + name + "}" not in self.path
}
if missing_placeholders:
raise ValueError(
"path arguments require matching placeholders: "
+ ", ".join(sorted(missing_placeholders))
)
placeholders = set(re.findall(r"\{([A-Za-z0-9_.-]+)\}", self.path))
required = set(self.input_schema.get("required", []))
invalid_placeholders = placeholders - set(properties)
optional_placeholders = placeholders - required
if invalid_placeholders or optional_placeholders:
raise ValueError(
"path placeholders must be required input_schema properties"
)
return self

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Path placeholders can bypass argument_locations location check.

The validator confirms placeholders are required schema properties, but never confirms the reverse: a property whose argument_locations entry is "query"/"body" can still appear as a {name} placeholder in path without any error. That configuration is accepted, yet at request-build time it's ambiguous whether {name} gets substituted into the path or sent as a query/body param, likely leaving a literal {name} token in the outgoing URL.

🐛 Proposed fix: require path arguments and path placeholders to match exactly
     `@model_validator`(mode="after")
     def validate_argument_locations(self) -> "ConnectorHttpToolDefinition":
         properties = self.input_schema.get("properties", {})
         unknown = set(self.argument_locations) - set(properties)
         if unknown:
             raise ValueError(
                 "argument_locations must reference input_schema properties: "
                 + ", ".join(sorted(unknown))
             )
-        path_arguments = {
-            name
-            for name, location in self.argument_locations.items()
-            if location == "path"
-        }
-        missing_placeholders = {
-            name for name in path_arguments if "{" + name + "}" not in self.path
-        }
-        if missing_placeholders:
-            raise ValueError(
-                "path arguments require matching placeholders: "
-                + ", ".join(sorted(missing_placeholders))
-            )
         placeholders = set(re.findall(r"\{([A-Za-z0-9_.-]+)\}", self.path))
         required = set(self.input_schema.get("required", []))
         invalid_placeholders = placeholders - set(properties)
         optional_placeholders = placeholders - required
         if invalid_placeholders or optional_placeholders:
             raise ValueError(
                 "path placeholders must be required input_schema properties"
             )
+        path_arguments = {
+            name
+            for name, location in self.argument_locations.items()
+            if location == "path"
+        }
+        if path_arguments != placeholders:
+            raise ValueError(
+                "path arguments and path placeholders must match exactly"
+            )
         return self
📝 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.

Suggested change
@model_validator(mode="after")
def validate_argument_locations(self) -> "ConnectorHttpToolDefinition":
properties = self.input_schema.get("properties", {})
unknown = set(self.argument_locations) - set(properties)
if unknown:
raise ValueError(
"argument_locations must reference input_schema properties: "
+ ", ".join(sorted(unknown))
)
path_arguments = {
name
for name, location in self.argument_locations.items()
if location == "path"
}
missing_placeholders = {
name for name in path_arguments if "{" + name + "}" not in self.path
}
if missing_placeholders:
raise ValueError(
"path arguments require matching placeholders: "
+ ", ".join(sorted(missing_placeholders))
)
placeholders = set(re.findall(r"\{([A-Za-z0-9_.-]+)\}", self.path))
required = set(self.input_schema.get("required", []))
invalid_placeholders = placeholders - set(properties)
optional_placeholders = placeholders - required
if invalid_placeholders or optional_placeholders:
raise ValueError(
"path placeholders must be required input_schema properties"
)
return self
`@model_validator`(mode="after")
def validate_argument_locations(self) -> "ConnectorHttpToolDefinition":
properties = self.input_schema.get("properties", {})
unknown = set(self.argument_locations) - set(properties)
if unknown:
raise ValueError(
"argument_locations must reference input_schema properties: "
", ".join(sorted(unknown))
)
placeholders = set(re.findall(r"\{([A-Za-z0-9_.-]+)\}", self.path))
required = set(self.input_schema.get("required", []))
invalid_placeholders = placeholders - set(properties)
optional_placeholders = placeholders - required
if invalid_placeholders or optional_placeholders:
raise ValueError(
"path placeholders must be required input_schema properties"
)
path_arguments = {
name
for name, location in self.argument_locations.items()
if location == "path"
}
if path_arguments != placeholders:
raise ValueError(
"path arguments and path placeholders must match exactly"
)
return self
🤖 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 `@backend/app/schemas/connector.py` around lines 84 - 114, Update
ConnectorHttpToolDefinition.validate_argument_locations so the set of path
placeholders in self.path must exactly match the names in argument_locations
whose location is "path". Reject both placeholders missing a corresponding path
argument and path arguments missing placeholders, while preserving the existing
schema-property and required-property validation.

Comment on lines +44 to +93
pub(crate) async fn request(
&self,
method: Method,
path: &str,
body: Option<Value>,
) -> Result<Value, ConnectorGatewayError> {
if self.expires_at_ms <= now_ms() + 5_000 {
return Err(ConnectorGatewayError::new(
"connector_token_expired",
"Wegent connector authorization expired; reconnect cloud",
));
}
let url = format!(
"{}/connector-runtime/{}",
self.api_base_url,
path.trim_start_matches('/')
);
let client = reqwest::Client::new();
let mut request = client
.request(method, &url)
.bearer_auth(&self.connector_token)
.timeout(Duration::from_secs(75));
if let Some(body) = body {
request = request.json(&body);
}
let response = request.send().await.map_err(|error| {
ConnectorGatewayError::new(
"connector_gateway_unavailable",
format!("Wegent connector gateway is unavailable: {error}"),
)
})?;
let status = response.status();
let value = response.json::<Value>().await.map_err(|error| {
ConnectorGatewayError::new(
"connector_gateway_invalid_response",
format!("Invalid connector gateway response: {error}"),
)
})?;
if !status.is_success() {
let message = value
.get("detail")
.and_then(Value::as_str)
.unwrap_or("Connector gateway request failed");
return Err(ConnectorGatewayError::new(
"connector_gateway_error",
format!("{message} (HTTP {status})"),
));
}
Ok(value)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Potential timeout-layering mismatch on the gateway request.

The gateway request timeout is fixed at 75s, but the MCP child is registered with tool_timeout_sec: 180 (in runtime_work/connectors.rs) and the backend MCP session uses 180s reads. A backend tool that runs between 75s and 180s will be aborted here as connector_gateway_unavailable even though Codex would still be waiting. Consider aligning this timeout with the downstream tool timeout (or making it configurable).

🤖 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 `@executor/src/connector_gateway.rs` around lines 44 - 93, Update the timeout
configured in ConnectorGateway::request to align with the downstream MCP
tool/session timeout of 180 seconds, or source it from the existing configurable
timeout setting if one is available. Preserve the existing request construction
and connector_gateway_unavailable error handling.

Comment on lines +346 to +368
<div className="flex shrink-0 gap-1">
<Button
variant="ghost"
size="icon"
className="h-8 w-8"
onClick={() => openEdit(app)}
data-testid={`edit-connector-app-${app.id}`}
aria-label={t('connector_apps.edit')}
>
<Pencil className="h-4 w-4" />
</Button>
{app.enabled ? (
<Button
variant="ghost"
size="icon"
className="h-8 w-8 hover:text-error"
onClick={() => void disable(app)}
data-testid={`disable-connector-app-${app.id}`}
aria-label={t('connector_apps.disable')}
>
<Power className="h-4 w-4" />
</Button>
) : null}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Card action buttons are below the mobile touch-target minimum.

The edit/disable icon buttons render at h-8 w-8 (32px). The admin surface is mobile-responsive (the tab nav switches to a dropdown on mobile), so these controls should meet the 44px minimum on mobile (e.g. responsive sizing that enlarges the hit area under md).

As per coding guidelines: "Mobile controls must be at least 44px × 44px."

🤖 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 `@frontend/src/features/admin/components/ConnectorAppList.tsx` around lines 346
- 368, Update the edit and disable buttons in the ConnectorAppList action group
to provide at least a 44px × 44px touch target on mobile, using responsive
sizing that may retain the existing 32px dimensions at md and larger. Preserve
their current behavior, styling, labels, and test IDs.

Source: Coding guidelines

Comment on lines +687 to +696
{t('common.cancel')}
</Button>
<Button
variant="primary"
onClick={onSave}
disabled={saving}
data-testid="save-connector-app-button"
>
{saving ? <Loader2 className="mr-2 h-4 w-4 animate-spin" /> : null}
{t('common.save')}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check whether admin.json defines a top-level `common` block with cancel/save
fd -t f 'admin.json' frontend/src/i18n/locales --exec sh -c 'echo "== $1 =="; jq -e ".common // empty | {cancel, save}" "$1" 2>/dev/null || echo "no common.cancel/common.save"' _ {}

Repository: wecode-ai/Wegent

Length of output: 329


Use the admin translation keys here
frontend/src/i18n/locales/en/admin.json and zh-CN/admin.json define cancel and save at the top level, not under common, so these buttons should use t('cancel') / t('save') (or a shared common: namespace if that’s the intent).

🤖 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 `@frontend/src/features/admin/components/ConnectorAppList.tsx` around lines 687
- 696, Update the cancel and save button labels in the ConnectorAppList
component to use the admin translation keys at the correct namespace, replacing
the current common.cancel and common.save lookups with t('cancel') and t('save')
(or the established shared common namespace if intentionally configured).

Source: Coding guidelines

Comment on lines +208 to +209
const authToken =
import.meta.env.VITE_WEWORK_E2E_CLOUD_AUTH_TOKEN?.trim() || 'wework-desktop-e2e-cloud-token'

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Remove the committed auth-token fallback.

Line 209 keeps a credential-like token in source and silently uses it when the environment is misconfigured. Require VITE_WEWORK_E2E_CLOUD_AUTH_TOKEN and fail the E2E setup if it is missing, while ensuring this variable is only supplied in isolated E2E builds.

As per coding guidelines, credentials must not be committed and secrets must come from environment configuration.

Proposed fix
-  const authToken =
-    import.meta.env.VITE_WEWORK_E2E_CLOUD_AUTH_TOKEN?.trim() || 'wework-desktop-e2e-cloud-token'
+  const authToken = import.meta.env.VITE_WEWORK_E2E_CLOUD_AUTH_TOKEN?.trim()
+  if (!authToken) {
+    throw new Error('VITE_WEWORK_E2E_CLOUD_AUTH_TOKEN is required')
+  }
📝 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.

Suggested change
const authToken =
import.meta.env.VITE_WEWORK_E2E_CLOUD_AUTH_TOKEN?.trim() || 'wework-desktop-e2e-cloud-token'
const authToken = import.meta.env.VITE_WEWORK_E2E_CLOUD_AUTH_TOKEN?.trim()
if (!authToken) {
throw new Error('VITE_WEWORK_E2E_CLOUD_AUTH_TOKEN is required')
}
🤖 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 `@wework/src/e2e/automation.ts` around lines 208 - 209, Update the authToken
initialization in the E2E setup to require a non-empty
VITE_WEWORK_E2E_CLOUD_AUTH_TOKEN value instead of using the committed fallback
token. Fail setup immediately with a clear configuration error when the variable
is missing, and ensure the variable is provided only through isolated E2E build
configuration.

Source: Coding guidelines

Comment on lines +84 to +88
await requestLocalExecutor('runtime.connectors.apps.sync', {
apps: apps
.filter(app => app.connection.status === 'connected')
.map(app => ({ slug: app.slug, name: app.name })),
})

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Include description in the sync payload to match WorkbenchProvider.

The payload provided here omits description, while WorkbenchProvider includes it when calling runtime.connectors.apps.sync (i.e., description: app.description). This discrepancy could result in the Executor occasionally receiving empty descriptions for Connector Apps when synchronized from the background.

🛠️ Proposed fix
         await requestLocalExecutor('runtime.connectors.apps.sync', {
           apps: apps
             .filter(app => app.connection.status === 'connected')
-            .map(app => ({ slug: app.slug, name: app.name })),
+            .map(app => ({ slug: app.slug, name: app.name, description: app.description })),
         })
📝 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.

Suggested change
await requestLocalExecutor('runtime.connectors.apps.sync', {
apps: apps
.filter(app => app.connection.status === 'connected')
.map(app => ({ slug: app.slug, name: app.name })),
})
await requestLocalExecutor('runtime.connectors.apps.sync', {
apps: apps
.filter(app => app.connection.status === 'connected')
.map(app => ({ slug: app.slug, name: app.name, description: app.description })),
})
🤖 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 `@wework/src/features/cloud-connection/LocalExecutorCloudBridge.tsx` around
lines 84 - 88, Update the payload mapping in LocalExecutorCloudBridge to include
each connected app’s description alongside slug and name, matching the
runtime.connectors.apps.sync payload used by WorkbenchProvider.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant