Skip to content

Codex/feature/wework source header - #1441

Merged
qdaxb merged 1 commit into
wecode-ai:mainfrom
FicoHu:codex/feature/wework-source-header
Jun 17, 2026
Merged

qdaxb merged 1 commit into
wecode-ai:mainfrom
FicoHu:codex/feature/wework-source-header

Conversation

@FicoHu

@FicoHu FicoHu commented Jun 17, 2026 •

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

Release Notes

  • New Features

    • Enabled opening workspace files directly from tool blocks in chat, and from assistant-rendered actions in the workbench panel.
  • Bug Fixes

    • Improved device offline handling by adding a reconnect confirmation window before marking tasks failed.
    • Refined websocket disconnect behavior to re-check the latest online state before applying offline cleanup.
    • Cleared stale assistant stream errors when new block updates arrive.
  • Documentation

    • Updated offline device handling guidance to match the reconnect-confirmation flow.
  • UI Improvements

    • Improved sizing for embedded terminal frames and iframe layout consistency.
  • Tests

    • Expanded coverage for project-id edge cases and disconnect reconnection behavior, plus updated relevant UI/chat reducer tests.

@coderabbitai

coderabbitai Bot commented Jun 17, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Device disconnections now include a configurable grace period to avoid spurious offline cleanup on transient reconnects. Message reducer clears stale error state when assistant updates arrive, enabling recovery after disconnects. Workspace file opening is threaded from chat tool blocks through the workbench hierarchy via callbacks and request objects, with path validation and Safari-friendly terminal sizing added.

Changes

Project ID zero in standalone chat

Layer / File(s) Summary
allow_zero flag in project ID normalization
executor/modes/local/capabilities.py
_normalize_project_id accepts keyword-only allow_zero parameter (False by default) to preserve "0" when enabled; get_project_id derives allow_zero from task_data.standalone_chat_workspace.
Updated project_id=0 tests
executor/tests/modes/local/test_global_capabilities.py
Replaces single zero-id test with two cases: frontend device chat (expects empty project_id and is_project_task=False) and WeWork standalone chat (expects "0" preserved when standalone_chat_workspace=True).

Device disconnect grace period and transience

Layer / File(s) Summary
Grace period and transient disconnect handling
backend/app/api/ws/device_namespace.py
DEVICE_DISCONNECT_FAILURE_GRACE_SECONDS constant introduced; on_disconnect now waits briefly and rechecks device's current online socket before marking offline and failing tasks.
Transient disconnect verification test
backend/tests/api/ws/test_device_reconnect_storm.py
New test verifies that on_disconnect rechecks device's current socket before cleanup, confirming transient disconnects (newer socket detected) skip offline/broadcast/failure side effects.
Offline device handling documentation
docs/en/user-guide/ai-devices/local-device-support.md, docs/zh/user-guide/ai-devices/local-device-support.md
English and Chinese guides updated to describe reconnect confirmation window before offline treatment; tasks marked FAILED and slots freed only after confirmed offline; device shown grayed in selector.

Message state recovery after disconnect

Layer / File(s) Summary
Error clearing and block state management
packages/chat-core/src/workbench-message-reducer.ts
assistant_started, assistant_cached, assistant_chunk, assistant_done now clear stale error/errorType; block_created delegated to createBlockCreatedMessage helper; block_updated uses withActiveStreamState and updates only targeted block; new helpers: clearMessageError, withActiveStreamState, isActiveBlockStatus, createBlockCreatedMessage.
Message recovery from error state test
packages/chat-core/src/workbench-message-reducer.test.ts
New test verifies that after assistant_error (disconnect), incoming block_updated for active block clears stale error and updates block status/output correctly.
Test timeout adjustment
frontend/src/__tests__/features/settings/components/SkillListWithScope.default-enabled.test.tsx
Explicit 10000ms timeout added to waitFor assertion after skill settings save.

Opening workspace files from chat messages

Layer / File(s) Summary
Type contract
wework/src/types/workspace-files.ts
New exported WorkspaceFileOpenRequest interface with id and path fields.
Message component callback wiring
wework/src/components/chat/MessageList.tsx, wework/src/components/chat/ScrollableMessageArea.tsx, wework/src/components/chat/blocks/ToolBlocksDisplay.tsx
onOpenWorkspaceFile callback added and threaded through component hierarchy: MessageList → AssistantMessage → ToolBlocksDisplay → ToolActivityGroup → ToolBlockItem.
File path extraction and callback invocation
wework/src/components/chat/blocks/ToolBlockItem.tsx
New getWorkspaceFilePath helper extracts file_path/path for file-based tools (read/write/edit); header click invokes callback for file-based tools instead of toggling expanded state; accessibility attributes (aria-label, aria-expanded) added.
Workbench file open request handling
wework/src/components/layout/DesktopWorkbenchMain.tsx
State openFileRequest stores next request; openWorkspaceFileFromMessage handler normalizes path, increments request id, triggers files panel; callback wired to ScrollableMessageArea; request passed to RightWorkspacePanel.
File panel path resolution and opening
wework/src/components/layout/workspace-panels/FileWorkspacePanel.tsx
resolveWorkspaceFilePath normalizes and validates path (prevents traversal, maps under target.path); openFilePath callback resolves path and delegates to openFile flow; useEffect reacts to openFileRequest changes with cancellation guard; preview retry handler updated to use openFilePath.
Request propagation through panels
wework/src/components/layout/workspace-panels/RightWorkspacePanel.tsx
openFileRequest prop added and forwarded to FileWorkspacePanel.
End-to-end file opening test
wework/src/components/layout/DesktopWorkbenchLayout.test.tsx
New test mocks workspace file reads, renders edit_file tool block, clicks file-opening button, verifies preview displays content and correct file tab selected.

Terminal iframe sizing

Layer / File(s) Summary
Safari-friendly terminal iframe sizing
wework/src/components/layout/workspace-panels/WorkspacePanelCards.tsx, wework/src/components/layout/workspace-panels/WorkspacePanelCards.test.tsx
Embedded terminal <iframe> className updated to include h-full and w-full for explicit full-height/full-width sizing; test verifies classes are applied.

Sequence Diagram(s)

sequenceDiagram
  participant Device as Device<br/>(socket_id)
  participant Executor as DeviceNamespace<br/>on_disconnect
  participant Check as Check Online<br/>Socket
  participant Cleanup as Offline Cleanup<br/>& Fail Tasks

  Device->>Executor: disconnect (socket_id="old")
  Executor->>Executor: await grace period<br/>(2 sec)
  Executor->>Check: get_device_online_info()
  Check-->>Executor: current socket_id
  alt socket_id unchanged (stale)
    Executor->>Cleanup: mark offline, fail tasks
    Cleanup-->>Device: tasks marked FAILED
  else socket_id changed (reconnected)
    Executor-->>Device: skip cleanup, device online
  end
Loading
sequenceDiagram
  participant Chat as Chat<br/>MessageList
  participant ToolBlock as ToolBlockItem<br/>(file-based tool)
  participant Workbench as DesktopWorkbenchMain
  participant FilePanel as FileWorkspacePanel
  participant Workspace as Workspace<br/>File System

  Chat->>ToolBlock: onOpenWorkspaceFile callback
  ToolBlock->>ToolBlock: getWorkspaceFilePath(block)<br/>→ path
  ToolBlock->>Workbench: onOpenWorkspaceFile(path)
  Workbench->>Workbench: openWorkspaceFileFromMessage<br/>→ create openFileRequest
  Workbench->>FilePanel: pass openFileRequest prop
  FilePanel->>FilePanel: resolveWorkspaceFilePath<br/>(validate, prevent traversal)
  FilePanel->>FilePanel: openFilePath(resolved_path)
  FilePanel->>Workspace: openFile request
  Workspace-->>FilePanel: file content
  FilePanel->>Chat: display preview
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • wecode-ai/Wegent#1375: Modifies FileWorkspacePanel.tsx and RightWorkspacePanel.tsx for workspace sidebar features; directly code-adjacent to the file-opening flow introduced in this PR.
  • wecode-ai/Wegent#1393: Modifies packages/chat-core/src/workbench-message-reducer.ts block-status finalization logic; directly related to the message-state recovery changes here.

Suggested reviewers

  • qdaxb

Poem

🐇 When devices flicker and briefly disconnect,
A grace period waits, rechecks with respect.
Messages cleared of their error-state scars,
Files open from chat like wishes on stars.
Safari's terminal stretches full-wide—huzzah! 🎉

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 6.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'Codex/feature/wework source header' is vague and does not clearly convey the specific changes in this pull request, which include device reconnection handling, project ID validation, message error clearing, and workspace file opening features. Revise the title to be more descriptive of the main changes, e.g., 'Add transient device disconnect handling and workspace file opening from chat' or similar to better communicate the primary purpose.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
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

Warning

Review ran into problems

🔥 Problems

Stopped waiting for pipeline failures after 30000ms. One of your pipelines takes longer than our 30000ms fetch window to run, so review may not consider pipeline-failure results for inline comments if any failures occurred after the fetch window. Increase the timeout if you want to wait longer or run a @coderabbit review after the pipeline has finished.


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.

🧹 Nitpick comments (2)
executor/modes/local/capabilities.py (1)

92-127: 💤 Low value

Consider documenting the new standalone chat workspace behavior.

The docstring for get_project_id doesn't mention that project_id="0" is now preserved when standalone_chat_workspace=True. Adding this detail would help maintainers understand the special handling.

Similarly, the private helper _normalize_project_id gained an allow_zero parameter but has no docstring explaining its purpose.

📝 Suggested docstring improvements

For get_project_id:

 def get_project_id(task_data: ExecutionRequest) -> str:
-    """Return the project ID for a project-backed execution request."""
+    """Return the project ID for a project-backed execution request.
+    
+    In standalone chat workspace mode, project_id="0" is preserved as a valid
+    project identifier. Otherwise, "0" is normalized to empty string.
+    """

For _normalize_project_id:

 def _normalize_project_id(project_id: Any, *, allow_zero: bool = False) -> str:
+    """Normalize project_id to a string, returning empty string for invalid values.
+    
+    Args:
+        project_id: Raw project ID value (int, str, or None).
+        allow_zero: If True, preserve "0" as valid; otherwise normalize to "".
+    
+    Returns:
+        Normalized project ID string, or "" if invalid.
+    """
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@executor/modes/local/capabilities.py` around lines 92 - 127, Update the
docstring for the get_project_id function to document the new behavior where
project_id="0" is preserved when standalone_chat_workspace is True.
Additionally, add or update the docstring for the _normalize_project_id helper
function to explain the purpose of its allow_zero parameter and how it affects
the normalization logic. This will help future maintainers understand the
special handling in both functions.
executor/tests/modes/local/test_global_capabilities.py (1)

19-27: ⚡ Quick win

Consider consolidating the duplicate WeWork standalone chat tests.

Both test_wework_standalone_chat_uses_global_capabilities (lines 19-27) and test_wework_standalone_chat_project_zero_keeps_project_header_id (lines 39-47) set up identical request objects with project_id=0 and standalone_chat_workspace=True. The first test only checks is_project_task, while the second checks both get_project_id and is_project_task.

Consolidating them into a single test that verifies both assertions would reduce duplication.

♻️ Proposed consolidation
-def test_wework_standalone_chat_uses_global_capabilities():
+def test_wework_standalone_chat_with_project_zero_uses_global_capabilities():
     request = ExecutionRequest(
         task_id=1901,
         project_id=0,
         standalone_chat_workspace=True,
     )
 
+    assert get_project_id(request) == "0"
     assert is_project_task(request) is True


 def test_frontend_device_chat_project_zero_does_not_use_project_capabilities():
     request = ExecutionRequest(
         task_id=1902,
         project_id=0,
     )

     assert get_project_id(request) == ""
     assert is_project_task(request) is False
-
-
-def test_wework_standalone_chat_project_zero_keeps_project_header_id():
-    request = ExecutionRequest(
-        task_id=1904,
-        project_id=0,
-        standalone_chat_workspace=True,
-    )
-
-    assert get_project_id(request) == "0"
-    assert is_project_task(request) is True

Also applies to: 39-47

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@executor/tests/modes/local/test_global_capabilities.py` around lines 19 - 27,
Consolidate the two WeWork standalone chat tests by removing
test_wework_standalone_chat_uses_global_capabilities and updating
test_wework_standalone_chat_project_zero_keeps_project_header_id to include all
assertions from both tests. The consolidated test should create a single
ExecutionRequest with project_id=0 and standalone_chat_workspace=True, then
verify both the is_project_task and get_project_id assertions in one test
function to eliminate the duplicate setup code and test redundancy.
🤖 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.

