Skip to content

fix(wework): keep background interactions live - #2042

Merged
qdaxb merged 19 commits into
mainfrom
fix/background-pane-tool-events
Jul 19, 2026
Merged

qdaxb merged 19 commits into
mainfrom
fix/background-pane-tool-events

Conversation

@qdaxb

@qdaxb qdaxb commented Jul 18, 2026 •

Copy link
Copy Markdown
Contributor

What changed

  • keep all runtime task panes mounted so background panes retain their live runtime subscription
  • keep the native runtime event listener application-scoped and rename localChatStream to runtimeChatStream
  • let the shared Codex notification loop continue draining events while request_user_input or MCP elicitation waits for an answer
  • correlate concurrent interaction answers by request/item ID and reject new waiters immediately after the answer channel closes
  • add regression coverage for reverse-order answers, delayed answers, channel closure, and notification volume beyond the broadcast buffer
  • extend the real desktop flow to switch away during request_user_input, return to the task, answer it, and capture the key states

Root cause

Inactive panes could be evicted and unmounted, which unsubscribed them from runtime events. Separately, the Executor synchronously waited for an interaction answer inside the same loop that drains Codex notifications. A long wait could overflow the broadcast receiver and surface Lagged after the user answered.

Codex remains the authoritative source for pending interactions. This change does not add frontend replay state or thread-resume behavior.

Validation

  • Wework: 180 test files, 1800 tests passed
  • Executor: full CI test suite passed, including interaction routing, delayed-answer flood, and 21 runtime send contract tests
  • pre-push ESLint, TypeScript, Wework tests, cargo fmt, cargo test, and clippy passed
  • real Tauri E2E passed the full background request_user_input flow and captured all three verification states
  • final CI: all checks passed, including Wework Desktop E2E, Executor E2E, and four frontend E2E shards
  • evidence run: https://github.com/wecode-ai/Wegent/actions/runs/29660334293

@coderabbitai

coderabbitai Bot commented Jul 18, 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

The change migrates chat streaming to runtime APIs, adds asynchronous Codex user-input handling and desktop E2E coverage, retains panes for all runtime tasks, and updates desktop automation visibility checks and E2E environment setup.

Changes

Runtime chat stream migration

Layer / File(s) Summary
Runtime stream lifecycle and event routing
wework/src/api/runtime/runtimeChatStream.ts
Runtime subscriptions share native listener state, route scoped events, track lifecycle counters, and use runtime-specific logging.
Runtime stream service wiring
wework/src/api/local/localServices.ts, wework/src/api/hybrid/hybridServices.ts
Local and cloud services construct runtime chat streams, including a reusable cloud stream.
Runtime stream tests and developer controls
wework/src/api/runtime/runtimeChatStream.test.ts, wework/src/lib/developerCommandMenu.*
Tests and developer commands target runtime stream APIs, logging, lifecycle, and IPC behavior.

Request-user-input workflow

Layer / File(s) Summary
Asynchronous executor input responses
executor/src/agents/codex.rs, executor/tests/app_runtime_work_send_contract.rs
Codex user-input and MCP elicitation responses run in spawned tasks using shared receivers and error propagation.
Desktop request-user-input scenario
wework/e2e/desktop/task-flow.e2e.mjs, wework/scripts/ai-verify-environment.mjs
The desktop E2E flow drives the request-user-input scenario, validates the Direction question, captures screenshots, and verifies completion.
Visible desktop control matching
wework/src/e2e/automation.ts
Desktop commands can filter candidates by computed visibility and viewport bounds.
E2E environment support
.github/workflows/wework-e2e.yml
The E2E job installs ImageMagick for screenshot capture.

Runtime workbench pane retention

Layer / File(s) Summary
All-runtime pane key derivation
wework/src/components/layout/workbenchPaneStack.tsx
Pane keys are generated for every runtime task rather than only running tasks.
Desktop and mobile pane pinning
wework/src/components/layout/DesktopWorkbenchMain.tsx, wework/src/components/layout/MobileWorkbenchLayout.tsx
Both layouts use all runtime pane keys when calculating pinned panes.
Pane retention tests
wework/src/components/layout/workbenchPaneStack.test.tsx
Tests expect multiple runtime panes to remain mounted and included in derived keys.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant DesktopE2ETest
  participant DesktopE2EServer
  participant RuntimeChatStream
  participant CodexExecutor
  participant DesktopUI
  DesktopE2ETest->>DesktopE2EServer: setScenario(request_user_input)
  DesktopUI->>RuntimeChatStream: send prompt
  RuntimeChatStream->>CodexExecutor: forward runtime request
  CodexExecutor->>DesktopE2EServer: emit request_user_input response
  DesktopE2EServer->>RuntimeChatStream: emit tool event
  RuntimeChatStream->>DesktopUI: forward scoped event
  DesktopE2ETest->>DesktopE2EServer: releaseRequestUserInputResponse()
  DesktopUI->>DesktopE2ETest: render question and completion
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 8.11% 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
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: keeping background interactions and runtime subscriptions live.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/background-pane-tool-events

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@qdaxb
qdaxb marked this pull request as ready for review July 18, 2026 17:36

@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

🤖 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/e2e/desktop/task-flow.e2e.mjs`:
- Around line 521-523: Update releaseRequestUserInputResponse() and the
associated task-row reopening flow to await a server acknowledgement that
resolves only after handleModelResponse() completes writeSse(), ensuring the
click exercises replay rather than live delivery. Add or update E2E regression
coverage for this ordering in the same flow.

In `@wework/src/api/local/localChatStream.ts`:
- Around line 194-201: Update the pending-block cache around the event handling
logic to store current block state keyed by taskKey and block ID, rather than
appending raw events to a 500-entry list. Merge created/updated events for each
block, remove blocks once resolved, and enforce
MAX_PENDING_BLOCK_EVENTS_PER_TASK against the number of retained blocks so late
subscribers still receive unresolved request_user_input interactions.
🪄 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: 23aa66e9-353a-4957-9cf5-47a6bb188f2d

📥 Commits

Reviewing files that changed from the base of the PR and between fd84d28 and 2f78e9c.

📒 Files selected for processing (6)
  • .github/workflows/wework-e2e.yml
  • wework/e2e/desktop/task-flow.e2e.mjs
  • wework/src/api/hybrid/hybridServices.ts
  • wework/src/api/local/localChatStream.test.ts
  • wework/src/api/local/localChatStream.ts
  • wework/src/e2e/automation.ts

Comment thread wework/e2e/desktop/task-flow.e2e.mjs
Comment thread wework/src/api/local/localChatStream.ts Outdated
@qdaxb qdaxb changed the title fix(wework): replay background pane tool events fix(wework): keep background interactions live Jul 18, 2026

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

🧹 Nitpick comments (1)
wework/src/components/layout/DesktopWorkbenchMain.tsx (1)

277-280: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Simplify pinnedPaneKeys array.

Because validTerminalPinnedPaneKeys is filtered to only include keys that are already present in validRuntimePaneKeySet, the union of runtimePaneKeys and validTerminalPinnedPaneKeys is functionally identical to runtimePaneKeys.

You can simplify this memoized value to just return runtimePaneKeys (or remove pinnedPaneKeys entirely and pass runtimePaneKeys directly).

♻️ Proposed refactor
  const pinnedPaneKeys = useMemo(
-    () => Array.from(new Set([...runtimePaneKeys, ...validTerminalPinnedPaneKeys])),
-    [runtimePaneKeys, validTerminalPinnedPaneKeys]
+    () => runtimePaneKeys,
+    [runtimePaneKeys]
  )
🤖 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 277 -
280, Update the pinnedPaneKeys useMemo in DesktopWorkbenchMain to return
runtimePaneKeys directly, or remove the memo and pass runtimePaneKeys to its
consumers; eliminate the redundant Set-based union while preserving the existing
pinned-pane behavior.
🤖 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 `@executor/src/agents/codex.rs`:
- Around line 1305-1309: Update the shared request-user-input response handling
around request_user_input_answers and the logic at the referenced
response-processing section to dispatch each response by its requestId/itemId
correlation key rather than mutex or receiver acquisition order. Maintain
per-request routing so out-of-order answers reach the matching pending handler,
and add a regression test covering overlapping requests answered in reverse
order.

In `@executor/tests/app_runtime_work_send_contract.rs`:
- Line 1155: Replace the fixed 500 ms timing sleep in the notification-flood
test with an observable handshake: emit a final sentinel notification after the
flood loop, await receipt of that sentinel, then call runtime.tasks.send. Apply
the same synchronization change to the corresponding flow around the other
reported location, removing reliance on timing delays while preserving the
existing notification assertions.

In `@wework/e2e/desktop/task-flow.e2e.mjs`:
- Around line 522-524: Update releaseRequestUserInputResponse() to await or wrap
requestUserInputResponseWritten with the existing withTimeout helper, using the
established timeout and diagnostic failure path so handler failures cannot hang
indefinitely; apply the same change to the corresponding occurrence noted in the
comment.

In `@wework/src/api/runtime/runtimeChatStream.ts`:
- Around line 46-58: The runtime stream currently drops relevant
response.block.* events received before a pane subscription exists. In
wework/src/api/runtime/runtimeChatStream.ts:46-58, add a per-task pending
tool-event cache, store events with no matching subscription, replay cached
events when that task subscribes, and clear the cache on terminal responses. In
wework/src/api/runtime/runtimeChatStream.test.ts:203-210, add a regression test
that emits a request_user_input block before subscription and verifies the late
subscriber receives it.

In `@wework/src/lib/developerCommandMenu.ts`:
- Around line 269-279: Update the toggle handler around
isRuntimeChatStreamDebugEnabled and setRuntimeChatStreamDebugEnabled to capture
the frontend debug flag before changing it, then restore that saved value in the
catch block when requestLocalExecutor fails. Keep the executor success path and
logging behavior unchanged.

---

Nitpick comments:
In `@wework/src/components/layout/DesktopWorkbenchMain.tsx`:
- Around line 277-280: Update the pinnedPaneKeys useMemo in DesktopWorkbenchMain
to return runtimePaneKeys directly, or remove the memo and pass runtimePaneKeys
to its consumers; eliminate the redundant Set-based union while preserving the
existing pinned-pane behavior.
🪄 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: 89a78bc1-1801-4968-a844-59104ff98204

📥 Commits

Reviewing files that changed from the base of the PR and between 2f78e9c and 564b2c5.

📒 Files selected for processing (14)
  • executor/src/agents/codex.rs
  • executor/tests/app_runtime_work_send_contract.rs
  • wework/e2e/desktop/task-flow.e2e.mjs
  • wework/scripts/ai-verify-environment.mjs
  • wework/src/api/hybrid/hybridServices.ts
  • wework/src/api/local/localServices.ts
  • wework/src/api/runtime/runtimeChatStream.test.ts
  • wework/src/api/runtime/runtimeChatStream.ts
  • wework/src/components/layout/DesktopWorkbenchMain.tsx
  • wework/src/components/layout/MobileWorkbenchLayout.tsx
  • wework/src/components/layout/workbenchPaneStack.test.tsx
  • wework/src/components/layout/workbenchPaneStack.tsx
  • wework/src/lib/developerCommandMenu.test.ts
  • wework/src/lib/developerCommandMenu.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • wework/src/api/hybrid/hybridServices.ts

Comment thread executor/src/agents/codex.rs
Comment thread executor/tests/app_runtime_work_send_contract.rs Outdated
Comment thread wework/e2e/desktop/task-flow.e2e.mjs
Comment on lines +46 to +58
deps.subscribe(event => {
if (import.meta.env.DEV && event.event === 'runtime.plan.updated') {
console.warn('[Wework] Local runtime task plan event received', {
console.warn('[Wework] Runtime task plan event received', {
taskId: stringField(asRecord(event.payload), 'taskId') ?? null,
deviceId: stringField(asRecord(event.payload), 'deviceId') ?? null,
})
}
const subscriptionEntries = Array.from(subscriptions)
if (shouldLogLocalChatStreamEvent(event.event)) {
if (shouldLogRuntimeChatStreamEvent(event.event)) {
const matchedSubscriptionCount = subscriptionEntries.filter(([, subscription]) =>
isLocalExecutorEventInScope(event, subscription.handlers.scope)
).length
logLocalChatTerminalEvent(event, matchedSubscriptionCount, subscriptionEntries.length)
logRuntimeChatTerminalEvent(event, matchedSubscriptionCount, subscriptionEntries.length)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

The eager listener still drops events before pane subscription. The runtime stream needs a per-task pending tool-event cache and a regression test that proves late replay.

  • wework/src/api/runtime/runtimeChatStream.ts#L46-L58: cache relevant response.block.* events when no matching subscription exists, replay them during subscription, and clear them on terminal responses.
  • wework/src/api/runtime/runtimeChatStream.test.ts#L203-L210: emit a request_user_input block before pane subscription and assert that the late subscriber receives it.
📍 Affects 2 files
  • wework/src/api/runtime/runtimeChatStream.ts#L46-L58 (this comment)
  • wework/src/api/runtime/runtimeChatStream.test.ts#L203-L210
🤖 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/runtime/runtimeChatStream.ts` around lines 46 - 58, The
runtime stream currently drops relevant response.block.* events received before
a pane subscription exists. In
wework/src/api/runtime/runtimeChatStream.ts:46-58, add a per-task pending
tool-event cache, store events with no matching subscription, replay cached
events when that task subscribes, and clear the cache on terminal responses. In
wework/src/api/runtime/runtimeChatStream.test.ts:203-210, add a regression test
that emits a request_user_input block before subscription and verifies the late
subscriber receives it.

Comment thread wework/src/lib/developerCommandMenu.ts Outdated

@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 `@executor/src/agents/codex.rs`:
- Around line 190-247: Update InteractionAnswerRouterState with a closed flag,
set it when the background receiver loop exits before draining pending waiters,
and have InteractionAnswerRouter::receive reject new requests immediately when
closed instead of inserting them into pending. Preserve existing buffered-answer
and duplicate-key behavior.
🪄 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: 30c3f3cb-0921-4b82-aa3e-716bef157d1d

📥 Commits

Reviewing files that changed from the base of the PR and between d0c262f and 49175db.

📒 Files selected for processing (6)
  • executor/src/agents/codex.rs
  • executor/tests/app_runtime_work_send_contract.rs
  • wework/e2e/desktop/task-flow.e2e.mjs
  • wework/src/components/layout/DesktopWorkbenchMain.tsx
  • wework/src/lib/developerCommandMenu.test.ts
  • wework/src/lib/developerCommandMenu.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • wework/src/lib/developerCommandMenu.ts
  • wework/e2e/desktop/task-flow.e2e.mjs

Comment thread executor/src/agents/codex.rs
@qdaxb
qdaxb merged commit f287adc into main Jul 19, 2026
29 checks passed
@qdaxb
qdaxb deleted the fix/background-pane-tool-events branch July 19, 2026 03:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant