Skip to content

Feature/sites auto install plugin - #2228

Merged
qdaxb merged 1 commit into
wecode-ai:mainfrom
FicoHu:feature/sites-auto-install-plugin
Jul 29, 2026
Merged

qdaxb merged 1 commit into
wecode-ai:mainfrom
FicoHu:feature/sites-auto-install-plugin

Conversation

@FicoHu

@FicoHu FicoHu commented Jul 27, 2026 •

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

  • New Features
    • Added built-in Sites plugin installation when creating a site, with duplicate installs prevented.
    • Plugin mentions in messages and the composer can now be opened to view plugin details.
    • Added support for opening plugin details directly from navigation links.
  • Bug Fixes
    • Improved Sites setup error messages for unavailable cloud services, required upgrades, unpublished plugins, and installation failures.
    • Added clearer create and retry behavior when Sites setup is unavailable.
  • Documentation
    • Updated plugin and Sites documentation, including cloud marketplace and local-mode requirements.

@coderabbitai

coderabbitai Bot commented Jul 27, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR adds a bundled Wegent Sites plugin lifecycle, including build staging, backend publication, idempotent installation, Sites UI integration, desktop E2E coverage, and documentation. It also adds plugin mention/detail navigation and documents explicit terminal-launch behavior.

Changes

Built-in Sites plugin lifecycle