Nitpick comments:
In `@executor/modes/local/capabilities.py`:
- Around line 92-127: Update the docstring for the get_project_id function to
document the new behavior where project_id="0" is preserved when
standalone_chat_workspace is True. Additionally, add or update the docstring for
the _normalize_project_id helper function to explain the purpose of its
allow_zero parameter and how it affects the normalization logic. This will help
future maintainers understand the special handling in both functions.

In `@executor/tests/modes/local/test_global_capabilities.py`:
- Around line 19-27: Consolidate the two WeWork standalone chat tests by
removing test_wework_standalone_chat_uses_global_capabilities and updating
test_wework_standalone_chat_project_zero_keeps_project_header_id to include all
assertions from both tests. The consolidated test should create a single
ExecutionRequest with project_id=0 and standalone_chat_workspace=True, then
verify both the is_project_task and get_project_id assertions in one test
function to eliminate the duplicate setup code and test redundancy.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ffbf8e07-9316-4a35-97d4-099a98bc6cc2

📥 Commits

Reviewing files that changed from the base of the PR and between 350f7d9 and 03326d9.

📒 Files selected for processing (2)
  • executor/modes/local/capabilities.py
  • executor/tests/modes/local/test_global_capabilities.py

@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 (2)
packages/chat-core/src/workbench-message-reducer.ts (1)

207-217: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Derive active-state from effective block status, not only updates.status

On Line 211, recovery depends solely on action.updates.status, but status is optional in ProcessingBlockUpdate. A block_updated carrying only incremental fields (e.g., toolOutput) for an already-streaming block will not clear stale error/errorType and can leave message status stuck at failed.

Proposed fix
     case 'block_updated':
       return state.map(message =>
-        message.subtaskId === action.subtaskId
-          ? {
-              ...withActiveStreamState(message, isActiveBlockStatus(action.updates.status)),
-              blocks: (message.blocks ?? []).map(block =>
-                block.id === action.blockId
-                  ? ({ ...block, ...action.updates } as WorkbenchProcessingBlock)
-                  : block
-              ),
-            }
-          : message
+        message.subtaskId === action.subtaskId
+          ? (() => {
+              const currentBlock = (message.blocks ?? []).find(
+                block => block.id === action.blockId
+              )
+              const effectiveStatus = action.updates.status ?? currentBlock?.status
+              const nextMessage = withActiveStreamState(
+                message,
+                isActiveBlockStatus(effectiveStatus)
+              )
+
+              return {
+                ...nextMessage,
+                blocks: (message.blocks ?? []).map(block =>
+                  block.id === action.blockId
+                    ? ({ ...block, ...action.updates } as WorkbenchProcessingBlock)
+                    : block
+                ),
+              }
+            })()
+          : message
       )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/chat-core/src/workbench-message-reducer.ts` around lines 207 - 217,
The issue is that the call to withActiveStreamState with
isActiveBlockStatus(action.updates.status) on the block_updated case relies
solely on the optional status field from action.updates. When a block_updated
action contains only incremental fields (like toolOutput) without a status
field, action.updates.status will be undefined, causing isActiveBlockStatus to
receive undefined and failing to properly clear stale error/errorType fields.
Fix this by deriving the effective block status more comprehensively by
combining the current block's status with the updates provided, rather than
relying only on the optional action.updates.status field when calling
isActiveBlockStatus.
wework/src/components/layout/DesktopWorkbenchMain.tsx (1)

430-444: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Clear stale file-open requests on session switch

openFileRequest is not reset when rightPanelSessionKey changes. That can replay a previous chat-triggered file-open in a different task/project when the Files tab mounts again.

Suggested fix
   useLayoutEffect(() => {
     if (previousRightPanelSessionKey.current === rightPanelSessionKey) {
       return
     }

     previousRightPanelSessionKey.current = rightPanelSessionKey
     reviewRequestSequence.current += 1
+    setOpenFileRequest(null)
     setRightPanelView('launcher')
     setRightPanelTabs([])
     setReviewState({
       loading: false,
       diff: '',
       error: undefined,
     })
   }, [rightPanelSessionKey])
🤖 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/DesktopWorkbenchMain.tsx` around lines 430 -
444, The useLayoutEffect hook that responds to rightPanelSessionKey changes is
not resetting the openFileRequest state, which allows stale file-open requests
from previous sessions to be replayed when switching between tasks or projects.
Add a line to reset openFileRequest to null or undefined in the useLayoutEffect
hook (after the previousRightPanelSessionKey.current assignment and alongside
the other state resets like setRightPanelView and setReviewState) to ensure no
stale file-open requests persist across session switches.
🧹 Nitpick comments (2)
packages/chat-core/src/workbench-message-reducer.test.ts (1)

