Skip to content

feat(wework): guide device version upgrades - #1319

Merged
qdaxb merged 1 commit into
wecode-ai:mainfrom
icycrystal4:codex/wework-device-version-upgrade
Jun 9, 2026
Merged

qdaxb merged 1 commit into
wecode-ai:mainfrom
icycrystal4:codex/wework-device-version-upgrade

Conversation

@icycrystal4

@icycrystal4 icycrystal4 commented Jun 9, 2026 •

Copy link
Copy Markdown
Collaborator

Task

  • o8GuHjOIZ7 引导用户升级支持wework版本

Summary

  • Add frontend WeWork device version compatibility checks and explicit device upgrade state handling.
  • Show device create/upgrade/update prompts outside the composer and block chats on incompatible active devices.
  • Update project creation UX so online low-version devices remain selectable, with upgrade guidance only for the selected device.
  • Remove user-facing "executor" wording from version upgrade prompts.

Validation

  • npm test -- ProjectCreateDialog.test.tsx DeviceStatusPrompt.test.tsx DesktopWorkbenchLayout.test.tsx WorkbenchProvider.test.tsx device-capabilities.test.ts device-selection.test.ts chatStream.test.ts devices.test.ts
  • npm run lint
  • npm run build

Integration

  • Synced to the integration environment for manual UI validation.

Summary by CodeRabbit

Release Notes

  • New Features

    • Added device upgrade functionality with progress tracking and status display
    • Device compatibility checks now prevent unsupported device usage in chat and project creation
    • Status prompts notify users when device upgrades are required
    • Device selection filters by executor version compatibility
  • Localization

    • Added device upgrade and compatibility status messages

Task: o8GuHjOIZ7 引导用户升级支持wework版本
@coderabbitai

coderabbitai Bot commented Jun 9, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds device executor version compatibility checking and a complete device upgrade feature. It introduces version gating for WeWork, type contracts for device events, a new DeviceStatusPrompt UI component, upgrade state orchestration in WorkbenchProvider, and integrates the upgrade flow through layouts and project creation dialogs with internationalization support.

Changes

Device Upgrade Feature

Layer / File(s) Summary
Type contracts for device upgrade
wework/src/types/devices.ts, wework/src/types/api.ts, wework/src/types/device-events.ts
Adds UpgradeDeviceOptions and UpgradeDeviceResponse types, expands DeviceInfo with optional executor version, slot usage, and running task fields, introduces DeviceRunningTask for task tracking, and defines event payload types (DeviceSlotUpdatePayload, DeviceUpgradeStatusPayload, DeviceUpgradeState) for device stream events.
Device capability utilities
wework/src/lib/device-capabilities.ts, wework/src/lib/device-capabilities.test.ts
Implements WEWORK_MIN_EXECUTOR_VERSION constant (1.8.5), isVersionAtLeast() version comparison with prerelease handling, isDeviceRunningTask() and isOnlineIdleDevice() state checks, executor compatibility helpers (isWeWorkExecutorVersionCompatible(), isDeviceBelowWeWorkVersion(), isWeWorkCompatibleDevice()), and updates canUseForProjectCreation() to require executor version compatibility. Comprehensive tests validate version parsing, task detection, and eligibility rules.
Device selection with executor compatibility
wework/src/lib/device-selection.ts, wework/src/lib/device-selection.test.ts
Extends SelectableDevice with executor_version field, adds isWeWorkSelectableStandaloneDevice() predicate requiring ClaudeCode, online status, and executor compatibility, and updates getPreferredStandaloneDeviceId() to prefer devices meeting the new standalone eligibility criteria.
Chat composer device compatibility gating
wework/src/components/chat/composer/ProjectWorkBar.tsx, wework/src/components/chat/ChatInput.test.tsx
Updates ProjectWorkBar to show "upgrade required" status when device executor version is incompatible, filters standalone devices in the device submenu by compatibility (online && compatible), and adjusts styling and selection logic to reflect compatibility eligibility. Test helper normalizes device executor versions in fixtures.
DeviceStatusPrompt component
wework/src/components/layout/DeviceStatusPrompt.tsx, wework/src/components/layout/DeviceStatusPrompt.test.tsx
New React component that renders conditional device status prompts covering: no devices (create button), actively upgrading device (spinner + progress), device below minimum version (amber prompt + upgrade button), offline/busy device (status only), outdated devices with upgrade blocking logic (multi-device scenarios), missing online compatible devices, and available WeWork updates for compatible devices. Full test coverage validates all scenarios and callback invocations.
Device upgrade API and stream event handling
wework/src/api/devices.ts, wework/src/api/devices.test.ts, wework/src/stream/chatStream.ts, wework/src/stream/chatStream.test.ts
Adds upgradeDevice(deviceId, options?) HTTP POST method to device API with auto-confirm flag support. Extends ChatStreamHandlers with onDeviceSlotUpdate and onDeviceUpgradeStatus callbacks and wires socket listeners for device:slot_update and device:upgrade_status events.
WorkbenchProvider upgrade orchestration
wework/src/features/workbench/WorkbenchProvider.tsx, wework/src/features/workbench/WorkbenchProvider.test.tsx
Core upgrade state management: maintains upgradingDevices map with progress/error/message, implements upgradeDevice(deviceId) that validates device is online/idle before calling API with auto-confirm, updates upgrade state through pending/checking/busy/error transitions, subscribes to stream onDeviceUpgradeStatus events, polls device lists while upgrading, and auto-clears UI state after terminal statuses. Also gates message sending on executor version compatibility and requires online compatible devices. Adds test asserting old devices are not auto-upgraded on bootstrap, updates device fixtures with executor_version, and fixes attachment test readiness probing.
Layout components wire upgrade state
wework/src/components/layout/DesktopWorkbenchLayout.tsx, wework/src/components/layout/DesktopWorkbenchMain.tsx, wework/src/components/layout/MobileWorkbenchLayout.tsx, wework/src/components/layout/DesktopSidebar.tsx
Desktop and Mobile layouts accept upgradingDevices and onUpgradeDevice props from context, compute device version incompatibility and standalone device availability flags, derive unified composerDisabled logic including compatibility checks, and render DeviceStatusPrompt in both conversation and empty states. DesktopWorkbenchMain manages the full device status/prompt rendering and callback plumbing.
ProjectCreateDialog upgrade-aware device selection
wework/src/components/projects/ProjectCreateDialog.tsx, wework/src/components/projects/ProjectCreateDialog.test.tsx
Integrates upgrade UI into project creation: filters selectable devices to Claude-code subset, disables directory browsing and inputs when selected device is below required executor version, shows unavailable device panel with upgrade button and contextual status messaging including progress from upgradingDevices. Device <select> options disable entries failing compatibility checks and use new labeling. Test coverage validates upgrade action visibility/enabledness, single-device upgrade prompt scoping, and directory browsing blocking.
WorkbenchPage context plumbing
wework/src/pages/WorkbenchPage.tsx
Retrieves upgradingDevices and upgradeDevice from useWorkbench() and passes them as props to the selected layout (Desktop or Mobile).
Internationalization for device upgrade UI
wework/src/i18n/locales/en/common.json, wework/src/i18n/locales/zh-CN/common.json
Adds English and Chinese i18n strings covering device unavailability titles, upgrade requirements and actions, device status labels (online/busy/offline/unavailable/upgrading/checking), conversation and project device prompts, upgrade messaging variants for single/multiple devices, and device blocking reasons.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • wecode-ai/Wegent#1139: Updates device filtering in ProjectCreateDialog to exclude bind_shell === 'openclaw' devices, overlapping with upgrade-aware device selection in this PR.
  • wecode-ai/Wegent#1173: Modifies workspace directory selection in ProjectCreateDialog, directly overlapping with upgrade/compatibility-driven disabling of directory browsing in this PR.
  • wecode-ai/Wegent#1200: Introduces standalone device selection controls in ProjectWorkBar, overlapped by executor-version compatibility gating and upgrade status display in this PR.

Suggested reviewers

  • qdaxb

🐰 A device upgrades with grace,
Version checks now guide the race,
Status prompts appear so bright,
WeWork flows, smooth as light! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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 'feat(wework): guide device version upgrades' accurately and concisely summarizes the primary change: adding frontend device version compatibility checks and upgrade guidance. It uses the conventional commit format and is directly related to the main objective.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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 and usage tips.

@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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
wework/src/components/chat/ChatInput.test.tsx (1)

70-82: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Remove the duplicate devices key in the returned object.

devices is declared twice in the same object literal, which triggers Biome lint/suspicious/noDuplicateObjectKeys and can fail lint in CI.

💡 Suggested patch
   return {
     projects: [],
-    devices,
     currentProjectId: undefined,
     currentStandaloneDeviceId: null,
     executionMode: 'current_workspace',
     executionModeLocked: false,
     onSelectProject: vi.fn(),
     onSelectStandaloneDevice: vi.fn(),
     onExecutionModeChange: vi.fn(),
     ...overrides,
     devices,
   }
🤖 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/chat/ChatInput.test.tsx` around lines 70 - 82, The
returned object in the test factory contains a duplicate devices key which
triggers the lint rule; remove the second occurrence of "devices" from the
object literal in ChatInput.test.tsx (the object returned in the test
setup/factory), leaving a single "devices" entry and keeping the ...overrides
spread as-is so overrides still apply correctly.

Source: Linters/SAST tools

🧹 Nitpick comments (11)
wework/src/features/workbench/WorkbenchProvider.tsx (4)

778-781: ⚡ Quick win

Internationalize error message.

The error message is hardcoded in Chinese. Like the status messages, this should use the i18n system for consistency with the rest of the upgrade UI (per PR objectives).

🤖 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/workbench/WorkbenchProvider.tsx` around lines 778 - 781,
Replace the hardcoded Chinese strings assigned to the local variable message (in
the device.status !== 'online' ? ... : ... conditional) with i18n translation
keys using the same translation helper used elsewhere (e.g., the t function from
useTranslation or i18n.t), import/use that helper in WorkbenchProvider.tsx if
not already present, and update the translation resource files with appropriate
keys and Chinese/other locales; ensure the conditional becomes something like
t('workbench.device_offline') and t('workbench.device_busy') so it matches
existing status message patterns.

1644-1666: ⚡ Quick win

Internationalize version-check error messages.

Lines 1651 and 1662 contain hardcoded Chinese error messages for version gating. These should use the i18n system added in this PR for consistency with other device upgrade UI.

🤖 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/workbench/WorkbenchProvider.tsx` around lines 1644 -
1666, Replace the two hardcoded Chinese error messages passed to dispatch({type:
'error_set', error: ...}) with i18n lookups that include placeholders for
dynamic values; for the device-specific message use the display name from
getWorkbenchDeviceDisplayName and WEWORK_MIN_EXECUTOR_VERSION as params (e.g.
i18n.t('workbench.deviceVersionLow', { deviceName, version })) and for the
no-online-device case use a key like
i18n.t('workbench.noCompatibleDeviceOnline', { version:
WEWORK_MIN_EXECUTOR_VERSION }); update the imports/use of the translator in
WorkbenchProvider (where state.currentProject/state.currentTask/dispatch and
helpers like isWeWorkCompatibleDevice are used) so the dispatch error strings
come from the i18n system rather than hardcoded text.

104-111: ⚡ Quick win

Consider internationalizing hardcoded status messages.

The function returns hardcoded Chinese strings for user-facing upgrade status messages. While i18n files are added in this PR (per objectives), these specific strings bypass the i18n system. For consistency and maintainability, consider using the i18n system for all user-facing text.

🤖 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/workbench/WorkbenchProvider.tsx` around lines 104 - 111,
getUpgradeStatusMessage currently returns hardcoded Chinese user-facing strings;
update it to use the project's i18n translation function instead (e.g., i18n.t
or the existing translate wrapper) so all messages go through the localization
system. Keep the existing behavior for payload.message and payload.error (using
error as fallback), but replace literals like '升级完成,正在检查设备版本', '升级失败',
'设备正在执行任务,空闲后再升级', '设备已是最新版本', and '设备升级中' with calls to the appropriate
translation keys (create/choose sensible keys like workbench.upgrade.success,
workbench.upgrade.error, workbench.upgrade.busy, workbench.upgrade.skipped,
workbench.upgrade.in_progress) and import/initialize the i18n helper in
WorkbenchProvider.tsx so getUpgradeStatusMessage(DeviceUpgradeStatusPayload)
returns translated strings.

803-803: 💤 Low value

Consider logging errors from background device refresh.

The .catch(() => undefined) pattern silently suppresses errors from the device list refresh. While surfacing these to users may not be appropriate, logging them would aid debugging without affecting UX.

💡 Optional improvement
-        void refreshDevices().catch(() => undefined)
+        void refreshDevices().catch((err) => console.error('Device refresh failed after upgrade:', err))
🤖 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/workbench/WorkbenchProvider.tsx` at line 803, The
background call to refreshDevices() currently swallows errors with .catch(() =>
undefined); update this to log the error and still swallow it so UX is
unchanged. Replace .catch(() => undefined) with something like .catch(err => {
/* use existing logger if available, e.g. logger.error('refreshDevices failed',
err) */ console.error('refreshDevices failed', err); }); and reference the
refreshDevices call inside WorkbenchProvider (where void refreshDevices() is
invoked).
wework/src/components/layout/DeviceStatusPrompt.tsx (1)

49-341: 🏗️ Heavy lift

Break DeviceStatusPrompt into smaller render helpers/hooks.

The component has grown far beyond the preferred function-length threshold, making this branching UI harder to test and evolve safely.

As per coding guidelines, **/*.{ts,tsx,js,jsx}: Function length: Max 50 lines per function (preferred).

🤖 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/layout/DeviceStatusPrompt.tsx` around lines 49 - 341,
DeviceStatusPrompt is too large—extract the large branching render blocks and
side-effect logic into smaller helpers/hooks to keep the main function under ~50
lines; specifically move runUpgrades into a useDeviceUpgrade hook (or utility)
and create small components or render helpers named e.g. NoDevicesPrompt,
ActiveUpgradePrompt (uses activeUpgradeDevice and upgradingDevices),
OutdatedActivePrompt (uses activeDevice, canRequestDeviceUpgrade,
getBlockedReason), ActiveOfflinePrompt (uses isDeviceRunningTask),
OutdatedDevicesPrompt (uses
lowVersionUpgradeCandidates/lowVersionBlockedDevices), RequiresOnlinePrompt, and
UpdateAvailablePrompt (uses updateCandidates and canRequestDeviceUpgrade); wire
these helpers from DeviceStatusPrompt so the top-level function only computes
claudeCodeDevices/compatibleDevices/updateCandidates/activeUpgradeDevice and
returns the appropriate small component. Ensure each helper receives only the
props it needs (device lists, handlers like
onUpgradeDevice/onOpenCloudDeviceSettings, manualPending state from
useDeviceUpgrade) and keep naming exact (DeviceStatusPrompt, runUpgrades,
activeUpgradeDevice, updateCandidates, lowVersionUpgradeCandidates) to locate
code easily.

Source: Coding guidelines

wework/src/components/chat/ChatInput.test.tsx (1)

1-2812: 🏗️ Heavy lift

Split this test file into smaller modules.

This file is well above the repository size limit and is becoming hard to maintain as behaviors keep expanding.

As per coding guidelines, **/*.{ts,tsx,js,jsx}: File size limit: If a file exceeds 1000 lines, split it into multiple sub-modules.

🤖 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/chat/ChatInput.test.tsx` around lines 1 - 2812, The
test file ChatInput.test.tsx is over the repository size limit (>1000 lines);
split it into focused test modules by feature (e.g., local-skill.tests.tsx,
model-menu.tests.tsx, project-work.tests.tsx, attachments.tests.tsx,
compact-desktop-ui.tests.tsx). Extract and reuse shared helpers and fixtures
(ControlledChatInput, projectChatControls, projectWorkControls, shared imports
like fireEvent/render/waitFor, and shared afterEach cleanup logic including
URL.createObjectURL restoration and vi.restoreAllMocks) into a new test utility
module (e.g., chatInputTestUtils) and import them from each smaller test file;
move global stubs (vi.stubGlobal('fetch')) and any DOM getBoundingClientRect
mocks into per-test setup in the new modules or shared setup functions to
preserve behavior; keep the top-level describe('ChatInput', ...) groups split so
each new file contains a focused describe and tests, retaining test IDs used in
assertions (e.g., 'chat-message-input', 'local-skill-autocomplete',
'model-selector-button', 'project-work-button', 'attachment-badge') so tests
continue to target the same component behavior.

Source: Coding guidelines

wework/src/components/projects/ProjectCreateDialog.tsx (1)

1-1186: 🏗️ Heavy lift

Split ProjectCreateDialog.tsx into sub-modules.

The file is over the repository’s size limit and now contains multiple domains (device gating, directory browsing, git flow, dialog rendering) in one module.

As per coding guidelines, **/*.{ts,tsx,js,jsx}: File size limit: If a file exceeds 1000 lines, split it into multiple sub-modules.

🤖 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/projects/ProjectCreateDialog.tsx` around lines 1 -
1186, This file exceeds the size guideline — split it into focused sub-modules:
1) move path utilities (sanitizePathSegment, normalizePath, joinPath, basename,
getParentPath, getPathSearchParts, directoryMatchesQuery) into a new path-utils
module; 2) move device-related helpers (sortDevicesForProjectCreation,
getProjectCreationDevices, getProjectDeviceOptions, canSelectProjectDevice,
getDefaultDeviceId, isProjectDeviceUpgradeActive,
canRequestDeviceUpgrade/isDeviceBelowWeWorkVersion checks wrappers, and
getDeviceOptionLabel/ directoryDeviceUnavailableMessage logic) into a
device-utils module; 3) move git helpers (getGitProjectName, uniqueRepositories,
getGitErrorMessage) into a git-utils module; 4) keep the React components
(ProjectCreateDialog and ProjectCreateDialogContent) in the component file and
import the helpers from the new modules; ensure all moved functions are
exported/imported with the same names and update any references (e.g.,
ProjectCreateDialogContent uses getDefaultDeviceId, getProjectDeviceOptions,
onGetProjectWorkspaceRoot, onListGitRepositories, onListGitBranches,
handleCreateDirectory, handleBrowsePath, handleConfirmPathInput) so behavior is
unchanged and tests/imports still work.

Source: Coding guidelines

wework/src/lib/device-capabilities.ts (1)

17-37: 💤 Low value

Consider adding a documentation comment for the version comparison algorithm.

The isVersionAtLeast function implements non-trivial semantic version comparison (handles prefixes, suffixes, variable-length segments). A brief comment explaining the algorithm would improve maintainability:

/**
 * Compares semantic versions segment-by-segment.
 * - Strips leading 'v' (case-insensitive)
 * - Ignores prerelease suffixes after '-'
 * - Treats missing segments as 0 (e.g., '1.8' equals '1.8.0')
 * - Returns true if version >= targetVersion
 */
export function isVersionAtLeast(version: string, targetVersion: string): boolean {
🤖 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/lib/device-capabilities.ts` around lines 17 - 37, Add a concise
JSDoc comment to the isVersionAtLeast function describing its algorithm: mention
that it strips a leading 'v' (case-insensitive), ignores prerelease suffixes
after '-', parses numeric dot-separated segments via the internal parseVersion
helper, treats missing segments as 0 for comparison, and returns true when
version >= targetVersion; place the comment immediately above the
isVersionAtLeast declaration and reference parseVersion in the description so
future readers know what helper logic is used.
wework/src/lib/device-capabilities.test.ts (2)

18-23: ⚡ Quick win

Test combined task detection conditions.

Currently each condition (slot_used, running_tasks, running_task_ids) is tested in isolation. Consider verifying combinations:

test('detects tasks when multiple indicators are present', () => {
  expect(isDeviceRunningTask({ 
    status: 'online', 
    slot_used: 1, 
    running_task_ids: [7] 
  })).toBe(true)
})

test('handles empty arrays vs undefined correctly', () => {
  expect(isDeviceRunningTask({ status: 'online', running_tasks: [] })).toBe(false)
  expect(isDeviceRunningTask({ status: 'online' })).toBe(false)
})
🤖 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/lib/device-capabilities.test.ts` around lines 18 - 23, Add tests
to cover combined detection and empty-array vs undefined behavior for
isDeviceRunningTask: add a test named like "detects tasks when multiple
indicators are present" asserting isDeviceRunningTask({ status: 'online',
slot_used: 1, running_task_ids: [7] }) returns true, and another test "handles
empty arrays vs undefined correctly" asserting isDeviceRunningTask({ status:
'online', running_tasks: [] }) is false and isDeviceRunningTask({ status:
'online' }) is false (also include checks for running_task_ids: [] and
slot_used: 0 vs undefined to ensure all empty/absent indicators are treated as
not-running). Ensure the new tests live alongside the existing tests that
reference isDeviceRunningTask.

12-16: ⚡ Quick win

Expand version comparison edge case coverage.

The current tests validate the main scenarios, but consider adding edge cases to ensure robustness:

test('handles version segments of different lengths', () => {
  expect(isVersionAtLeast('1.8', '1.8.0')).toBe(true)      // Treats missing as 0
  expect(isVersionAtLeast('1.8.0', '1.8')).toBe(true)      
  expect(isVersionAtLeast('1.8.0.0', '1.8')).toBe(true)
})

test('rejects malformed versions', () => {
  expect(isVersionAtLeast('1.x.5', WEWORK_MIN_EXECUTOR_VERSION)).toBe(false)
  expect(isVersionAtLeast('', WEWORK_MIN_EXECUTOR_VERSION)).toBe(false)
})
🤖 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/lib/device-capabilities.test.ts` around lines 12 - 16, Add unit
tests to cover version-segment-length differences and malformed inputs: in
device-capabilities.test.ts, add cases calling isVersionAtLeast with versions
like '1.8' vs '1.8.0', '1.8.0' vs '1.8', and '1.8.0.0' vs '1.8' to assert true
(treat missing segments as 0), and add malformed inputs such as '1.x.5' and ''
compared against WEWORK_MIN_EXECUTOR_VERSION to assert false; reference the
existing test block that uses isVersionAtLeast and WEWORK_MIN_EXECUTOR_VERSION
to place these new test() cases.
wework/src/lib/device-selection.test.ts (1)

8-30: 💤 Low value

Consider expanding test coverage for device selection scenarios.

The current test validates the core upgrade scenario. Additional test cases would improve confidence:

test('prefers current device when it meets compatibility requirements', () => {
  const devices = [
    { device_id: 'current', status: 'online', bind_shell: 'claudecode', executor_version: '1.8.5' },
    { device_id: 'other', status: 'online', bind_shell: 'claudecode', executor_version: '1.8.6' }
  ]
  expect(getPreferredStandaloneDeviceId(devices, 'current')).toBe('current')
})

test('returns null when no devices are compatible', () => {
  const devices = [
    { device_id: 'old', status: 'online', bind_shell: 'claudecode', executor_version: '1.8.4' }
  ]
  expect(getPreferredStandaloneDeviceId(devices, 'old')).toBe(null)
})
🤖 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/lib/device-selection.test.ts` around lines 8 - 30, Add two
additional unit tests to cover edge cases for standalone device selection: one
where the current device meets compatibility so
getPreferredStandaloneDeviceId(devices, 'current') returns 'current' (reference
is isWeWorkSelectableStandaloneDevice and getPreferredStandaloneDeviceId) and
another where no devices are compatible so getPreferredStandaloneDeviceId
returns null; place these alongside the existing test to assert preferred
selection when current is compatible and null when none qualify.
🤖 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/layout/DeviceStatusPrompt.tsx`:
- Around line 89-97: The runUpgrades function can surface unhandled promise
rejections when callers invoke it with void; change its aggregation to a
non-throwing form (e.g., use Promise.allSettled or catch each onUpgradeDevice
promise) so failures are captured rather than propagated, preserve the early
return for empty deviceIds and ensure setManualPending(true) is still set before
starting and setManualPending(false) in the finally/cleanup block; update
references inside runUpgrades to use onUpgradeDevice for individual tasks and
handle results/errors without rethrowing so callers won't receive unhandled
rejections.

In `@wework/src/components/projects/ProjectCreateDialog.tsx`:
- Around line 547-551: The handleUpgradeDevice async function can let rejections
from onUpgradeDevice become unhandled; wrap the await on
onUpgradeDevice(nextDeviceId) in a try/catch inside handleUpgradeDevice (and
keep the early return when onUpgradeDevice is falsy), and on error call
setProjectCreateError with a user-facing message (or error.message) and
optionally log the error so callers who trigger the void click handler won’t
produce an unhandled rejection; reference the handleUpgradeDevice function and
the onUpgradeDevice prop when making this change.

---

Outside diff comments:
In `@wework/src/components/chat/ChatInput.test.tsx`:
- Around line 70-82: The returned object in the test factory contains a
duplicate devices key which triggers the lint rule; remove the second occurrence
of "devices" from the object literal in ChatInput.test.tsx (the object returned
in the test setup/factory), leaving a single "devices" entry and keeping the
...overrides spread as-is so overrides still apply correctly.

---

Nitpick comments:
In `@wework/src/components/chat/ChatInput.test.tsx`:
- Around line 1-2812: The test file ChatInput.test.tsx is over the repository
size limit (>1000 lines); split it into focused test modules by feature (e.g.,
local-skill.tests.tsx, model-menu.tests.tsx, project-work.tests.tsx,
attachments.tests.tsx, compact-desktop-ui.tests.tsx). Extract and reuse shared
helpers and fixtures (ControlledChatInput, projectChatControls,
projectWorkControls, shared imports like fireEvent/render/waitFor, and shared
afterEach cleanup logic including URL.createObjectURL restoration and
vi.restoreAllMocks) into a new test utility module (e.g., chatInputTestUtils)
and import them from each smaller test file; move global stubs
(vi.stubGlobal('fetch')) and any DOM getBoundingClientRect mocks into per-test
setup in the new modules or shared setup functions to preserve behavior; keep
the top-level describe('ChatInput', ...) groups split so each new file contains
a focused describe and tests, retaining test IDs used in assertions (e.g.,
'chat-message-input', 'local-skill-autocomplete', 'model-selector-button',
'project-work-button', 'attachment-badge') so tests continue to target the same
component behavior.

In `@wework/src/components/layout/DeviceStatusPrompt.tsx`:
- Around line 49-341: DeviceStatusPrompt is too large—extract the large
branching render blocks and side-effect logic into smaller helpers/hooks to keep
the main function under ~50 lines; specifically move runUpgrades into a
useDeviceUpgrade hook (or utility) and create small components or render helpers
named e.g. NoDevicesPrompt, ActiveUpgradePrompt (uses activeUpgradeDevice and
upgradingDevices), OutdatedActivePrompt (uses activeDevice,
canRequestDeviceUpgrade, getBlockedReason), ActiveOfflinePrompt (uses
isDeviceRunningTask), OutdatedDevicesPrompt (uses
lowVersionUpgradeCandidates/lowVersionBlockedDevices), RequiresOnlinePrompt, and
UpdateAvailablePrompt (uses updateCandidates and canRequestDeviceUpgrade); wire
these helpers from DeviceStatusPrompt so the top-level function only computes
claudeCodeDevices/compatibleDevices/updateCandidates/activeUpgradeDevice and
returns the appropriate small component. Ensure each helper receives only the
props it needs (device lists, handlers like
onUpgradeDevice/onOpenCloudDeviceSettings, manualPending state from
useDeviceUpgrade) and keep naming exact (DeviceStatusPrompt, runUpgrades,
activeUpgradeDevice, updateCandidates, lowVersionUpgradeCandidates) to locate
code easily.

In `@wework/src/components/projects/ProjectCreateDialog.tsx`:
- Around line 1-1186: This file exceeds the size guideline — split it into
focused sub-modules: 1) move path utilities (sanitizePathSegment, normalizePath,
joinPath, basename, getParentPath, getPathSearchParts, directoryMatchesQuery)
into a new path-utils module; 2) move device-related helpers
(sortDevicesForProjectCreation, getProjectCreationDevices,
getProjectDeviceOptions, canSelectProjectDevice, getDefaultDeviceId,
isProjectDeviceUpgradeActive, canRequestDeviceUpgrade/isDeviceBelowWeWorkVersion
checks wrappers, and getDeviceOptionLabel/ directoryDeviceUnavailableMessage
logic) into a device-utils module; 3) move git helpers (getGitProjectName,
uniqueRepositories, getGitErrorMessage) into a git-utils module; 4) keep the
React components (ProjectCreateDialog and ProjectCreateDialogContent) in the
component file and import the helpers from the new modules; ensure all moved
functions are exported/imported with the same names and update any references
(e.g., ProjectCreateDialogContent uses getDefaultDeviceId,
getProjectDeviceOptions, onGetProjectWorkspaceRoot, onListGitRepositories,
onListGitBranches, handleCreateDirectory, handleBrowsePath,
handleConfirmPathInput) so behavior is unchanged and tests/imports still work.

In `@wework/src/features/workbench/WorkbenchProvider.tsx`:
- Around line 778-781: Replace the hardcoded Chinese strings assigned to the
local variable message (in the device.status !== 'online' ? ... : ...
conditional) with i18n translation keys using the same translation helper used
elsewhere (e.g., the t function from useTranslation or i18n.t), import/use that
helper in WorkbenchProvider.tsx if not already present, and update the
translation resource files with appropriate keys and Chinese/other locales;
ensure the conditional becomes something like t('workbench.device_offline') and
t('workbench.device_busy') so it matches existing status message patterns.
- Around line 1644-1666: Replace the two hardcoded Chinese error messages passed
to dispatch({type: 'error_set', error: ...}) with i18n lookups that include
placeholders for dynamic values; for the device-specific message use the display
name from getWorkbenchDeviceDisplayName and WEWORK_MIN_EXECUTOR_VERSION as
params (e.g. i18n.t('workbench.deviceVersionLow', { deviceName, version })) and
for the no-online-device case use a key like
i18n.t('workbench.noCompatibleDeviceOnline', { version:
WEWORK_MIN_EXECUTOR_VERSION }); update the imports/use of the translator in
WorkbenchProvider (where state.currentProject/state.currentTask/dispatch and
helpers like isWeWorkCompatibleDevice are used) so the dispatch error strings
come from the i18n system rather than hardcoded text.
- Around line 104-111: getUpgradeStatusMessage currently returns hardcoded
Chinese user-facing strings; update it to use the project's i18n translation
function instead (e.g., i18n.t or the existing translate wrapper) so all
messages go through the localization system. Keep the existing behavior for
payload.message and payload.error (using error as fallback), but replace
literals like '升级完成,正在检查设备版本', '升级失败', '设备正在执行任务,空闲后再升级', '设备已是最新版本', and
'设备升级中' with calls to the appropriate translation keys (create/choose sensible
keys like workbench.upgrade.success, workbench.upgrade.error,
workbench.upgrade.busy, workbench.upgrade.skipped,
workbench.upgrade.in_progress) and import/initialize the i18n helper in
WorkbenchProvider.tsx so getUpgradeStatusMessage(DeviceUpgradeStatusPayload)
returns translated strings.
- Line 803: The background call to refreshDevices() currently swallows errors
with .catch(() => undefined); update this to log the error and still swallow it
so UX is unchanged. Replace .catch(() => undefined) with something like
.catch(err => { /* use existing logger if available, e.g.
logger.error('refreshDevices failed', err) */ console.error('refreshDevices
failed', err); }); and reference the refreshDevices call inside
WorkbenchProvider (where void refreshDevices() is invoked).

In `@wework/src/lib/device-capabilities.test.ts`:
- Around line 18-23: Add tests to cover combined detection and empty-array vs
undefined behavior for isDeviceRunningTask: add a test named like "detects tasks
when multiple indicators are present" asserting isDeviceRunningTask({ status:
'online', slot_used: 1, running_task_ids: [7] }) returns true, and another test
"handles empty arrays vs undefined correctly" asserting isDeviceRunningTask({
status: 'online', running_tasks: [] }) is false and isDeviceRunningTask({
status: 'online' }) is false (also include checks for running_task_ids: [] and
slot_used: 0 vs undefined to ensure all empty/absent indicators are treated as
not-running). Ensure the new tests live alongside the existing tests that
reference isDeviceRunningTask.
- Around line 12-16: Add unit tests to cover version-segment-length differences
and malformed inputs: in device-capabilities.test.ts, add cases calling
isVersionAtLeast with versions like '1.8' vs '1.8.0', '1.8.0' vs '1.8', and
'1.8.0.0' vs '1.8' to assert true (treat missing segments as 0), and add
malformed inputs such as '1.x.5' and '' compared against
WEWORK_MIN_EXECUTOR_VERSION to assert false; reference the existing test block
that uses isVersionAtLeast and WEWORK_MIN_EXECUTOR_VERSION to place these new
test() cases.

In `@wework/src/lib/device-capabilities.ts`:
- Around line 17-37: Add a concise JSDoc comment to the isVersionAtLeast
function describing its algorithm: mention that it strips a leading 'v'
(case-insensitive), ignores prerelease suffixes after '-', parses numeric
dot-separated segments via the internal parseVersion helper, treats missing
segments as 0 for comparison, and returns true when version >= targetVersion;
place the comment immediately above the isVersionAtLeast declaration and
reference parseVersion in the description so future readers know what helper
logic is used.

In `@wework/src/lib/device-selection.test.ts`:
- Around line 8-30: Add two additional unit tests to cover edge cases for
standalone device selection: one where the current device meets compatibility so
getPreferredStandaloneDeviceId(devices, 'current') returns 'current' (reference
is isWeWorkSelectableStandaloneDevice and getPreferredStandaloneDeviceId) and
another where no devices are compatible so getPreferredStandaloneDeviceId
returns null; place these alongside the existing test to assert preferred
selection when current is compatible and null when none qualify.
🪄 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: db8caafc-93bb-41c1-ad37-af3269c96821

📥 Commits

Reviewing files that changed from the base of the PR and between 5b5bf04 and 06f7743.

📒 Files selected for processing (27)
  • wework/src/api/devices.test.ts
  • wework/src/api/devices.ts
  • wework/src/components/chat/ChatInput.test.tsx
  • wework/src/components/chat/composer/ProjectWorkBar.tsx
  • wework/src/components/layout/DesktopSidebar.tsx
  • wework/src/components/layout/DesktopWorkbenchLayout.test.tsx
  • wework/src/components/layout/DesktopWorkbenchLayout.tsx
  • wework/src/components/layout/DesktopWorkbenchMain.tsx
  • wework/src/components/layout/DeviceStatusPrompt.test.tsx
  • wework/src/components/layout/DeviceStatusPrompt.tsx
  • wework/src/components/layout/MobileWorkbenchLayout.tsx
  • wework/src/components/projects/ProjectCreateDialog.test.tsx
  • wework/src/components/projects/ProjectCreateDialog.tsx
  • wework/src/features/workbench/WorkbenchProvider.test.tsx
  • wework/src/features/workbench/WorkbenchProvider.tsx
  • wework/src/i18n/locales/en/common.json
  • wework/src/i18n/locales/zh-CN/common.json
  • wework/src/lib/device-capabilities.test.ts
  • wework/src/lib/device-capabilities.ts
  • wework/src/lib/device-selection.test.ts
  • wework/src/lib/device-selection.ts
  • wework/src/pages/WorkbenchPage.tsx
  • wework/src/stream/chatStream.test.ts
  • wework/src/stream/chatStream.ts
  • wework/src/types/api.ts
  • wework/src/types/device-events.ts
  • wework/src/types/devices.ts

Comment on lines +89 to +97
const runUpgrades = async (deviceIds: string[]) => {
if (deviceIds.length === 0) return
setManualPending(true)
try {
await Promise.all(deviceIds.map(deviceId => onUpgradeDevice(deviceId)))
} finally {
setManualPending(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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Guard runUpgrades against unhandled promise rejections.

If one upgrade request rejects, this path can surface an unhandled rejection because callers invoke it with void .... Use a non-throwing aggregation path here.

💡 Suggested patch
   const runUpgrades = async (deviceIds: string[]) => {
     if (deviceIds.length === 0) return
     setManualPending(true)
     try {
-      await Promise.all(deviceIds.map(deviceId => onUpgradeDevice(deviceId)))
+      await Promise.allSettled(
+        deviceIds.map(deviceId => Promise.resolve().then(() => onUpgradeDevice(deviceId))),
+      )
     } finally {
       setManualPending(false)
     }
   }
🤖 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/layout/DeviceStatusPrompt.tsx` around lines 89 - 97,
The runUpgrades function can surface unhandled promise rejections when callers
invoke it with void; change its aggregation to a non-throwing form (e.g., use
Promise.allSettled or catch each onUpgradeDevice promise) so failures are
captured rather than propagated, preserve the early return for empty deviceIds
and ensure setManualPending(true) is still set before starting and
setManualPending(false) in the finally/cleanup block; update references inside
runUpgrades to use onUpgradeDevice for individual tasks and handle
results/errors without rethrowing so callers won't receive unhandled rejections.

Comment on lines +547 to 551
const handleUpgradeDevice = async (nextDeviceId: string) => {
if (!onUpgradeDevice) return
setProjectCreateError(null)
await onUpgradeDevice(nextDeviceId)
}

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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Catch upgrade callback failures in handleUpgradeDevice.

This async path is triggered from void click handlers; if onUpgradeDevice rejects, it can become an unhandled rejection and leaves the user without feedback.

💡 Suggested patch
   const handleUpgradeDevice = async (nextDeviceId: string) => {
     if (!onUpgradeDevice) return
     setProjectCreateError(null)
-    await onUpgradeDevice(nextDeviceId)
+    try {
+      await onUpgradeDevice(nextDeviceId)
+    } catch (error) {
+      setProjectCreateError(
+        error instanceof Error
+          ? error.message
+          : t('workbench.project_create_failed', '项目创建失败'),
+      )
+    }
   }
🤖 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/projects/ProjectCreateDialog.tsx` around lines 547 -
551, The handleUpgradeDevice async function can let rejections from
onUpgradeDevice become unhandled; wrap the await on
onUpgradeDevice(nextDeviceId) in a try/catch inside handleUpgradeDevice (and
keep the early return when onUpgradeDevice is falsy), and on error call
setProjectCreateError with a user-facing message (or error.message) and
optionally log the error so callers who trigger the void click handler won’t
produce an unhandled rejection; reference the handleUpgradeDevice function and
the onUpgradeDevice prop when making this change.

@qdaxb
qdaxb merged commit d501571 into wecode-ai:main Jun 9, 2026
22 checks passed
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.

2 participants