Repository navigation
refactor(wework): centralize runtime task lifecycle - #2211
Conversation
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughChangesThe PR centralizes runtime task execution, turn, goal, and unread state in Runtime lifecycle centralization
Estimated code review effort: 5 (Critical) | ~120 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
|
@coderabbitai review |
✅ Action performedReview finished.
|
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (4)
wework/e2e/desktop/task-flow.e2e.mjs (2)
1163-1176: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winCollapse into
waitForLogPatternwith an offset parameter.This is
waitForLogPattern(Line 1153) plus a slice offset; the two will drift. A default of0keeps every existing call site unchanged.As per coding guidelines, "Before adding code, search for and reuse existing components, services, utilities, and patterns; extract shared logic instead of duplicating it."
♻️ Single helper
-async function waitForLogPattern(logPath, pattern, timeoutMs = UI_TIMEOUT_MS) { +async function waitForLogPattern( + logPath, + pattern, + { fromOffset = 0, timeoutMs = UI_TIMEOUT_MS } = {} +) { const startedAt = Date.now() while (Date.now() - startedAt < timeoutMs) { const content = await readFile(logPath, 'utf8').catch(() => '') - if (pattern.test(content)) return content + if (pattern.test(content.slice(fromOffset))) return content await new Promise(resolvePromise => setTimeout(resolvePromise, 100)) } - throw new Error(`Timed out waiting for ${pattern} in ${logPath}`) + throw new Error(`Timed out waiting for ${pattern} in ${logPath} after offset ${fromOffset}`) } - -async function waitForNewLogPattern( - logPath, - pattern, - previousContentLength, - timeoutMs = UI_TIMEOUT_MS -) { - const startedAt = Date.now() - while (Date.now() - startedAt < timeoutMs) { - const content = await readFile(logPath, 'utf8').catch(() => '') - if (pattern.test(content.slice(previousContentLength))) return content - await new Promise(resolvePromise => setTimeout(resolvePromise, 100)) - } - throw new Error(`Timed out waiting for new ${pattern} in ${logPath}`) -}Then Line 1778 becomes:
- await waitForNewLogPattern(tauriLogPath, /windowWillClose:/, tauriLogLengthBeforeClose) + await waitForLogPattern(tauriLogPath, /windowWillClose:/, { + fromOffset: tauriLogLengthBeforeClose, + })🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@wework/e2e/desktop/task-flow.e2e.mjs` around lines 1163 - 1176, Remove the duplicate waitForNewLogPattern helper and extend waitForLogPattern with an optional offset parameter defaulting to 0. Apply the offset when testing newly read log content, preserving existing waitForLogPattern callers and updating the offset-based call site to use the shared helper.Source: Coding guidelines
2355-2380: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winCapture stage screenshots for this new lifecycle.
Every sibling verifier (
verifyBackgroundTaskWindowLifecycle,verifyAttachmentOnlySidebarLifecycle,verifyMemoryGrowth) writescaptureVerificationScreenshotevidence at each critical stage. This flow has four distinct states (running → continuing → background-unread-free → settled-unread) and none are captured, so a CI failure here leaves only the assertion message.As per coding guidelines, verification must produce "reproducible failure or critical-path evidence."
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@wework/e2e/desktop/task-flow.e2e.mjs` around lines 2355 - 2380, Update verifyActiveGoalIdleUnreadLifecycle to call captureVerificationScreenshot at each critical lifecycle stage: running, continuing, background-unread-free, and settled-unread. Use the existing sibling verifier screenshot conventions and include stage-specific identifiers or labels so CI failures preserve reproducible evidence for each state.Source: Coding guidelines
wework/src/features/workbench/workbenchReducer.test.ts (1)
1276-1312: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winConsider a reducer-level stale-refresh case too.
This covers the identity change well. The new
applyRuntimeTaskActivitypath (settle →runtime_work_refreshedreturns an olderrunning: truesnapshot → stillfalse, thenruntime_task_started→trueagain) is only exercised at the provider integration level; a direct reducer test would pin the contract cheaply.🤖 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/workbenchReducer.test.ts` around lines 1276 - 1312, Add a direct reducer test alongside the existing runtime task settlement test that applies runtime_task_settled, then runtime_work_refreshed with an older running:true snapshot and verifies running remains false, followed by runtime_task_started and verifies it becomes true. Use the existing workbenchReducer and runtime task state fixtures to pin this stale-refresh contract.wework/src/features/workbench/workbenchReducer.ts (1)
1249-1260: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win
pendingSettledRuntimeTasksonly ever shrinks on a matchingruntime_task_started.Every settled task accumulates for the session — including tasks later archived (
runtime_tasks_archived, Line 1233) or removed with their project (runtime_project_removed, Line 1076), which can never match a task again. SinceapplyRuntimeTaskActivityre-walks the whole work-list tree once per pending entry on everybootstrapped/lists_refreshed/runtime_work_refreshed, refresh cost grows monotonically with the number of settled tasks.Pruning entries that are no longer present in the refreshed work list keeps the settled-state guarantee (a stale snapshot still contains the task) while bounding the array.
♻️ Drop pending entries for tasks that are gone
function applyRuntimeTaskActivity( runtimeWork: RuntimeWorkListResponse | null, pendingSettledRuntimeTasks: RuntimeTaskAddress[] ): RuntimeWorkListResponse | null { return pendingSettledRuntimeTasks.reduce( (current, address) => updateRuntimeTaskRunning(current, address, false), runtimeWork ) } + +function retainPendingSettledRuntimeTasks( + runtimeWork: RuntimeWorkListResponse | null, + pendingSettledRuntimeTasks: RuntimeTaskAddress[] +): RuntimeTaskAddress[] { + if (!runtimeWork || pendingSettledRuntimeTasks.length === 0) return pendingSettledRuntimeTasks + const next = pendingSettledRuntimeTasks.filter(address => + Boolean(findRuntimeTask(runtimeWork, address)) + ) + return next.length === pendingSettledRuntimeTasks.length ? pendingSettledRuntimeTasks : next +}Then in each refresh case commit both, e.g. for
runtime_work_refreshed:return { ...state, runtimeWork, + pendingSettledRuntimeTasks: retainPendingSettledRuntimeTasks( + runtimeWork, + state.pendingSettledRuntimeTasks + ),🤖 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/workbenchReducer.ts` around lines 1249 - 1260, Prune pendingSettledRuntimeTasks during each work-list refresh case (bootstrapped, lists_refreshed, and runtime_work_refreshed) to retain only tasks still present in the refreshed work list. Apply the same filtered pending list both when calling applyRuntimeTaskActivity and when storing the refreshed reducer state, while preserving entries represented by the refreshed snapshot so settled-state handling remains intact.
🤖 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/MessageTurnNavigation.tsx`:
- Around line 560-564: The live-turn deduplication in MessageTurnNavigation.tsx
at lines 560-564 mixes runtime and local messageIndex values; update it to use
stable turn IDs or normalize live turns to runtimeMessageIndex before
comparison. In ScrollableMessageArea.test.tsx at lines 715-756, add coverage
with differing local and runtime indexes and assert that every live marker
remains visible.
In `@wework/src/features/workbench/WorkbenchProvider.test.tsx`:
- Around line 1816-1821: Update the listRuntimeWork mock in the
staleTerminalRefresh test setup to provide a default resolved
RuntimeWorkListResponse after the two
mockResolvedValueOnce/mockImplementationOnce behaviors. Preserve both existing
one-time responses and ensure any third call returns a valid response instead of
undefined.
In `@wework/src/features/workbench/WorkbenchProvider.tsx`:
- Around line 1195-1197: Update onRefreshWorkLists in WorkbenchProvider to
invoke the stable refresh callback reference and report any rejected
refreshWorkLists promise with the relevant device/task context instead of
swallowing it. Adjust the surrounding dependency array to use that stable
callback reference, while preserving the existing refresh behavior.
---
Nitpick comments:
In `@wework/e2e/desktop/task-flow.e2e.mjs`:
- Around line 1163-1176: Remove the duplicate waitForNewLogPattern helper and
extend waitForLogPattern with an optional offset parameter defaulting to 0.
Apply the offset when testing newly read log content, preserving existing
waitForLogPattern callers and updating the offset-based call site to use the
shared helper.
- Around line 2355-2380: Update verifyActiveGoalIdleUnreadLifecycle to call
captureVerificationScreenshot at each critical lifecycle stage: running,
continuing, background-unread-free, and settled-unread. Use the existing sibling
verifier screenshot conventions and include stage-specific identifiers or labels
so CI failures preserve reproducible evidence for each state.
In `@wework/src/features/workbench/workbenchReducer.test.ts`:
- Around line 1276-1312: Add a direct reducer test alongside the existing
runtime task settlement test that applies runtime_task_settled, then
runtime_work_refreshed with an older running:true snapshot and verifies running
remains false, followed by runtime_task_started and verifies it becomes true.
Use the existing workbenchReducer and runtime task state fixtures to pin this
stale-refresh contract.
In `@wework/src/features/workbench/workbenchReducer.ts`:
- Around line 1249-1260: Prune pendingSettledRuntimeTasks during each work-list
refresh case (bootstrapped, lists_refreshed, and runtime_work_refreshed) to
retain only tasks still present in the refreshed work list. Apply the same
filtered pending list both when calling applyRuntimeTaskActivity and when
storing the refreshed reducer state, while preserving entries represented by the
refreshed snapshot so settled-state handling remains intact.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 3cfc6884-f529-4959-8ac9-1fabed931f29
📒 Files selected for processing (15)
docs/en/wework/developer-guide/wework-chat-state-sources.mddocs/zh/wework/developer-guide/wework-chat-state-sources.mdexecutor/tests/app_runtime_work_contract.rsexecutor/tests/app_runtime_work_workspace_contract.rswework/e2e/desktop/task-flow.e2e.mjswework/src/components/chat/MessageTurnNavigation.tsxwework/src/components/chat/ScrollableMessageArea.test.tsxwework/src/features/workbench/WorkbenchProvider.test.tsxwework/src/features/workbench/WorkbenchProvider.tsxwework/src/features/workbench/runtimeTaskReminders.test.tswework/src/features/workbench/runtimeTaskReminders.tswework/src/features/workbench/useWorkbenchRuntimeMessaging.tswework/src/features/workbench/workbenchReducer.test.tswework/src/features/workbench/workbenchReducer.tswework/src/types/workbench.ts
bcbed34 to
857e929
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
wework/src/features/workbench/runtimeTaskStatus.ts (1)
1-6: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winConsider a dedicated unit test for this shared helper.
isRuntimeTaskRunningis now the shared running/active-goal contract consumed acrossruntimePaneStatus.ts,runtimeTaskCloseGuard.ts, and other files in this cohort. It's currently only exercised indirectly through each consumer's tests. A small dedicated test file covering therunning/goalStatuscombinations would pin down this contract directly.🤖 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/runtimeTaskStatus.ts` around lines 1 - 6, Add a dedicated unit test for isRuntimeTaskRunning in runtimeTaskStatus.ts, covering true and false running values combined with active and non-active goalStatus values. Assert that the helper returns true when either condition indicates activity and false only when both indicate inactivity.
🤖 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/DesktopWorkbenchMain.tsx`:
- Around line 1192-1193: Update the active-task pause flow around
paneIsResponseStreaming so taskExecution.running is routed through ChatInput and
its embedded composer via the existing isStreaming/onPause handling, rather than
relying only on GoalStatusBar. Preserve response-streaming behavior and ensure a
visible pause control remains available when task execution is active without an
active response.
---
Nitpick comments:
In `@wework/src/features/workbench/runtimeTaskStatus.ts`:
- Around line 1-6: Add a dedicated unit test for isRuntimeTaskRunning in
runtimeTaskStatus.ts, covering true and false running values combined with
active and non-active goalStatus values. Assert that the helper returns true
when either condition indicates activity and false only when both indicate
inactivity.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: b00a1181-7327-439c-9944-57ca05bd9065
📒 Files selected for processing (29)
docs/en/wework/developer-guide/wework-chat-state-sources.mddocs/zh/wework/developer-guide/wework-chat-state-sources.mdexecutor/tests/app_runtime_work_contract.rsexecutor/tests/app_runtime_work_workspace_contract.rswework/e2e/desktop/task-flow.e2e.mjswework/src/components/chat/MessageList.test.tsxwework/src/components/chat/MessageList.tsxwework/src/components/chat/MessageTurnNavigation.tsxwework/src/components/chat/ScrollableMessageArea.test.tsxwework/src/components/layout/DesktopSidebar.tsxwework/src/components/layout/DesktopWorkbenchLayout.test.tsxwework/src/components/layout/DesktopWorkbenchMain.tsxwework/src/components/layout/MobileWorkbenchLayout.test.tsxwework/src/components/layout/MobileWorkbenchLayout.tsxwework/src/features/workbench/WorkbenchProvider.test.tsxwework/src/features/workbench/WorkbenchProvider.tsxwework/src/features/workbench/runtimePaneStatus.test.tswework/src/features/workbench/runtimePaneStatus.tswework/src/features/workbench/runtimeTaskReminders.test.tswework/src/features/workbench/runtimeTaskReminders.tswework/src/features/workbench/runtimeTaskStatus.tswework/src/features/workbench/workbenchReducer.test.tswework/src/features/workbench/workbenchReducer.tswework/src/lib/developerCommandMenu.test.tswework/src/tauri/runtimeTaskCloseGuard.test.tswework/src/tauri/runtimeTaskCloseGuard.tswework/src/tauri/trayMenuState.test.tswework/src/tauri/trayMenuState.tswework/src/types/workbench.ts
💤 Files with no reviewable changes (1)
- wework/src/components/chat/MessageList.tsx
🚧 Files skipped from review as they are similar to previous changes (10)
- docs/zh/wework/developer-guide/wework-chat-state-sources.md
- docs/en/wework/developer-guide/wework-chat-state-sources.md
- wework/src/components/chat/ScrollableMessageArea.test.tsx
- wework/src/features/workbench/runtimeTaskReminders.test.ts
- wework/src/features/workbench/WorkbenchProvider.tsx
- wework/src/components/chat/MessageTurnNavigation.tsx
- executor/tests/app_runtime_work_contract.rs
- wework/src/features/workbench/WorkbenchProvider.test.tsx
- wework/src/features/workbench/workbenchReducer.ts
- wework/e2e/desktop/task-flow.e2e.mjs
…sk-status # Conflicts: # wework/e2e/desktop/task-flow.e2e.mjs # wework/src/components/chat/MessageTurnNavigation.tsx # wework/src/components/chat/ScrollableMessageArea.test.tsx # wework/src/features/workbench/workbenchReducer.ts
…sk-status # Conflicts: # wework/e2e/desktop/task-flow.e2e.mjs
There was a problem hiding this comment.
Actionable comments posted: 10
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
wework/src/features/workbench/RuntimeTaskCloseGuard.tsx (1)
17-43: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winKeep the close listener installed across lifecycle changes.
Reinstalling this async listener on every count change creates a cleanup-to-subscribe gap. A close event in that window is lost, so the app can close while tasks are running. Install once and read the current running count through a ref or stable event callback.
🤖 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/RuntimeTaskCloseGuard.tsx` around lines 17 - 43, The useEffect around installRuntimeTaskCloseGuard must install the close listener only once rather than rerunning when lifecycle.runningTaskKeys.size changes. Store the current running-task count in a ref or use a stable callback so the listener always reads fresh state, while preserving cancellation, cleanup, and dialog behavior.wework/src/features/workbench/useWorkbenchRuntimeMessaging.ts (1)
1301-1313: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winMirror lifecycle store updates in
pauseCurrentResponse.
pauseCurrentResponsecancelsstate.currentRuntimeTask, but unlikecancelRuntimePaneTask, it never callslifecycleStore.stopRequested/stopRejected. Pause actions wired here can leave the sidebar/composer/tray with a stale running/stopping state until reconciliation.🐛 Proposed fix: mirror cancelRuntimePaneTask's lifecycle wiring
const pauseCurrentResponse = useCallback(async () => { if (!state.currentRuntimeTask) return + lifecycleStore.stopRequested(state.currentRuntimeTask) const ack = await executorClient.runtime.cancelRuntimeTask(state.currentRuntimeTask) if (!ack.accepted) { + lifecycleStore.stopRejected(state.currentRuntimeTask) dispatch({ type: 'error_set', error: normalizeGuidanceError(ack.error ?? '取消当前回复失败'), }) return } await refreshWorkLists() -}, [dispatch, executorClient, refreshWorkLists, state.currentRuntimeTask]) +}, [dispatch, executorClient, lifecycleStore, refreshWorkLists, state.currentRuntimeTask])🤖 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/useWorkbenchRuntimeMessaging.ts` around lines 1301 - 1313, Update pauseCurrentResponse to mirror cancelRuntimePaneTask’s lifecycle handling: call lifecycleStore.stopRequested when cancellation begins and lifecycleStore.stopRejected when the acknowledgement is not accepted, using the same task or runtime identifiers and error context as the existing cancel flow. Preserve the current error dispatch, early returns, and refreshWorkLists behavior.
🧹 Nitpick comments (3)
wework/src/components/layout/MobileWorkbenchLayout.test.tsx (1)
208-215: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winHarness forces every open task into a running state.
lifecycleTaskRunning = Boolean(state.currentRuntimeTask)plus the unconditionalstore.executorStarted(...)means any test that opens a task renders withisBusy/runningtrue, so idle-task composer/message assertions can no longer be expressed. Consider deriving running from the task'srunningflag instate.runtimeWork(or an explicit prop) so both states remain testable. Also note the store mutation insideuseMemois impure; auseStateinitializer oruseEffectwould be safer if React discards the memo.Also applies to: 259-259
🤖 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/MobileWorkbenchLayout.test.tsx` around lines 208 - 215, Update the MobileWorkbenchLayout test harness around lifecycleStore and lifecycleTaskRunning so an open task is marked running only when its corresponding state.runtimeWork entry has running=true, preserving idle-task scenarios. Remove the unconditional executorStarted call based solely on currentRuntimeTask, and avoid mutating the store inside useMemo by initializing it through useState or synchronizing it in an appropriate effect.wework/src/features/workbench/runtimeTaskLifecycle/RuntimeTaskLifecycleStore.ts (1)
201-209: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick winCache the persisted unread set instead of re-reading
localStorageper machine.
ensureMachineperforms a synchronouslocalStorage.getItem+JSON.parsefor every task it creates, so the firstsyncRuntimeWorkover a large work list does N reads on the layout-effect path inWorkbenchProvider. Read it once and keep it in a field (the store already owns writes viapersistUnreadKeys).♻️ Proposed fix
- const machine = new RuntimeTaskMachine(address, this.readUnreadKeys().has(key)) + this.persistedUnreadKeys ??= this.readUnreadKeys() + const machine = new RuntimeTaskMachine(address, this.persistedUnreadKeys.has(key))🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@wework/src/features/workbench/runtimeTaskLifecycle/RuntimeTaskLifecycleStore.ts` around lines 201 - 209, Cache the persisted unread-key set on RuntimeTaskLifecycleStore and initialize it once from localStorage, then have ensureMachine reuse that field instead of calling readUnreadKeys for each machine. Update persistUnreadKeys to keep the cache synchronized whenever writes occur, preserving existing unread-key behavior.wework/src/features/workbench/runtimeTaskLifecycle/RuntimeTaskLifecycleStore.test.ts (1)
182-200: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd coverage for unread rehydration from
localStorage.The store persists unread keys per user and seeds new machines from them (
ensureMachine), but no test constructs a second store with the sameuserIdto assert the unread state survives a reload. That path is easy to regress silently.🤖 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/runtimeTaskLifecycle/RuntimeTaskLifecycleStore.test.ts` around lines 182 - 200, Add a test covering unread-state rehydration by creating one RuntimeTaskLifecycleStore for a user, producing and persisting an unread task state, then creating a second store with the same userId and asserting the task’s derived.shouldShowUnread remains true. Exercise the ensureMachine initialization path and preserve the existing behavior that opening the task clears unread state.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/en/wework/developer-guide/wework-chat-state-sources.md`:
- Line 24: Update the “Current runtime running snapshot” row in the developer
guide to replace the removed getRuntimePaneTaskExecution and
currentRuntimeTaskRunning references with the runtime task lifecycle snapshot,
specifically deriveRuntimePaneStatus({ lifecycle }) from runtimePaneStatus.ts.
Preserve the documented running semantics and update the source/consumer columns
to describe the lifecycle store as authoritative.
In `@docs/zh/wework/developer-guide/wework-chat-state-sources.md`:
- Line 24: 更新“当前 runtime 运行快照”表格行,改为记录 pane status consumers 使用的
RuntimeTaskLifecycleStoreSnapshot,并将 lifecycle state 标明为前端/UI 的权威来源;保留 executor
的 running 作为外部输入单独说明,移除过时的 getRuntimePaneTaskExecution 和
currentRuntimeTaskRunning 引用。
In `@wework/src/components/layout/DesktopWorkbenchLayout.test.tsx`:
- Around line 835-846: Derive the effective lifecycle-running value once, using
the explicit props.lifecycleTaskRunning value with the existing
currentRuntimeTask fallback, and reuse it in both the lifecycleStore
initialization and the provider’s lifecycleTaskRunning value. Update the
lifecycleStore useMemo dependencies to track this derived value and current
runtime state, ensuring executorStarted and the rendered status stay consistent.
In `@wework/src/components/layout/runtimeTaskSidebarHelpers.ts`:
- Around line 16-18: Update getRuntimeTaskSortTime to return the latest
available timestamp among completedAt, updatedAt, and createdAt rather than
prioritizing completedAt; preserve undefined when no timestamps exist and avoid
adding any running-state dependency.
In `@wework/src/components/layout/useWorkbenchPaneSession.ts`:
- Around line 1607-1617: In the currentRuntimeTask branch of the submission
flow, update the !response.accepted failure path after setRuntimeGoal to restore
the cleared input using submittedInput before setting the error and returning,
matching the sibling failure path’s behavior.
In `@wework/src/features/workbench/runtimeTaskLifecycle/reducer.ts`:
- Around line 8-39: Update the goalStatus assignment in the
executor_snapshot_received reducer case to preserve state.goalStatus when
event.task.goalStatus is undefined, while still applying explicit values
including null when provided. Keep the existing snapshot handling and other
state updates unchanged.
- Around line 41-64: Update the send_rejected branch in the runtime task
lifecycle reducer so it only transitions to idle when no executor confirmation
has been observed; otherwise preserve the current active executionPhase,
turnPhase, and expectedExecutorRunning state. Use the existing
executor-confirmation state/symbols and keep send_requested and send_accepted
behavior unchanged.
In
`@wework/src/features/workbench/runtimeTaskLifecycle/RuntimeTaskLifecycleStore.ts`:
- Around line 146-176: Update RuntimeTaskLifecycleStore.rename to migrate
previousState.goalStatus to nextMachine alongside the existing task, execution
phase, turn phase, and unread state; use the machine’s established goal-status
event or transition so reduceMachine preserves the same goal status after
renaming.
- Around line 242-245: Update persistUnreadKeys to skip writing when the
incoming key set matches the currently persisted snapshot, and wrap
localStorage.setItem in defensive error handling so quota or private-mode
failures cannot propagate into publish() or prevent listener notification.
Preserve the existing 200-key serialization and SSR guard.
- Around line 112-131: Update syncTranscript so historical settled assistant
messages cannot trigger turnSettled or dispatch turn_settled when
transcript.running is unknown. Restrict settlement to transcript.running ===
false, or ensure any settled assistant check is scoped to the current turn while
preserving streaming recovery behavior.
---
Outside diff comments:
In `@wework/src/features/workbench/RuntimeTaskCloseGuard.tsx`:
- Around line 17-43: The useEffect around installRuntimeTaskCloseGuard must
install the close listener only once rather than rerunning when
lifecycle.runningTaskKeys.size changes. Store the current running-task count in
a ref or use a stable callback so the listener always reads fresh state, while
preserving cancellation, cleanup, and dialog behavior.
In `@wework/src/features/workbench/useWorkbenchRuntimeMessaging.ts`:
- Around line 1301-1313: Update pauseCurrentResponse to mirror
cancelRuntimePaneTask’s lifecycle handling: call lifecycleStore.stopRequested
when cancellation begins and lifecycleStore.stopRejected when the
acknowledgement is not accepted, using the same task or runtime identifiers and
error context as the existing cancel flow. Preserve the current error dispatch,
early returns, and refreshWorkLists behavior.
---
Nitpick comments:
In `@wework/src/components/layout/MobileWorkbenchLayout.test.tsx`:
- Around line 208-215: Update the MobileWorkbenchLayout test harness around
lifecycleStore and lifecycleTaskRunning so an open task is marked running only
when its corresponding state.runtimeWork entry has running=true, preserving
idle-task scenarios. Remove the unconditional executorStarted call based solely
on currentRuntimeTask, and avoid mutating the store inside useMemo by
initializing it through useState or synchronizing it in an appropriate effect.
In
`@wework/src/features/workbench/runtimeTaskLifecycle/RuntimeTaskLifecycleStore.test.ts`:
- Around line 182-200: Add a test covering unread-state rehydration by creating
one RuntimeTaskLifecycleStore for a user, producing and persisting an unread
task state, then creating a second store with the same userId and asserting the
task’s derived.shouldShowUnread remains true. Exercise the ensureMachine
initialization path and preserve the existing behavior that opening the task
clears unread state.
In
`@wework/src/features/workbench/runtimeTaskLifecycle/RuntimeTaskLifecycleStore.ts`:
- Around line 201-209: Cache the persisted unread-key set on
RuntimeTaskLifecycleStore and initialize it once from localStorage, then have
ensureMachine reuse that field instead of calling readUnreadKeys for each
machine. Update persistUnreadKeys to keep the cache synchronized whenever writes
occur, preserving existing unread-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 Plus
Run ID: 992bcc01-2a27-49da-a55e-714fc45ff877
📒 Files selected for processing (55)
docs/en/wegent/developer-guide/local-device-architecture.mddocs/en/wework/developer-guide/wework-chat-state-sources.mddocs/zh/wegent/developer-guide/local-device-architecture.mddocs/zh/wework/developer-guide/wework-chat-state-sources.mdexecutor/src/agents/codex.rsexecutor/src/agents/codex/run_state.rsexecutor/src/runtime_work/handler/tests.rsexecutor/src/runtime_work/handler/turns.rsexecutor/src/runtime_work/response.rswework/e2e/desktop/process-lifecycle.mjswework/e2e/desktop/task-flow.e2e.mjswework/package.jsonwework/scripts/check-runtime-task-lifecycle.mjswework/src/App.plugins.test.tsxwework/src/components/chat/MessageList.test.tsxwework/src/components/chat/MessageList.tsxwework/src/components/layout/DesktopSidebar.test.tsxwework/src/components/layout/DesktopSidebar.tsxwework/src/components/layout/DesktopWorkbenchLayout.test.tsxwework/src/components/layout/DesktopWorkbenchMain.tsxwework/src/components/layout/MobileDrawer.tsxwework/src/components/layout/MobileWorkbenchLayout.test.tsxwework/src/components/layout/MobileWorkbenchLayout.tsxwework/src/components/layout/runtimeTaskSidebarHelpers.tswework/src/components/layout/useWorkbenchPaneSession.tswework/src/e2e/desktop-process-lifecycle.test.tswework/src/features/workbench/RuntimeTaskCloseGuard.tsxwework/src/features/workbench/WorkbenchProvider.test.tsxwework/src/features/workbench/WorkbenchProvider.tsxwework/src/features/workbench/runtimePaneStatus.test.tswework/src/features/workbench/runtimePaneStatus.tswework/src/features/workbench/runtimeTaskLifecycle/RuntimeTaskLifecycleProvider.tsxwework/src/features/workbench/runtimeTaskLifecycle/RuntimeTaskLifecycleStore.test.tswework/src/features/workbench/runtimeTaskLifecycle/RuntimeTaskLifecycleStore.tswework/src/features/workbench/runtimeTaskLifecycle/RuntimeTaskMachine.tswework/src/features/workbench/runtimeTaskLifecycle/context.tswework/src/features/workbench/runtimeTaskLifecycle/index.tswework/src/features/workbench/runtimeTaskLifecycle/internalContext.tswework/src/features/workbench/runtimeTaskLifecycle/reducer.tswework/src/features/workbench/runtimeTaskLifecycle/types.tswework/src/features/workbench/runtimeTaskReminders.test.tswework/src/features/workbench/runtimeTaskReminders.tswework/src/features/workbench/useWorkbenchRuntimeMessaging.tswework/src/features/workbench/useWorkbenchRuntimeTasks.tswework/src/features/workbench/workbenchContextTypes.tswework/src/features/workbench/workbenchReducer.test.tswework/src/features/workbench/workbenchReducer.tswework/src/lib/debugPanel.tswework/src/lib/developerCommandMenu.test.tswework/src/pages/WorkbenchPage.tsxwework/src/tauri/runtimeTaskCloseGuard.test.tswework/src/tauri/runtimeTaskCloseGuard.tswework/src/tauri/trayMenuState.test.tswework/src/tauri/trayMenuState.tswework/src/types/workbench.ts
💤 Files with no reviewable changes (2)
- wework/src/features/workbench/workbenchReducer.test.ts
- wework/src/features/workbench/runtimeTaskReminders.test.ts
🚧 Files skipped from review as they are similar to previous changes (4)
- wework/src/components/chat/MessageList.test.tsx
- wework/src/components/chat/MessageList.tsx
- wework/src/types/workbench.ts
- wework/src/features/workbench/workbenchReducer.ts
What
RuntimeTaskLifecycleStorethat owns all task lifecycle machines and routes executor, stream, Goal, optimistic-send, stop, read, rename, and archive eventsRuntimeTaskMachinethe aggregate root for execution, turn, Goal, and unread state; keep the reducer private to the machine implementationRuntimeTaskLifecycleProvideras a strict React subscription adapter with no fallback Store or local execution cachesendPhase, reminder-owned ongoing set, and legacy runtime status helperWhy
Runtime state previously existed in several frontend layers. The task list, pane, optimistic send path, stream callbacks, reminders, and tray could each retain or infer a slightly different answer. That made active-Goal gaps, stale snapshots, background completion, and renderer/executor restart disagree in user-visible ways.
The final authority chain is:
Execution state is memory-only. Optimistic
startingis maintained by the same machine and converges on explicit executor state. Only unread completion reminders persist; they never reconstruct execution.User-visible contract
running=falsefrom the new executor process means idle; it does not auto-resume. Explicit user input starts a new run.Cleanup evidence
pnpm --dir wework lint:task-lifecycletask/runtimeTask/summary/transcript.runninginterpretation outside the lifecycle aggregate and transport ingestionreducer.tsandRuntimeTaskMachine.tsruntimeTaskStatus.tsValidation
pnpm --dir wework lintpnpm --dir wework typecheckpnpm --dir wework test— 215 files / 2127 tests passednode wework/e2e/desktop/task-flow.e2e.mjs --goal-idle-onlynode wework/e2e/desktop/task-flow.e2e.mjs --goal-restart-onlycargo test --lockedinexecutor— complete suite passedThe two desktop E2E journeys use real backend/runtime requests and verify the sidebar, composer, message thinking state, Goal state, unread state, background completion, full Wework/executor restart, non-automatic recovery, and explicit user resume together.
Summary by CodeRabbit