178-205: ⚡ Quick win

Strengthen regression signal by asserting pre-update error state (Line 178 onward).

This test only checks the final resumed state. Add precondition assertions on withTool (status: 'failed', error, errorType) so the test proves the clear/recovery happens specifically on block_updated.

Suggested diff
     const withTool = reduceWorkbenchMessages(state, {
       type: 'block_created',
       subtaskId: 9,
       block: {
         id: 'call_1',
         subtaskId: 9,
         type: 'tool',
         toolName: 'bash',
         status: 'pending',
         createdAt: 1770000000000,
       },
     })
+    expect(withTool[0].status).toBe('failed')
+    expect(withTool[0].error).toBe('Device disconnected')
+    expect(withTool[0].errorType).toBe('container_error')
+
     const resumed = reduceWorkbenchMessages(withTool, {
       type: 'block_updated',
       subtaskId: 9,
       blockId: 'call_1',
       updates: {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/chat-core/src/workbench-message-reducer.test.ts` around lines 178 -
205, The test only verifies the final state after block_updated is called but
lacks precondition assertions on the withTool state. Add assertions immediately
after the withTool constant is assigned to verify that the block has status as
'failed' and contains error and errorType properties. This strengthens the
regression signal by proving that the subsequent block_updated action with
status 'streaming' specifically clears these error states rather than just
setting a passing end state, making the error recovery behavior explicit and
testable.
backend/tests/api/ws/test_device_reconnect_storm.py (1)

207-207: ⚡ Quick win

Assert the configured grace-period value, not just await count.

At Line 207, assert_awaited_once() verifies a sleep happened, but not that it used DEVICE_DISCONNECT_FAILURE_GRACE_SECONDS. This test can pass even if the grace window is accidentally changed.

Suggested diff
-    device_namespace.asyncio.sleep.assert_awaited_once()
+    device_namespace.asyncio.sleep.assert_awaited_once_with(
+        device_namespace.DEVICE_DISCONNECT_FAILURE_GRACE_SECONDS
+    )
🤖 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/tests/api/ws/test_device_reconnect_storm.py` at line 207, The
assertion on device_namespace.asyncio.sleep only verifies that sleep was called
once using assert_awaited_once(), but does not validate that it was called with
the correct grace period value. Replace assert_awaited_once() with
assert_awaited_once_with() and pass the expected
DEVICE_DISCONNECT_FAILURE_GRACE_SECONDS constant as an argument to ensure the
test fails if someone accidentally changes the grace window value.
🤖 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/chat/blocks/ToolBlockItem.tsx`:
- Around line 50-51: The aria-label attributes in the ToolBlockItem component
contain hardcoded Chinese strings instead of using i18n keys. Replace the
hardcoded Chinese strings ('收起工具详情' and '展开工具详情') in the aria-label props with
translation keys from useTranslation('chat'), such as t('collapseToolDetails')
and t('expandToolDetails'). Import useTranslation from '`@/hooks/useTranslation`'
if not already imported, then add the corresponding translation keys to both the
Chinese and English language files in the chat namespace without using fallback
literals.

In `@wework/src/components/layout/workspace-panels/FileWorkspacePanel.tsx`:
- Around line 27-42: The function `resolveWorkspaceFilePath` has a security
issue where absolute paths starting with `/` are returned immediately without
validation at line 30, allowing access to files outside the workspace root like
`/etc/passwd`. Remove the early return for paths starting with `/` and instead
process all paths through the same segment validation logic that handles
relative paths. After constructing the final resolved path, add validation to
ensure the resolved path stays within the current workspace root (target.path)
and return null if the path would escape the workspace boundaries.

---

Outside diff comments:
In `@packages/chat-core/src/workbench-message-reducer.ts`:
- Around line 207-217: The issue is that the call to withActiveStreamState with
isActiveBlockStatus(action.updates.status) on the block_updated case relies
solely on the optional status field from action.updates. When a block_updated
action contains only incremental fields (like toolOutput) without a status
field, action.updates.status will be undefined, causing isActiveBlockStatus to
receive undefined and failing to properly clear stale error/errorType fields.
Fix this by deriving the effective block status more comprehensively by
combining the current block's status with the updates provided, rather than
relying only on the optional action.updates.status field when calling
isActiveBlockStatus.

In `@wework/src/components/layout/DesktopWorkbenchMain.tsx`:
- Around line 430-444: The useLayoutEffect hook that responds to
rightPanelSessionKey changes is not resetting the openFileRequest state, which
allows stale file-open requests from previous sessions to be replayed when
switching between tasks or projects. Add a line to reset openFileRequest to null
or undefined in the useLayoutEffect hook (after the
previousRightPanelSessionKey.current assignment and alongside the other state
resets like setRightPanelView and setReviewState) to ensure no stale file-open
requests persist across session switches.

---

Nitpick comments:
In `@backend/tests/api/ws/test_device_reconnect_storm.py`:
- Line 207: The assertion on device_namespace.asyncio.sleep only verifies that
sleep was called once using assert_awaited_once(), but does not validate that it
was called with the correct grace period value. Replace assert_awaited_once()
with assert_awaited_once_with() and pass the expected
DEVICE_DISCONNECT_FAILURE_GRACE_SECONDS constant as an argument to ensure the
test fails if someone accidentally changes the grace window value.

In `@packages/chat-core/src/workbench-message-reducer.test.ts`:
- Around line 178-205: The test only verifies the final state after
block_updated is called but lacks precondition assertions on the withTool state.
Add assertions immediately after the withTool constant is assigned to verify
that the block has status as 'failed' and contains error and errorType
properties. This strengthens the regression signal by proving that the
subsequent block_updated action with status 'streaming' specifically clears
these error states rather than just setting a passing end state, making the
error recovery behavior explicit and testable.
🪄 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: db691014-f0e9-4695-8d4f-84de51db3520

📥 Commits

Reviewing files that changed from the base of the PR and between 03326d9 and f7e823c.

📒 Files selected for processing (18)
  • backend/app/api/ws/device_namespace.py
  • backend/tests/api/ws/test_device_reconnect_storm.py
  • docs/en/user-guide/ai-devices/local-device-support.md
  • docs/zh/user-guide/ai-devices/local-device-support.md
  • frontend/src/__tests__/features/settings/components/SkillListWithScope.default-enabled.test.tsx
  • packages/chat-core/src/workbench-message-reducer.test.ts
  • packages/chat-core/src/workbench-message-reducer.ts
  • wework/src/components/chat/MessageList.tsx
  • wework/src/components/chat/ScrollableMessageArea.tsx
  • wework/src/components/chat/blocks/ToolBlockItem.tsx
  • wework/src/components/chat/blocks/ToolBlocksDisplay.tsx
  • wework/src/components/layout/DesktopWorkbenchLayout.test.tsx
  • wework/src/components/layout/DesktopWorkbenchMain.tsx
  • wework/src/components/layout/workspace-panels/FileWorkspacePanel.tsx
  • wework/src/components/layout/workspace-panels/RightWorkspacePanel.tsx
  • wework/src/components/layout/workspace-panels/WorkspacePanelCards.test.tsx
  • wework/src/components/layout/workspace-panels/WorkspacePanelCards.tsx
  • wework/src/types/workspace-files.ts
✅ Files skipped from review due to trivial changes (3)
  • docs/en/user-guide/ai-devices/local-device-support.md
  • wework/src/components/layout/workspace-panels/WorkspacePanelCards.tsx
  • wework/src/types/workspace-files.ts

Comment on lines +50 to +51
aria-label={expanded ? '收起工具详情' : '展开工具详情'}
aria-expanded={expanded}

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 | 🟡 Minor | ⚡ Quick win

Replace hardcoded aria labels with i18n keys

These new aria-label literals are hardcoded Chinese strings, which breaks localization consistency and screen-reader language expectations in non-Chinese locales. Please route them through useTranslation('chat') keys instead.

Suggested fix
-          aria-label={expanded ? '收起工具详情' : '展开工具详情'}
+          aria-label={
+            expanded ? t('tool_block.collapse_details') : t('tool_block.expand_details')
+          }

As per coding guidelines, “Import from '@/hooks/useTranslation' for i18n in application code” and “Do not add fallback literals to hide missing translations in new code. Add the key to the correct namespace in both languages instead.”

📝 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
aria-label={expanded ? '收起工具详情' : '展开工具详情'}
aria-expanded={expanded}
aria-label={
expanded ? t('tool_block.collapse_details') : t('tool_block.expand_details')
}
aria-expanded={expanded}
🤖 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/blocks/ToolBlockItem.tsx` around lines 50 - 51,
The aria-label attributes in the ToolBlockItem component contain hardcoded
Chinese strings instead of using i18n keys. Replace the hardcoded Chinese
strings ('收起工具详情' and '展开工具详情') in the aria-label props with translation keys
from useTranslation('chat'), such as t('collapseToolDetails') and
t('expandToolDetails'). Import useTranslation from '`@/hooks/useTranslation`' if
not already imported, then add the corresponding translation keys to both the
Chinese and English language files in the chat namespace without using fallback
literals.

Source: Coding guidelines

Comment on lines +27 to +42
function resolveWorkspaceFilePath(target: WorkspaceTarget, path: string): string | null {
const normalizedPath = path.trim().replace(/\\/g, '/')
if (!normalizedPath) return null
if (normalizedPath.startsWith('/')) return normalizedPath

const segments: string[] = []
for (const segment of normalizedPath.split('/')) {
if (!segment || segment === '.') continue
if (segment === '..') return null
segments.push(segment)
}
if (segments.length === 0) return null

const root = target.path.replace(/\/+$/, '') || '/'
const child = segments.join('/')
return root === '/' ? `/${child}` : `${root}/${child}`

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

Constrain absolute requests to the workspace root.

Line 30 returns any leading-slash path unchanged, so /etc/passwd, /workspace/../secret, or another workspace path bypasses the target.path anchoring before openFilePath() sends it to readWorkspaceTextFile(). Normalize absolute and relative requests through the same segment validation, then reject resolved paths outside the current workspace root.

🛡️ Proposed fix
 function resolveWorkspaceFilePath(target: WorkspaceTarget, path: string): string | null {
   const normalizedPath = path.trim().replace(/\\/g, '/')
   if (!normalizedPath) return null
-  if (normalizedPath.startsWith('/')) return normalizedPath
+
+  const root = target.path.trim().replace(/\\/g, '/').replace(/\/+$/, '') || '/'
+  const candidate = normalizedPath.startsWith('/')
+    ? normalizedPath
+    : root === '/'
+      ? `/${normalizedPath}`
+      : `${root}/${normalizedPath}`
 
   const segments: string[] = []
-  for (const segment of normalizedPath.split('/')) {
+  for (const segment of candidate.split('/')) {
     if (!segment || segment === '.') continue
     if (segment === '..') return null
     segments.push(segment)
   }
   if (segments.length === 0) return null
 
-  const root = target.path.replace(/\/+$/, '') || '/'
-  const child = segments.join('/')
-  return root === '/' ? `/${child}` : `${root}/${child}`
+  const resolvedPath = `/${segments.join('/')}`
+  if (root !== '/') {
+    const rootWithSlash = `${root}/`
+    if (resolvedPath !== root && !resolvedPath.startsWith(rootWithSlash)) {
+      return null
+    }
+  }
+
+  return resolvedPath
 }
🤖 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/workspace-panels/FileWorkspacePanel.tsx` around
lines 27 - 42, The function `resolveWorkspaceFilePath` has a security issue
where absolute paths starting with `/` are returned immediately without
validation at line 30, allowing access to files outside the workspace root like
`/etc/passwd`. Remove the early return for paths starting with `/` and instead
process all paths through the same segment validation logic that handles
relative paths. After constructing the final resolved path, add validation to
ensure the resolved path stays within the current workspace root (target.path)
and return null if the path would escape the workspace boundaries.

@FicoHu
FicoHu force-pushed the codex/feature/wework-source-header branch from f7e823c to b5b5983 Compare June 17, 2026 09:11
@qdaxb
qdaxb merged commit e1ccc8c into wecode-ai:main Jun 17, 2026
24 checks passed
@coderabbitai coderabbitai Bot mentioned this pull request Jul 24, 2026
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