Layer / File(s) Summary
Plugin staging and publication
.env.example, backend/.env.example, scripts/*, build_image*.sh, docker-compose.yml, backend/app/services/*
The bundled wegent-sites plugin is staged during builds, packaged deterministically, published at backend startup, and installed through a validated builtin-plugin service.
Sites creation flow
wework/src/pages/SitesPage.tsx, wework/src/components/sites/*, wework/src/api/plugins.ts, wework/src/features/plugins/*
Sites creation ensures the builtin plugin, queues its trial reference, reports cloud/install errors, and supports retry actions.
Validation and documentation
backend/tests/services/*, wework/src/App.plugins.test.tsx, wework/e2e/desktop/task-flow.e2e.mjs, docs/*
Backend, frontend, desktop, and marketplace documentation cover installation, identity, synchronization, and plugin detail access.

Plugin mention and detail navigation

Layer / File(s) Summary
Navigation contracts
wework/src/features/plugins/pluginNavigation.ts, wework/src/features/plugins/pluginNavigation.test.ts
Plugin references, URIs, mention strings, marketplace aliases, and detail routes can be parsed, formatted, and validated.
Composer and chat navigation
wework/src/components/chat/*
Plugin mentions become focusable interactive elements and open plugin detail routes from mouse, keyboard, and chat-link interactions.
Plugin workspace resolution
wework/src/components/plugins/*, wework/src/api/local/codexPlugins.ts, wework/src/pages/PluginsPage.tsx
Plugin detail routes resolve against local or cloud marketplace data and installed plugin identities, with authenticated cloud API support and back navigation.

Explicit terminal launch design

Layer / File(s) Summary
Terminal launch plan and specification
docs/superpowers/plans/*, docs/superpowers/specs/*
Planning and design documents define explicit Terminal selection, shared tool actions, capability-aware menus, terminal preservation, error handling, tests, and E2E verification.

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

Possibly related PRs

Suggested reviewers: qdaxb

Sequence Diagram(s)

sequenceDiagram
  participant Build
  participant Backend
  participant SitesPage
  participant PluginAPI
  participant PluginWorkspace
  Build->>Backend: stage and publish wegent-sites
  SitesPage->>PluginAPI: ensure builtin plugin installed
  PluginAPI-->>SitesPage: installed plugin metadata
  SitesPage->>SitesPage: queue plugin trial reference
  SitesPage->>PluginWorkspace: navigate to plugin detail route
  PluginWorkspace->>PluginAPI: load marketplace and installed plugin data
  PluginWorkspace-->>SitesPage: render matching plugin detail
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 6.52% 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 matches the main change: adding automatic installation flow for the Sites plugin.
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

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.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 89aca53f2e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread wework/package.json Outdated
"prepare:bundled-plugins": "node scripts/sync-bundled-sites-plugin.mjs",
"prepare:codex": "node scripts/prepare-codex-binary.mjs",
"build": "tsc -b && vite build",
"build:tauri": "pnpm run prepare:bundled-plugins && pnpm run build",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Provision the bundled plugin in clean builds

In a clean checkout, build:tauri always runs prepare:bundled-plugins, but the sync script requires an untracked sibling repository at ../wegent-skills (or WEWORK_SITES_PLUGIN_SOURCE). The existing desktop E2E and macOS/Windows release jobs in .github/workflows/wework-e2e.yml and .github/workflows/wework-app.yml check out only this repository and provide neither source, so every tauri build now fails before compilation with “Sites plugin not found”; the plugin must be supplied or fetched as part of those builds, ideally from a pinned source.

AGENTS.md reference: wework/AGENTS.md:L32-L33

Useful? React with 👍 / 👎.

@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: 2

🧹 Nitpick comments (1)
wework/src/api/local/codexPlugins.ts (1)

788-854: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider decomposing ensureLocalMarketplacePlugin.

The method inlines validation, existing-plugin lookup, marketplace reconciliation, registration, path verification, and install+verify in one ~67-line block. Extracting helpers (e.g. ensureMarketplaceConfigured(reference, state) and installAndVerifyPlugin(...)) would ease future maintenance without changing behavior.

🤖 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/api/local/codexPlugins.ts` around lines 788 - 854, Decompose
ensureLocalMarketplacePlugin into focused helpers while preserving its behavior:
keep runtime/reference validation and existing-plugin lookup in the main flow,
extract marketplace reconciliation/registration into an
ensureMarketplaceConfigured helper, and extract plugin installation plus
refreshed-state verification into an installAndVerifyPlugin helper. Reuse the
existing state and marketplace symbols so error handling and return behavior
remain unchanged.
🤖 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 `@docs/superpowers/plans/2026-07-22-wework-explicit-terminal-launch.md`:
- Around line 77-94: Resolve the undefined terminalSequenceRef in
openTerminalTab by either introducing and consistently updating that ref for
every terminal-tab creation path, or retaining the existing terminalSequence
state with a functional updater. Ensure initial and reopen-after-empty creation
still call createTerminalTab(index) without a tool, while the add-menu path
opens with the terminal tool.
- Around line 907-915: Update the final-diff verification commands in Step 6 to
use the target branch merge base, such as git merge-base main HEAD, instead of
the fixed HEAD~4 base. Apply the stable base consistently to the git diff
--check and git diff --stat commands while preserving the existing git status
check and expected-results guidance.

---

Nitpick comments:
In `@wework/src/api/local/codexPlugins.ts`:
- Around line 788-854: Decompose ensureLocalMarketplacePlugin into focused
helpers while preserving its behavior: keep runtime/reference validation and
existing-plugin lookup in the main flow, extract marketplace
reconciliation/registration into an ensureMarketplaceConfigured helper, and
extract plugin installation plus refreshed-state verification into an
installAndVerifyPlugin helper. Reuse the existing state and marketplace symbols
so error handling and return behavior remain unchanged.
🪄 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 Plus

Run ID: d92808ef-ef3e-4d55-bed4-38dcb80474c5

📥 Commits

Reviewing files that changed from the base of the PR and between 7e423a2 and 89aca53.

📒 Files selected for processing (20)
  • .gitignore
  • docs/superpowers/plans/2026-07-22-wework-explicit-terminal-launch.md
  • docs/superpowers/specs/2026-07-22-wework-explicit-terminal-launch-design.md
  • wework/README.md
  • wework/e2e/desktop/task-flow.e2e.mjs
  • wework/package.json
  • wework/scripts/dev-mac-app.sh
  • wework/scripts/sync-bundled-sites-plugin.mjs
  • wework/src-tauri/bundled-plugins/wegent-marketplace/.agents/plugins/marketplace.json
  • wework/src-tauri/src/bundled_plugins.rs
  • wework/src-tauri/src/lib.rs
  • wework/src-tauri/tauri.conf.json
  • wework/src/App.plugins.test.tsx
  • wework/src/api/local/codexPlugins.test.ts
  • wework/src/api/local/codexPlugins.ts
  • wework/src/i18n/locales/en/sites.json
  • wework/src/i18n/locales/zh-CN/sites.json
  • wework/src/lib/bundled-plugins.test.ts
  • wework/src/lib/bundled-plugins.ts
  • wework/src/pages/SitesPage.tsx

Comment on lines +77 to +94
function createTerminalTab(
index: number,
defaultOpenTool?: BottomWorkspacePanelTab['defaultOpenTool']
): BottomWorkspacePanelTab {
return { id: `terminal-${index}`, title: `Terminal ${index}`, defaultOpenTool }
}
```

Keep both the initial state and the reopen-after-empty effect on `createTerminalTab(index)` with no tool. Change the existing add-menu Terminal path to call:

```tsx
const openTerminalTab = () => {
const tab = createTerminalTab(terminalSequenceRef.current, 'terminal')
terminalSequenceRef.current += 1
setTabs(current => [...current, tab])
setActiveTabId(tab.id)
}
```

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 | 🔴 Critical | ⚡ Quick win

Define terminalSequenceRef or keep the existing state-based sequence.

The proposed openTerminalTab uses terminalSequenceRef.current, but the plan never introduces that ref or replaces the existing terminalSequence state used by BottomWorkspacePanel.tsx. Implementing this literally will fail type checking. Either define and consistently update the ref across all tab-creation paths, or retain the existing functional state updater.

🤖 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 `@docs/superpowers/plans/2026-07-22-wework-explicit-terminal-launch.md` around
lines 77 - 94, Resolve the undefined terminalSequenceRef in openTerminalTab by
either introducing and consistently updating that ref for every terminal-tab
creation path, or retaining the existing terminalSequence state with a
functional updater. Ensure initial and reopen-after-empty creation still call
createTerminalTab(index) without a tool, while the add-menu path opens with the
terminal tool.

Comment on lines +907 to +915
- [ ] **Step 6: Review the final diff against the specification**

```bash
git diff HEAD~4 --check
git diff HEAD~4 --stat
git status --short
```

Expected: no whitespace errors, only the planned Wework component/test/E2E files differ, and the worktree is clean after the planned commits.

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

Use a stable diff base instead of HEAD~4.

HEAD~4 assumes the repository has exactly four preceding implementation commits, which is not reliable after squashing, extra commits, or committing these documents separately. Use the target-branch merge base instead, such as git diff "$(git merge-base main HEAD)" HEAD --check, so the final verification examines the actual PR diff.

🤖 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 `@docs/superpowers/plans/2026-07-22-wework-explicit-terminal-launch.md` around
lines 907 - 915, Update the final-diff verification commands in Step 6 to use
the target branch merge base, such as git merge-base main HEAD, instead of the
fixed HEAD~4 base. Apply the stable base consistently to the git diff --check
and git diff --stat commands while preserving the existing git status check and
expected-results guidance.

@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: 1

🤖 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 `@pnpm-workspace.yaml`:
- Around line 13-16: Update the packageExtensions selector for tiptap-markdown
to use the same ^0.8.10 range declared by the package dependency, ensuring the
`@tiptap/pm` injection applies to all compatible resolved versions.
🪄 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 Plus

Run ID: b86abe89-00f7-4c6d-bd08-92b22a98fda3

📥 Commits

Reviewing files that changed from the base of the PR and between 89aca53 and 3de9d84.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (1)
  • pnpm-workspace.yaml

Comment thread pnpm-workspace.yaml
Comment on lines +13 to +16
packageExtensions:
'tiptap-markdown@0.8.10':
dependencies:
'@tiptap/pm': ^2.27.2

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 | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

python - <<'PY'
import json
import re

with open("wework/package.json") as f:
    spec = json.load(f)["dependencies"]["tiptap-markdown"]

workspace = open("pnpm-workspace.yaml").read()
match = re.search(r"'tiptap-markdown@([^']+)'", workspace)
assert match, "packageExtensions selector missing"

selector = match.group(1)
assert selector == spec, f"selector {selector!r} does not match dependency {spec!r}"
PY

rg -n "tiptap-markdown|`@tiptap/pm`" pnpm-lock.yaml

Repository: wecode-ai/Wegent

Length of output: 298


🌐 Web query:

pnpm packageExtensions semver range selector tilde caret exact version documentation

💡 Result:

The pnpm packageExtensions feature allows you to extend package definitions with additional dependencies, optionalDependencies, peerDependencies, and peerDependenciesMeta [1][2][3]. In the packageExtensions configuration, keys act as selectors to target specific packages [1][3]. These selectors support package names optionally followed by a semver range [1][3]. Key syntax rules for packageExtensions selectors: - Package Name Only: Using just the package name (e.g., react-redux) applies the extension to all versions of that package [1][3]. - Semver Range: You can append a semver range (e.g., react-redux@1 or react-redux@^2.0.0) to target only the versions of the package that satisfy that specific range [1][3][4]. - Semver Compatibility: The underlying implementation uses the semver library to check if a package's version satisfies the specified range selector [4]. You can use standard semver operators, including tildes (~), carets (^), and exact versions, as they are part of valid semver range expressions [4]. Configuration location: In recent versions of pnpm, packageExtensions must be defined in the pnpm-workspace.yaml file (or equivalent project configuration) [1][5]. Historically, it was sometimes configured within the pnpm field of package.json, but this is no longer supported in newer versions [6][5]. Example: packageExtensions: react-redux@1: peerDependencies: react-dom: "*" express@^4.0.0: dependencies: some-dep: "1.2.3" When a package is installed, pnpm uses these selectors to determine which extensions to apply to that package's manifest [4]. If the version of the package matches the selector's range, the defined dependencies are merged into the package's existing manifest fields [4].

Citations:


🌐 Web query:

pnpm-lock.yaml tiptap-markdown 0.8.10 @tiptap/pm pnpm-lock.yaml

💡 Result:

The package tiptap-markdown version 0.8.10 is a community-maintained extension for the Tiptap editor [1][2][3]. It is distinct from the official @tiptap/markdown package, which is maintained by the Tiptap team [4]. Regarding the relationship between tiptap-markdown@0.8.10 and @tiptap/pm: 1. Dependency Issue: Users have reported issues where @tiptap/pm was incorrectly listed as a devDependency in certain versions of tiptap-markdown, leading to resolution errors in strict environments like Yarn PnP or when strict dependency checking is enabled [5]. 2. pnpm-lock.yaml Context: In a pnpm-lock.yaml file, tiptap-markdown will typically be listed alongside its required dependencies [6][7]. Because tiptap-markdown (specifically the community version) relies on ProseMirror functionality provided by Tiptap, it requires @tiptap/pm and @tiptap/core as peer or runtime dependencies [1][3]. 3. Versioning: tiptap-markdown version 0.8.10 was released in May 2025 [1]. When using pnpm, the pnpm-lock.yaml file will record the specific dependency graph, often showing tiptap-markdown linked to the specific versions of @tiptap/core and @tiptap/pm it was installed with [6][7]. If you are encountering errors related to @tiptap/pm in your project's pnpm-lock.yaml or build process, it is likely due to a mismatch between the expected peer dependency version of @tiptap/pm and what is installed in your project [5]. Ensure that your project's Tiptap version aligns with the requirements of the tiptap-markdown extension you are using [8]. Note that official Tiptap ecosystem tools are now often scoped under @tiptap/, so verify if you should be using the official @tiptap/markdown instead [4].

Citations:


Align the packageExtensions selector with the tiptap-markdown dependency range.

wework/package.json allows tiptap-markdown versions ^0.8.10, while packageExtensions only targets 0.8.10. If the lockfile refreshes to 0.8.11 or another compatible version, the injected @tiptap/pm dependency can drop. Use 'tiptap-markdown@^0.8.10', or pin the package dependency to 0.8.10.

Suggested change
-  'tiptap-markdown@0.8.10':
+  'tiptap-markdown@^0.8.10':
📝 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
packageExtensions:
'tiptap-markdown@0.8.10':
dependencies:
'@tiptap/pm': ^2.27.2
packageExtensions:
'tiptap-markdown@^0.8.10':
dependencies:
'`@tiptap/pm`': ^2.27.2
🤖 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 `@pnpm-workspace.yaml` around lines 13 - 16, Update the packageExtensions
selector for tiptap-markdown to use the same ^0.8.10 range declared by the
package dependency, ensuring the `@tiptap/pm` injection applies to all compatible
resolved versions.

@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: 1

🧹 Nitpick comments (1)
wework/src/components/plugins/PluginsWorkspace.tsx (1)

292-295: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Extract and reuse normalizedPluginIdentity.

PluginsWorkspace defines this exact normalizer locally, while codexPlugins.ts has the same private helper for the same case-insensitive identity comparisons. Move it to a shared utility/module and import it here to avoid keeping two identical definitions in sync.

🤖 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/components/plugins/PluginsWorkspace.tsx` around lines 292 - 295,
Move the shared normalizedPluginIdentity helper out of PluginsWorkspace and
codexPlugins.ts into a common utility/module, export it there, and import/reuse
it in both callers. Remove both local private definitions while preserving the
existing trim-and-lowercase behavior for string identities and the empty-string
fallback for non-strings.

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 `@wework/src/components/plugins/PluginsWorkspace.tsx`:
- Around line 711-712: Update the requested plugin deep-link handling around
requestedPluginReference and requestedPluginHandledRef so route search changes
are reparsed and resolved. Make the memo depend on a normalized current search
value, and reset the one-shot handled flag when that route reference changes,
preserving single handling per distinct plugin/marketplace reference.

---

Nitpick comments:
In `@wework/src/components/plugins/PluginsWorkspace.tsx`:
- Around line 292-295: Move the shared normalizedPluginIdentity helper out of
PluginsWorkspace and codexPlugins.ts into a common utility/module, export it
there, and import/reuse it in both callers. Remove both local private
definitions while preserving the existing trim-and-lowercase behavior for string
identities and the empty-string fallback for non-strings.
🪄 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 Plus

Run ID: 9fb0d0b8-a1fc-4428-b3d5-67aa70064205

📥 Commits

Reviewing files that changed from the base of the PR and between aa4e3d0 and a54f136.

📒 Files selected for processing (13)
  • wework/e2e/desktop/task-flow.e2e.mjs
  • wework/src/api/local/codexPlugins.ts
  • wework/src/components/chat/MessageList.test.tsx
  • wework/src/components/chat/MessageList.tsx
  • wework/src/components/chat/composer/ComposerMentionNodeView.ts
  • wework/src/components/chat/composer/ComposerProseMirrorEditor.test.tsx
  • wework/src/components/chat/composer/ComposerProseMirrorEditor.tsx
  • wework/src/components/chat/composer/ComposerTextarea.tsx
  • wework/src/components/chat/composer/composerMentions.ts
  • wework/src/components/plugins/PluginsWorkspace.test.tsx
  • wework/src/components/plugins/PluginsWorkspace.tsx
  • wework/src/features/plugins/pluginNavigation.test.ts
  • wework/src/features/plugins/pluginNavigation.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • wework/e2e/desktop/task-flow.e2e.mjs
  • wework/src/api/local/codexPlugins.ts

Comment on lines +711 to +712
const requestedPluginReference = useMemo(() => parsePluginDetailRoute(window.location.search), [])
const requestedPluginHandledRef = useRef(false)

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 PluginsWorkspace is keyed/remounted per full-search navigation, and readState's marketplaceId semantics
rg -n 'PluginsWorkspace' --type=tsx -g '!*.test.tsx' wework/src -C3
rg -n -A20 'function readState' wework/src/api/local/codexPlugins.ts
rg -n 'popstate' wework/src -C3

Repository: wecode-ai/Wegent

Length of output: 6590


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate relevant files =="
fd -a 'PluginsWorkspace.tsx|codexPlugins.ts|App.tsx|navigation.ts' wework/src | sed 's#^\./##'

echo "== codexPlugins readState =="
sed -n '480,640p' wework/src/api/local/codexPlugins.ts

echo "== AppsPage search route section =="
sed -n '560,720p' wework/src/pages/AppsPage.tsx

echo "== AppsPage MarketplaceWorkspace usage search if applicable =="
rg -n 'MarketplaceWorkspace|readState\(|selectedMarketplaceId|pluginMarketplaceState|readState' wework/src/pages/AppsPage.tsx -C 5

echo "== PluginsWorkspace outline =="
ast-grep outline wework/src/components/plugins/PluginsWorkspace.tsx --view compact | sed -n '1,240p' || true

Repository: wecode-ai/Wegent

Length of output: 11772


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== search route navigation outline =="
ast-grep outline wework/src/pages/plugin-route-navigation.ts --view compact | sed -n '1,220p' || true

echo "== plugin route navigation relevant sections =="
sed -n '1,260p' wework/src/pages/plugin-route-navigation.ts

echo "== marketplace workspace relevant sections =="
sed -n '1320,1595p' wework/src/components/plugins/PluginsWorkspace.tsx

echo "== readState dependency / stale-state usages and skip reload references =="
rg -n 'cachedState|refresh|readState\(|pluginMarketplaceState|selectedMarketplaceId|marketplaceItems|marketplacePath' wework/src/components/plugins/PluginsWorkspace.tsx -C 6

echo "== locate key component route mounts =="
rg -n 'plugin-route-navigation|PluginRouteNavigation|MarketplaceWorkspace|PluginsWorkspace' wework/src --glob '!*.test.*' -C 4

Repository: wecode-ai/Wegent

Length of output: 22707


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== codexPlugins installedPluginMatchesReference =="
rg -n -A25 'function installedPluginMatchesReference|installedPluginMatchesReference' wework/src -C 5

echo "== normalizedPluginIdentity and selectedMarketplaceKey helpers =="
rg -n -A10 'function normalizedPluginIdentity|normalizedPluginIdentity|selectedMarketplaceKey|localMarketplaceKey' wework/src -C 5

echo "== local marketplace API readState implementation =="
rg -n 'async readState|readState.*marketplaceId|marketplaceItems|cachedState' wework/src -C 5

echo "== test behavior relevant to deep link marketplaces if available =="
rg -n 'requestedPluginReference|marketplaceName|marketplaceItems|readState\(' wework/src/components/plugins wework/src/pages wework/src/App.plugins.test.tsx -C 4

Repository: wecode-ai/Wegent

Length of output: 48974


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate parsePluginDetailRoute and useParams hooks =="
rg -n 'function parsePluginDetailRoute|parsePluginDetailRoute|useParams|useSearchParams|useLocation|search' wework/src/pages/PluginsPage.tsx wework/src/components/layout wework/src/App.tsx wework/src/lib -C 4
fd -a 'useSearchParams.*|useLocation.*|useParams.*' wework/src/hooks wework/src/lib | sed 's#^\./##' | xargs -r -I{} sh -c 'echo "--- {}"; cat -n "{}"'

echo "== PluginsPage relevant routing sections =="
sed -n '1,120p' wework/src/pages/PluginsPage.tsx
sed -n '220,285p' wework/src/pages/PluginsPage.tsx

echo "== layout path state effects =="
sed -n '160,215p' wework/src/components/layout/DesktopWorkbenchLayout.tsx
sed -n '220,245p' wework/src/components/layout/MobileWorkbenchLayout.tsx

Repository: wecode-ai/Wegent

Length of output: 50372


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== exact parsePluginDetailRoute definition =="
rg -n -A30 'parsePluginDetailRoute' wework/src -C 6

echo "== focused usage of window.location.search in PluginsWorkspace =="
rg -n 'window\.location\.search|location\.search|parsePluginDetailRoute|requestedPluginReference' wework/src/components/plugins/PluginsWorkspace.tsx -C 4

echo "== focused route matching definitions =="
rg -n 'function getAppsSectionFromLocation|function parse.*Route|section.*from.*location|case \"/plugins\"|\"/plugins\"' wework/src wework/src/pages -C 4

Repository: wecode-ai/Wegent

Length of output: 10762


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== focused route matching definitions =="
rg -n 'getAppsSectionFromLocation|parse.*Route|section.*from.*location|/plugins|plugins' wework/src wework/src/pages -C 4 | sed -n '1,260p'

echo "== PluginsWorkspace imports around parsePluginDetailRoute usage only =="
sed -n '1,35p' wework/src/components/plugins/PluginsWorkspace.tsx

Repository: wecode-ai/Wegent

Length of output: 24078


Re-run deep-link resolution when the plugin route search changes

readState({ marketplaceId }) already scopes marketplace items by marketplace.name, and installed-plugin matching validates both plugin and marketplace names. If PluginsWorkspace is reused for /plugins?...?plugin=…&marketplace=… changes, requestedPluginReference = useMemo(..., []) keeps the initial parsed search while requestedPluginHandledRef blocks later plugin references from being resolved. Reset the one-shot flag or make resolution depend on a normalized route search.

🤖 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/components/plugins/PluginsWorkspace.tsx` around lines 711 - 712,
Update the requested plugin deep-link handling around requestedPluginReference
and requestedPluginHandledRef so route search changes are reparsed and resolved.
Make the memo depend on a normalized current search value, and reset the
one-shot handled flag when that route reference changes, preserving single
handling per distinct plugin/marketplace reference.

@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: 7

🧹 Nitpick comments (2)
wework/src/components/sites/SitesWorkspace.tsx (1)

347-360: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicated Create-button markup across both render branches.

The sitesUnavailable branch's Create button (347-360) is now near-identical to the one in the main render path (415-428) — same classes, same spinner/plus logic, same label. Consider extracting a shared CreateSiteButton sub-component to avoid maintaining two copies in lockstep.

As per coding guidelines, "Before adding code, search for and reuse existing components, services, utilities, and patterns; extract shared logic instead of duplicating it."

♻️ Suggested extraction
function CreateSiteButton({ creating, onCreate, label }: { creating: boolean; onCreate: () => void; label: string }) {
  return (
    <button
      type="button"
      data-testid="sites-create-button"
      disabled={creating}
      onClick={onCreate}
      className="flex h-8 items-center gap-1.5 rounded-lg border border-border bg-background px-3 text-sm font-medium text-text-primary transition-colors hover:bg-surface disabled:cursor-wait disabled:opacity-60"
    >
      {creating ? (
        <Loader2 className="h-3.5 w-3.5 animate-spin" aria-hidden="true" />
      ) : (
        <Plus className="h-3.5 w-3.5" aria-hidden="true" />
      )}
      {label}
    </button>
  )
}

Also applies to: 415-428

🤖 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/components/sites/SitesWorkspace.tsx` around lines 347 - 360,
Extract the duplicated Create button markup into a shared CreateSiteButton
component near the existing SitesWorkspace helpers, accepting creating,
onCreate, and label props. Replace the buttons in both the sitesUnavailable
branch and the main render path with this component, preserving their current
behavior, styling, test ID, icons, disabled state, and translated label.

Source: Coding guidelines

backend/app/services/installed_plugin_service.py (1)

315-319: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Hardcoded user_id=0 duplicates the BUILTIN_PLUGIN_OWNER_ID constant.

builtin_plugin_service.py publishes built-in items with user_id=BUILTIN_PLUGIN_OWNER_ID, but this lookup uses the literal 0. If the constant's value ever changes, this lookup silently breaks while still type-checking fine.

♻️ Proposed fix
+from app.services.builtin_plugin_service import BUILTIN_PLUGIN_OWNER_ID
...
         row = self._find_marketplace_item(
             db,
-            user_id=0,
+            user_id=BUILTIN_PLUGIN_OWNER_ID,
             plugin_key=plugin_key,
         )
🤖 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/installed_plugin_service.py` around lines 315 - 319,
Update the _find_marketplace_item call in the built-in plugin lookup to pass
BUILTIN_PLUGIN_OWNER_ID instead of the hardcoded user_id=0, reusing the existing
constant imported or defined for built-in plugin ownership.
🤖 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/app/services/builtin_plugin_service.py`:
- Around line 39-55: Update the built-in plugin discovery flow to raise an error
when the source directory is missing or when the required “wegent-sites” plugin
directory is absent, instead of returning an empty list or silently continuing.
Preserve the existing behavior for other optional plugin directories, and ensure
the startup synchronization path receives the failure rather than reporting
success with zero items.
- Around line 93-102: Update the packaging loop in the builtin plugin archive
creation flow to reject symlinks before calling path.stat() or
path.read_bytes(). Use the path’s non-following symlink check and fail packaging
when any staged entry is a symlink, ensuring only regular files physically
contained in plugin_dir are archived.
- Around line 79-84: Update _resolve_plugins_dir so the repository fallback is
used only when BUILTIN_PLUGINS_DIR is the canonical container default; for any
other configured path, including nonexistent absolute paths, return the
configured Path unchanged and let the downstream missing-bundle validation fail.

In `@backend/app/services/installed_plugin_service.py`:
- Around line 308-336: The install path used by install_builtin_plugin and
install_marketplace_plugin must enforce idempotency under concurrent requests.
Add a database-level unique constraint covering user_id, plugin kind, and the
marketplace source identifier (pluginKey/catalogItemId), then handle uniqueness
conflicts by returning the existing InstalledPlugin rather than creating a
duplicate; preserve the current behavior for already-installed and unavailable
plugins.

In `@docs/en/wework/developer-guide/wework-codex-plugins.md`:
- Line 26: Document device synchronization as best-effort by updating
docs/en/wework/developer-guide/wework-codex-plugins.md:26 to say “attempts to
synchronize,” docs/en/wework/plugins-and-skills.md:13 to say “attempts to
synchronize it,” docs/zh/wework/developer-guide/wework-codex-plugins.md:26 to
say “尝试同步到,” and docs/zh/wework/plugins-and-skills.md:17 to say “并尝试同步到.”

In `@wework/e2e/desktop/task-flow.e2e.mjs`:
- Around line 1068-1105: Remove the local canned handlers and fixtures,
including installedSitesPlugin, that intercept builtin-install and marketplace
requests in this E2E flow. Let the test send real backend requests and consume
the backend’s actual response so publication, authentication, idempotency,
persistence, and response-contract behavior are exercised; apply the same change
to the corresponding logic near the additionally referenced section.

In `@wework/src/components/sites/SitesWorkspace.tsx`:
- Around line 166-169: Add the full message as a title attribute on the inner
span rendering message within the SitesWorkspace error display, while preserving
the existing truncate styling and layout. Ensure the title uses the same message
value so users can reveal text that is visually truncated.

---

Nitpick comments:
In `@backend/app/services/installed_plugin_service.py`:
- Around line 315-319: Update the _find_marketplace_item call in the built-in
plugin lookup to pass BUILTIN_PLUGIN_OWNER_ID instead of the hardcoded
user_id=0, reusing the existing constant imported or defined for built-in plugin
ownership.

In `@wework/src/components/sites/SitesWorkspace.tsx`:
- Around line 347-360: Extract the duplicated Create button markup into a shared
CreateSiteButton component near the existing SitesWorkspace helpers, accepting
creating, onCreate, and label props. Replace the buttons in both the
sitesUnavailable branch and the main render path with this component, preserving
their current behavior, styling, test ID, icons, disabled state, and translated
label.
🪄 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 Plus

Run ID: f83a7651-3bcf-4e7f-92ae-f33c1a89023a

📥 Commits

Reviewing files that changed from the base of the PR and between a54f136 and e362115.

📒 Files selected for processing (37)
  • .env.example
  • .gitignore
  • backend/.env.example
  • backend/app/api/endpoints/installed_plugins.py
  • backend/app/core/config.py
  • backend/app/main.py
  • backend/app/services/builtin_plugin_service.py
  • backend/app/services/installed_plugin_service.py
  • backend/init_data/README.md
  • backend/tests/services/test_installed_plugin_upload.py
  • build_image.sh
  • build_image_mac.sh
  • docker-compose.yml
  • docs/en/wework/developer-guide/wework-codex-plugins.md
  • docs/en/wework/plugins-and-skills.md
  • docs/zh/wework/developer-guide/wework-codex-plugins.md
  • docs/zh/wework/plugins-and-skills.md
  • package.json
  • scripts/sync-builtin-sites-plugin.mjs
  • wework/e2e/desktop/task-flow.e2e.mjs
  • wework/src/App.plugins.test.tsx
  • wework/src/api/adapters.test.ts
  • wework/src/api/local/codexPlugins.ts
  • wework/src/api/plugins.ts
  • wework/src/components/plugins/PluginsWorkspace.test.tsx
  • wework/src/components/plugins/PluginsWorkspace.tsx
  • wework/src/components/sites/SitesWorkspace.test.tsx
  • wework/src/components/sites/SitesWorkspace.tsx
  • wework/src/features/plugins/builtinPlugins.ts
  • wework/src/features/plugins/pluginNavigation.test.ts
  • wework/src/features/plugins/pluginNavigation.ts
  • wework/src/features/plugins/pluginTrial.test.ts
  • wework/src/features/plugins/pluginTrial.ts
  • wework/src/i18n/locales/en/sites.json
  • wework/src/i18n/locales/zh-CN/sites.json
  • wework/src/pages/PluginsPage.tsx
  • wework/src/pages/SitesPage.tsx
🚧 Files skipped from review as they are similar to previous changes (3)
  • wework/src/features/plugins/pluginNavigation.test.ts
  • wework/src/features/plugins/pluginNavigation.ts
  • wework/src/pages/SitesPage.tsx

Comment on lines +93 to +102
for path in sorted(plugin_dir.rglob("*")):
if not path.is_file() or path.name == ".DS_Store":
continue
relative_path = path.relative_to(plugin_dir).as_posix()
info = zipfile.ZipInfo(relative_path, date_time=ZIP_TIMESTAMP)
info.create_system = 3
mode = stat.S_IMODE(path.stat().st_mode)
info.external_attr = mode << 16
info.compress_type = zipfile.ZIP_DEFLATED
archive.writestr(info, path.read_bytes())

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

Reject symlinks before packaging the public plugin.

is_file() and read_bytes() follow symlinks. A staged plugin can therefore include a symlink to a readable file outside plugin_dir, and its contents are uploaded with visibility="public". Reject symlinks (or enforce resolved paths remain under the plugin root) before reading files.

🤖 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/builtin_plugin_service.py` around lines 93 - 102, Update
the packaging loop in the builtin plugin archive creation flow to reject
symlinks before calling path.stat() or path.read_bytes(). Use the path’s
non-following symlink check and fail packaging when any staged entry is a
symlink, ensuring only regular files physically contained in plugin_dir are
archived.

Comment on lines +308 to +336
def install_builtin_plugin(
self,
*,
db: Session,
user_id: int,
plugin_key: str,
) -> InstalledPlugin:
row = self._find_marketplace_item(
db,
user_id=0,
plugin_key=plugin_key,
)
if not row or not row.is_active:
raise HTTPException(
status_code=503,
detail=f"Built-in plugin is unavailable: {plugin_key}",
)
spec = self._get_spec(row)
if spec.get("visibility") != "public":
raise HTTPException(
status_code=503,
detail=f"Built-in plugin is unavailable: {plugin_key}",
)
return self.install_marketplace_plugin(
db=db,
user_id=user_id,
marketplace_id=row.id,
)

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 | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check whether Kind has a DB-level unique constraint that would prevent duplicate InstalledPlugin rows
fd -e py 'kind.py' backend/app/models | xargs cat -n | rg -n -A5 -B5 'UniqueConstraint|unique=True|__table_args__'

Repository: wecode-ai/Wegent

Length of output: 154


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate and inspect the relevant service/model/schema definitions and callers.
printf '--- matching files ---\n'
fd -e py 'installed_plugin_service.py|plugins?|migration' backend | sed -n '1,120p'

printf '\n--- installed_plugin_service outline ---\n'
ast-grep outline backend/app/services/installed_plugin_service.py --view expanded || true
printf '\n--- service relevant lines ---\n'
cat -n backend/app/services/installed_plugin_service.py | sed -n '1,420p'

printf '\n--- installed plugin model(s) ---\n'
rg -n "class Installed|InstalledPlugin|installed_plugins|installed_plugin" backend/app/models backend -g '*.py' | sed -n '1,200p'

Repository: wecode-ai/Wegent

Length of output: 245


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- matching files ---'
fd -e py 'installed_plugin_service.py|plugins?|migration' backend 2>/dev/null | sed -n '1,160p'

printf '%s\n' ''
printf '%s\n' '--- installed_plugin_service outline ---'
ast-grep outline backend/app/services/installed_plugin_service.py --view expanded 2>/dev/null || true
printf '%s\n' ''
printf '%s\n' '--- service relevant lines ---'
cat -n backend/app/services/installed_plugin_service.py | sed -n '1,420p'

printf '%s\n' ''
printf '%s\n' '--- installed plugin model(s) and usages ---'
rg -n "class Installed|InstalledPlugin|installed_plugins|installed_plugin" backend/app/models backend -g '*.py' 2>/dev/null | sed -n '1,240p'

printf '%s\n' ''
printf '%s\n' '--- constraints/unique installs searches ---'
rg -n "UniqueConstraint|unique=True|__table_args__|install_builtin_plugin|install_marketplace_plugin|_find_installed_plugin" backend -g '*.py' 2>/dev/null | sed -n '1,240p'

Repository: wecode-ai/Wegent

Length of output: 39910


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- kind model ---'
cat -n backend/app/models/kind.py | sed -n '1,220p'

printf '%s\n' ''
printf '%s\n' '--- installed plugin service helper section ---'
cat -n backend/app/services/installed_plugin_service.py | sed -n '420,500p'

printf '%s\n' ''
printf '%s\n' '--- installed_plugins API section ---'
cat -n backend/app/api/endpoints/installed_plugins.py | sed -n '90,165p'

printf '%s\n' ''
printf '%s\n' '--- migration references to kinds table / unique constraints ---'
rg -n "kind.*table|InstalledPlugin|PluginMarketplaceItem|UniqueConstraint|unique|create_index|op.add_column|op.alter_column" backend/alembic backend/app/models -g '*.py' 2>/dev/null | sed -n '1,260p'

printf '%s\n' ''
printf '%s\n' '--- behavioral model read for uniqueness ---'
python3 - <<'PY'
import ast
from pathlib import Path
p = Path('backend/app/models/kind.py')
tree = ast.parse(p.read_text())
for node in tree.body:
    if not isinstance(node, ast.ClassDef) or node.name != 'Kind':
        continue
    print("Class Kind found")
    for stmt in node.body:
        if isinstance(stmt, ast.Assign) and len(stmt.targets) == 1:
            target = stmt.targets[0]
            if isinstance(target, ast.Name) and target.id in {"__tablename__", "__table_args__"}:
                print(ast.unparse(stmt))
PY

Repository: wecode-ai/Wegent

Length of output: 21909


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- installed plugin upload/concurrence tests ---'
cat -n backend/tests/services/test_installed_plugin_upload.py | sed -n '100,260p'

printf '%s\n' ''
printf '%s\n' '--- kinds performance indexes migration ---'
cat -n backend/alembic/versions/20260314_a7b8c9d0e1f3_add_kinds_performance_indexes.py | sed -n '1,160p'

printf '%s\n' ''
printf '%s\n' '--- shared Kind model ---'
if [ -f shared/models/db.py ]; then
  cat -n shared/models/db.py | sed -n '1,220p'
elif [ -f shared/models/kind.py ]; then
  cat -n shared/models/kind.py | sed -n '1,220p'
else
  fd -e py 'db.py|kind.py' shared -x cat -n {} | sed -n '1,260p'
fi

printf '%s\n' ''
printf '%s\n' '--- precise uniques and index uniqueness in relevant migrations/models ---'
python3 - <<'PY'
import ast
from pathlib import Path

terms = {
    "InstalledPlugin",
    "PluginMarketplaceItem",
    "__table_args__",
    "UniqueConstraint",
    "unique=True",
    "create_index",
    "create_unique_constraint",
    "op.add_column",
}

for path in (
    Path("shared/models/db.py"),
    Path("backend/app/models/kind.py"),
    Path("backend/alembic/versions/20260314_a7b8c9d0e1f3_add_kinds_performance_indexes.py"),
):
    if not path.exists():
        continue
    print(f"FILE {path}")
    text = path.read_text()
    tree = ast.parse(text)
    for line_no, line in enumerate(text.splitlines(), 1):
        if any(t in line for t in terms):
            print(f"{line_no}: {line.strip()}")
    print("--- AST hits ---")
    for node in ast.walk(tree):
        if isinstance(node, (ast.Assign, ast.CreateIndex, ast.CreateUniqueConstraint, ast.Call)):
            src = ast.get_source_segment(text, node) or ""
            if any(t in src for t in terms):
                print(ast.dump(node, include_attributes=True)[:400] if hasattr(ast, "dump") else str(node))
PY

Repository: wecode-ai/Wegent

Length of output: 10606


Make ensure-installed idempotent under concurrency.

install_builtin_plugin() documents the /builtin/{plugin_key}/ensure-installed endpoint as idempotent, but it relies only on _find_installed_plugin() before creating a new InstalledPlugin row. There is no DB-level unique constraint for (user_id, kind, source pluginKey/catalogItemId), so concurrent calls for the same user can both miss the existing install and create duplicate rows. Add the missing uniqueness protection or use atomic/locking behavior for the install path.

🤖 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/installed_plugin_service.py` around lines 308 - 336, The
install path used by install_builtin_plugin and install_marketplace_plugin must
enforce idempotency under concurrent requests. Add a database-level unique
constraint covering user_id, plugin kind, and the marketplace source identifier
(pluginKey/catalogItemId), then handle uniqueness conflicts by returning the
existing InstalledPlugin rather than creating a duplicate; preserve the current
behavior for already-installed and unavailable plugins.

Comment on lines +1068 to +1105
function installedSitesPlugin() {
const marketplacePlugin = sitesMarketplacePlugin(true)
return {
apiVersion: 'agent.wecode.io/v1',
kind: 'InstalledPlugin',
metadata: {
name: 'wegent-sites',
namespace: 'default',
labels: { id: '601' },
},
spec: {
source: {
type: 'marketplace',
providerKey: 'wegent-marketplace',
pluginKey: 'wegent-sites',
catalogItemId: '501',
marketplace: 'wegent',
},
displayName: '站点',
description: marketplacePlugin.description,
version: marketplacePlugin.version,
author: marketplacePlugin.author,
installState: 'installed',
enabled: true,
componentStates: {},
manifest: marketplacePlugin.manifest,
components: marketplacePlugin.components,
interface: marketplacePlugin.interface,
packageRef: {
storageKey: 'skill-binaries/601',
checksum: 'sha256:desktop-e2e-sites',
sizeBytes: 1024,
},
sourcePayload: { filename: 'wegent-sites.zip' },
},
status: { state: 'Available' },
}
}

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 | 🏗️ Heavy lift

Exercise the real backend for this E2E flow.

These local handlers replace the new builtin-install and marketplace requests with canned responses, and the marketplace response is always marked installed. Consequently, this test does not validate backend publication, authentication, idempotency, persistence, or the real response contract.

As per coding guidelines, E2E tests must use real backend requests and must not replace integrations with mocks.

Also applies to: 2821-2833

🤖 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/e2e/desktop/task-flow.e2e.mjs` around lines 1068 - 1105, Remove the
local canned handlers and fixtures, including installedSitesPlugin, that
intercept builtin-install and marketplace requests in this E2E flow. Let the
test send real backend requests and consume the backend’s actual response so
publication, authentication, idempotency, persistence, and response-contract
behavior are exercised; apply the same change to the corresponding logic near
the additionally referenced section.

Source: Coding guidelines

Comment on lines +166 to +169
<span className="flex min-w-0 items-center gap-2 text-sm text-text-secondary">
<AlertCircle className="h-4 w-4 shrink-0 text-danger" aria-hidden="true" />
<span className="truncate">{message}</span>
</span>

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

Truncated error text has no way to reveal the full message.

message is truncated with CSS ellipsis but no title attribute is set, so sighted users on narrow viewports can't see the full error (e.g. the longer backend-upgrade-required copy).

🩹 Proposed fix
       <span className="flex min-w-0 items-center gap-2 text-sm text-text-secondary">
         <AlertCircle className="h-4 w-4 shrink-0 text-danger" aria-hidden="true" />
-        <span className="truncate">{message}</span>
+        <span className="truncate" title={message}>{message}</span>
       </span>
📝 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
<span className="flex min-w-0 items-center gap-2 text-sm text-text-secondary">
<AlertCircle className="h-4 w-4 shrink-0 text-danger" aria-hidden="true" />
<span className="truncate">{message}</span>
</span>
<span className="flex min-w-0 items-center gap-2 text-sm text-text-secondary">
<AlertCircle className="h-4 w-4 shrink-0 text-danger" aria-hidden="true" />
<span className="truncate" title={message}>{message}</span>
</span>
🤖 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/components/sites/SitesWorkspace.tsx` around lines 166 - 169, Add
the full message as a title attribute on the inner span rendering message within
the SitesWorkspace error display, while preserving the existing truncate styling
and layout. Ensure the title uses the same message value so users can reveal
text that is visually truncated.

@FicoHu
FicoHu force-pushed the feature/sites-auto-install-plugin branch from 11b6f96 to 5c0e2f4 Compare July 28, 2026 06:25
@qdaxb qdaxb added the ci:all Force all module tests in test.yml regardless of changed paths label Jul 29, 2026
@FicoHu
FicoHu force-pushed the feature/sites-auto-install-plugin branch 2 times, most recently from 3ca87ac to 8cf01f4 Compare July 29, 2026 04:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci:all Force all module tests in test.yml regardless of changed paths

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants