Skip to content

fix(wework): keep Codex goal state authoritative - #1886

Merged
qdaxb merged 22 commits into
mainfrom
fix/wework-goal-continuation-status
Jul 11, 2026
Merged

qdaxb merged 22 commits into
mainfrom
fix/wework-goal-continuation-status

Conversation

@qdaxb

@qdaxb qdaxb commented Jul 10, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Keep Wework goal and in-turn guidance state aligned with Codex, including automatic continuation between turns.

Root causes

  1. Codex completes a turn before its goal extension starts the next idle continuation turn. Wework previously interpreted the short running: false interval as a user-paused goal.
  2. The executor refreshed the active Codex turn id only from turn/started. During automatic continuation, item notifications for the new root turn could arrive while that boundary was unavailable; a template guidance request then sent turn/steer with the old expectedTurnId. Codex correctly rejected it.

State-flow coverage

Flow Result
Open a historical task with a goal Load thread/goal/get; revision ordering prevents a delayed response from overwriting a stream event.
Automatic continuation between Codex turns Keep the Codex active goal authoritative; surface root lifecycle state without inferring pause from task-list idle.
Template guidance during continuation Refresh the active id from every root notification carrying turnId; clear it at completion so guidance waits for the next real turn.
Goal pauses or becomes blocked/limited Render the Codex status; only active and paused expose pause/resume.
Send a goal without an existing task Send the initial goal while creating the runtime task and retain the optimistic goal until the authoritative response arrives.
Send a new goal on an existing task Set the goal, then send the instruction; retain the text and show an error if instruction delivery fails.
Edit and send an existing goal Update objective and explicitly set status: active, resuming a paused goal.
Stop a task while a goal is active Cancel the runtime task, then explicitly set the goal to paused.

Validation

  • cargo fmt --check
  • cargo test active_root_turn_notification_uses_item_turn_id_and_ignores_completed_or_child_turns
  • cargo test codex_run_state_ignores_cross_thread_turn_completion --lib
  • pnpm exec vitest run src/features/workbench/WorkbenchProvider.test.tsx (89 tests)
  • pnpm typecheck
  • Local pre-push quality gate
  • GitHub CI runs on this PR.

Summary by CodeRabbit

  • New Features

    • Added “Goal continuing” UI for active goals across turns, with correct pause/resume controls.
    • Introduced runtime goal continuation events/stream handling to keep lifecycle updates turn-linked.
  • Bug Fixes

    • Improved active-turn tracking to ensure continuation reflects the active root turn.
    • Updated cancellation flow to detach/interrupt shared turns and return the cancelled result immediately.
  • Documentation

    • Added English + Chinese translations for the continuing status.
    • Updated desktop automation/E2E documentation and test expectations.
  • Tests

    • Added unit/UI and stream-mapping coverage for goal continuation and toggle behavior.

@coderabbitai

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

Adds root Codex turn completion callbacks and runtime goal continuation events, propagates continuation state through workbench streams and session state, updates goal controls, and changes desktop E2E polling, editor filling, compaction handling, cancellation assertions, and diagnostics.

Changes

Runtime goal continuation

Layer / File(s) Summary
Codex active turn completion
executor/src/agents/codex.rs, executor/src/agents/mod.rs, executor/src/runtime_work/handler.rs
Adds active-root turn detection, detached cancellation interruption, completion callbacks, callback wiring, and active-turn cleanup.
Goal lifecycle event emission
executor/src/runtime_work/codex_notifications.rs, executor/src/runtime_work/events.rs
Tracks active goal status and emits root-thread continuation events for started and settled turns; goal updates now include turn_id.
Continuation event transport
wework/src/types/api.ts, wework/src/stream/*, wework/src/features/workbench/runtimePaneMessages.ts
Defines continuation payloads and forwards continuation events through response and runtime task streams.
Session goal state synchronization
wework/src/components/layout/useWorkbenchPaneSession.ts, wework/src/features/workbench/runtimePaneStatus.ts
Adds revision-guarded goal commits, continuation tracking, goal reset handling, and derived goalContinuing state while removing inactive-task auto-pausing.
Goal composer UI and validation
wework/src/components/chat/*, wework/src/components/layout/DesktopWorkbenchMain.tsx, wework/src/i18n/locales/*, wework/src/features/workbench/WorkbenchProvider.test.tsx
Displays continuing status, conditionally renders pause/resume controls, wires continuation state into both composer paths, adds translations, and tests goal transitions.

Desktop E2E automation

Layer / File(s) Summary
Desktop control protocol
wework/src/e2e/automation.ts, wework/e2e/desktop/task-flow.e2e.mjs
Uses short polling with 204 idle responses, records delivered commands, and fills editor values through the native setter before fallback insertion.
Compaction handling and diagnostics
wework/e2e/desktop/task-flow.e2e.mjs, docs/en/developer-guide/wework-e2e-automation.md, docs/zh/developer-guide/wework-e2e-automation.md
Detects Codex compaction requests, emits a minimal SSE response, updates cancellation assertions, documents the control behavior, and includes command history in failure diagnostics.

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

Sequence Diagram(s)

sequenceDiagram
  participant CodexAppServer
  participant CodexNotificationEventMapper
  participant ResponseApiStream
  participant WorkbenchPaneSession
  participant GoalStatusBar
  CodexAppServer->>CodexNotificationEventMapper: send root turn lifecycle notification
  CodexNotificationEventMapper->>ResponseApiStream: emit runtime.goal.continuation
  ResponseApiStream->>WorkbenchPaneSession: dispatch continuation payload
  WorkbenchPaneSession->>WorkbenchPaneSession: derive goalContinuing
  WorkbenchPaneSession->>GoalStatusBar: pass continuing state
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: keeping Wework goal state aligned with Codex as the authoritative source.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/wework-goal-continuation-status

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 changed the title fix(wework): preserve active goal during continuation fix(wework): keep Codex goal state authoritative Jul 11, 2026
@qdaxb
qdaxb marked this pull request as ready for review July 11, 2026 00:53

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

Caution

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

⚠️ Outside diff range comments (3)
wework/src/components/layout/useWorkbenchPaneSession.ts (2)

414-442: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Goal-load failure handler can clobber a newer, valid goal.

The .then() branch correctly guards against stale responses with requestedGoalRevision === goalRevisionRef.current (line 420), but the .catch() branch commits null unconditionally once !cancelled. If this effect's getRuntimeGoal call rejects after a different, more recent commitThreadGoal already landed (e.g. from the independent onAssistantSettled refresh at lines 553-579, which runs concurrently against the same task), this catch will incorrectly wipe out the fresher goal state.

🔒 Proposed fix to guard the catch branch with the same revision check
       .catch(error => {
         if (!cancelled) {
-          commitThreadGoal(null)
+          if (requestedGoalRevision === goalRevisionRef.current) {
+            commitThreadGoal(null)
+          }
           console.error('[Wework] Runtime goal load failed', {
             address: runtimeAddressDebug(runtimeTaskLoadTarget.address),
             error,
           })
         }
       })
🤖 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/useWorkbenchPaneSession.ts` around lines 414 -
442, Guard the getRuntimeGoal rejection handler in the effect using the same
requestedGoalRevision === goalRevisionRef.current check as the success branch
before calling commitThreadGoal(null) or logging the failure. Update the
.catch() condition so stale failures cannot overwrite a newer goal loaded by
concurrent refreshes such as onAssistantSettled.

118-118: 📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift

File exceeds the 1000-line guideline threshold.

This hook file runs well past 1000 lines (shown ranges extend to ~1836), and this PR adds further state (goalContinuation, goalRevisionRef, commitThreadGoal) and effect logic to it. Consider extracting goal-state management (threadGoal/goalContinuation/pendingGoalState/commitThreadGoal and their effects) into a dedicated hook.

As per coding guidelines, "Split files that exceed 1000 lines and preferably keep functions at 50 lines or fewer."

🤖 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/useWorkbenchPaneSession.ts` at line 118, Extract
the goal-state management from useWorkbenchPaneSession into a dedicated hook,
including threadGoal, goalContinuation, pendingGoalState, goalRevisionRef,
commitThreadGoal, and their related effects. Keep useWorkbenchPaneSession
focused on composing the returned state and actions, and update it to consume
the new hook without changing behavior.

Source: Coding guidelines

executor/src/agents/codex.rs (1)

1264-1276: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Avoid re-sending turn/interrupt for the same turn. Once cancel_requested is set, every later root notification with a turn id can trigger another interrupt_shared_turn until the turn ends. Since active_root_turn_notification_id now matches more than just turn/started, this can spam duplicate interrupt RPCs during cancellation. Gate the interrupt on a turn-id change or clear the flag after the first send.

🤖 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/src/agents/codex.rs` around lines 1264 - 1276, The cancellation
branch in the root-notification handling can repeatedly call
interrupt_shared_turn for the same turn. Track whether the active turn ID
changed before updating options.active_turn_id, and only send the interrupt when
cancel_requested is true and the notification introduces a new turn ID;
alternatively clear the cancellation flag immediately after the first successful
interrupt.
🧹 Nitpick comments (1)
wework/src/components/layout/useWorkbenchPaneSession.ts (1)

1641-1679: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add commitThreadGoal to both dependency arrays. It’s referenced in updateCurrentGoalStatus and clearCurrentGoal, and including it keeps the hook deps aligned with the rest of this file.

🤖 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/useWorkbenchPaneSession.ts` around lines 1641 -
1679, Include commitThreadGoal in the dependency arrays for both
updateCurrentGoalStatus and clearCurrentGoal, alongside their existing
dependencies, so each callback accurately declares the values it references.
🤖 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/runtime_work/events.rs`:
- Line 103: Initialize CodexNotificationEventMapper’s goal_status from the
resumed thread’s current goal state during spawn_turn, rather than always
starting at None. Ensure has_active_goal() reflects an already-active goal
before live thread/goal/updated notifications arrive, allowing
runtime.goal.continuation to emit for resumed turns.

In `@wework/src/components/chat/ChatInput.test.tsx`:
- Around line 2195-2222: Extend the status matrix in the “does not offer the
pause action for a %s goal” test to include the complete status alongside
blocked, usageLimited, and budgetLimited, preserving the existing isolated
parameterized test coverage for all non-toggleable goal states.
- Around line 2169-2193: The ChatInput goal-status tests do not cover an active
goal when goalContinuing is false. Add a separate test near the existing “shows
an active goal as continuing” case using the same active goal fixture, arrange
goalContinuing={false}, render ChatInput, and assert the normal active label is
shown rather than “目标继续执行中”; follow Arrange, Act, Assert structure.

In `@wework/src/components/layout/useWorkbenchPaneSession.ts`:
- Around line 1272-1277: Clear the stale goalContinuation state alongside every
client-initiated goal mutation. In the goal creation flow near
commitThreadGoal(response.goal), and in the pause/resume updateCurrentGoalStatus
flow and manual clear flow, reset goalContinuation immediately after the
successful commit so newly created, resumed, or cleared goals cannot inherit a
prior started continuation state.

In `@wework/src/features/workbench/WorkbenchProvider.test.tsx`:
- Around line 5767-5837: Extend the test “resumes a paused runtime goal when
editing and sending its objective” to retain a reference to the mocked
sendRuntimeMessage function and assert it is called after the edit flow with the
edited objective and the runtime task address. Keep the existing setRuntimeGoal
assertion and place the sendRuntimeMessage expectation after the user clicks
“send runtime goal.”
- Around line 5714-5765: Update the test “keeps an active runtime goal active
while the task list is between automatic turns” to drive an actual
automatic-turn boundary rather than only loading a static idle response. Make
the listRuntimeWork fixture transition through running to idle, or trigger the
relevant continuation event, and assert that runtime-goal-status remains active
after the transition; keep the mock and assertions isolated to this test.

---

Outside diff comments:
In `@executor/src/agents/codex.rs`:
- Around line 1264-1276: The cancellation branch in the root-notification
handling can repeatedly call interrupt_shared_turn for the same turn. Track
whether the active turn ID changed before updating options.active_turn_id, and
only send the interrupt when cancel_requested is true and the notification
introduces a new turn ID; alternatively clear the cancellation flag immediately
after the first successful interrupt.

In `@wework/src/components/layout/useWorkbenchPaneSession.ts`:
- Around line 414-442: Guard the getRuntimeGoal rejection handler in the effect
using the same requestedGoalRevision === goalRevisionRef.current check as the
success branch before calling commitThreadGoal(null) or logging the failure.
Update the .catch() condition so stale failures cannot overwrite a newer goal
loaded by concurrent refreshes such as onAssistantSettled.
- Line 118: Extract the goal-state management from useWorkbenchPaneSession into
a dedicated hook, including threadGoal, goalContinuation, pendingGoalState,
goalRevisionRef, commitThreadGoal, and their related effects. Keep
useWorkbenchPaneSession focused on composing the returned state and actions, and
update it to consume the new hook without changing behavior.

---

Nitpick comments:
In `@wework/src/components/layout/useWorkbenchPaneSession.ts`:
- Around line 1641-1679: Include commitThreadGoal in the dependency arrays for
both updateCurrentGoalStatus and clearCurrentGoal, alongside their existing
dependencies, so each callback accurately declares the values it references.
🪄 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: 5badbdb6-e4d7-4714-a8ab-283a1755670b

📥 Commits

Reviewing files that changed from the base of the PR and between 95a1609 and f34d509.

📒 Files selected for processing (20)
  • executor/src/agents/codex.rs
  • executor/src/agents/mod.rs
  • executor/src/runtime_work/codex_notifications.rs
  • executor/src/runtime_work/events.rs
  • executor/src/runtime_work/handler.rs
  • wework/src/components/chat/ChatInput.test.tsx
  • wework/src/components/chat/ChatInput.tsx
  • wework/src/components/chat/composer/GoalStatusBar.tsx
  • wework/src/components/layout/DesktopWorkbenchMain.tsx
  • wework/src/components/layout/useWorkbenchPaneSession.ts
  • wework/src/features/workbench/WorkbenchProvider.test.tsx
  • wework/src/features/workbench/runtimePaneMessages.ts
  • wework/src/features/workbench/runtimePaneStatus.test.ts
  • wework/src/features/workbench/runtimePaneStatus.ts
  • wework/src/i18n/locales/en/common.json
  • wework/src/i18n/locales/zh-CN/common.json
  • wework/src/stream/chatStream.ts
  • wework/src/stream/responseApiStream.test.ts
  • wework/src/stream/responseApiStream.ts
  • wework/src/types/api.ts

plan_blocks: BTreeMap<String, String>,
tool_output_deltas: BTreeMap<String, String>,
completed_user_message_count: usize,
goal_status: Option<String>,

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate the relevant runtime files and map their structure first.
git ls-files executor/src/runtime_work/events.rs executor/src/runtime_work/handler.rs executor/src/runtime_work/codex.rs
echo '--- outline events.rs ---'
ast-grep outline executor/src/runtime_work/events.rs --view expanded || true
echo '--- outline handler.rs ---'
ast-grep outline executor/src/runtime_work/handler.rs --view expanded || true
echo '--- outline codex.rs ---'
ast-grep outline executor/src/runtime_work/codex.rs --view expanded || true

Repository: wecode-ai/Wegent

Length of output: 27749


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Read the relevant sections around the cited lines and search for goal status flow.
sed -n '1,220p' executor/src/runtime_work/events.rs
echo '--- handler.rs matches ---'
rg -n "notification-mapper|CodexNotificationEventMapper|notification_tx|thread/goal/updated|thread/goal/get|goal_status|has_active_goal|runtime.goal.continuation" executor/src/runtime_work/handler.rs executor/src/runtime_work/codex.rs executor/src/runtime_work/events.rs

Repository: wecode-ai/Wegent

Length of output: 10772


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the specific code paths that determine goal_status initialization and updates.
printf '\n== events.rs (selected range) ==\n'
nl -ba executor/src/runtime_work/events.rs | sed -n '1,220p'

printf '\n== handler.rs goal/thread references ==\n'
rg -n -C 3 "CodexNotificationEventMapper|notification_tx|thread/goal/updated|thread/goal/get|goal_status|has_active_goal|runtime\.goal\.continuation|spawn_turn" executor/src/runtime_work/handler.rs executor/src/runtime_work/codex.rs executor/src/runtime_work/events.rs

Repository: wecode-ai/Wegent

Length of output: 228


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Read only the relevant code slices.
echo '--- executor/src/runtime_work/events.rs ---'
nl -ba executor/src/runtime_work/events.rs | sed -n '1,220p'

echo '--- executor/src/runtime_work/handler.rs ---'
nl -ba executor/src/runtime_work/handler.rs | sed -n '1,260p'

echo '--- executor/src/runtime_work/codex.rs ---'
nl -ba executor/src/runtime_work/codex.rs | sed -n '1,260p'

Repository: wecode-ai/Wegent

Length of output: 238


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the exact control flow around goal-status initialization and continuation emission.
echo '--- executor/src/runtime_work/events.rs ---'
nl -ba executor/src/runtime_work/events.rs | sed -n '1,220p'

echo '--- executor/src/runtime_work/handler.rs ---'
nl -ba executor/src/runtime_work/handler.rs | sed -n '1,260p'

echo '--- executor/src/runtime_work/codex.rs ---'
nl -ba executor/src/runtime_work/codex.rs | sed -n '1,260p'

echo '--- references ---'
rg -n -C 2 "thread/goal/updated|thread/goal/get|goal_status|has_active_goal|runtime\.goal\.continuation|CodexNotificationEventMapper|notification_tx|spawn_turn" executor/src/runtime_work

Repository: wecode-ai/Wegent

Length of output: 238


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find where CodexNotificationEventMapper is constructed and how goal_status is initialized.
rg -n -C 4 "CodexNotificationEventMapper\s*\{|goal_status\s*:\s*None|goal_status\s*:\s*Some|new\(" executor/src/runtime_work

Repository: wecode-ai/Wegent

Length of output: 50373


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path

def show(path, start, end):
    print(f"\n== {path}:{start}-{end} ==")
    lines = Path(path).read_text().splitlines()
    for i in range(start, min(end, len(lines)) + 1):
        print(f"{i:4d}: {lines[i-1]}")

show("executor/src/runtime_work/events.rs", 300, 375)
show("executor/src/runtime_work/events.rs", 930, 960)
show("executor/src/runtime_work/handler.rs", 1260, 1295)
show("executor/src/runtime_work/handler.rs", 2215, 2570)
PY

Repository: wecode-ai/Wegent

Length of output: 23631


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path

for path, ranges in [
    ("executor/src/runtime_work/events.rs", [(300, 375), (930, 960), (2650, 2755)]),
    ("executor/src/runtime_work/handler.rs", [(1260, 1295), (2215, 2570)]),
]:
    lines = Path(path).read_text().splitlines()
    for start, end in ranges:
        print(f"\n== {path}:{start}-{end} ==")
        for i in range(start, min(end, len(lines)) + 1):
            print(f"{i:4d}: {lines[i-1]}")
PY

Repository: wecode-ai/Wegent

Length of output: 27915


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path

def show(path, start, end):
    lines = Path(path).read_text().splitlines()
    print(f"\n== {path}:{start}-{end} ==")
    for i in range(start, min(end, len(lines)) + 1):
        print(f"{i:4d}: {lines[i-1]}")

show("executor/src/runtime_work/handler.rs", 2162, 2315)
show("executor/src/runtime_work/handler.rs", 1848, 1915)
PY

Repository: wecode-ai/Wegent

Length of output: 10816


Seed goal_status for resumed turns — CodexNotificationEventMapper starts with goal_status = None on every spawn_turn, and only a live thread/goal/updated notification sets it. If a resumed thread is already active when this mapper is created, has_active_goal() stays false and runtime.goal.continuation won’t emit for that turn.

🤖 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/src/runtime_work/events.rs` at line 103, Initialize
CodexNotificationEventMapper’s goal_status from the resumed thread’s current
goal state during spawn_turn, rather than always starting at None. Ensure
has_active_goal() reflects an already-active goal before live
thread/goal/updated notifications arrive, allowing runtime.goal.continuation to
emit for resumed turns.

Comment on lines +2169 to +2193
test('shows an active goal as continuing only while a new goal turn is running', () => {
render(
<ChatInput
value=""
onChange={vi.fn()}
onSubmit={vi.fn()}
disabled={false}
variant="desktop"
goalContinuing
goal={{
threadId: 'thread-1',
objective: '继续完成测试',
status: 'active',
tokenBudget: null,
tokensUsed: 0,
timeUsedSeconds: 0,
createdAt: 1780000000000,
updatedAt: 1780000000000,
}}
/>
)

expect(screen.getByTestId('goal-status-bar')).toHaveTextContent('目标继续执行中')
expect(screen.getByTestId('pause-goal-button')).toBeInTheDocument()
})

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Cover the non-continuing active state.

This only asserts goalContinuing=true; it never verifies that an active goal uses the normal active label when the flag is false. Add a false-path case so the “only while” behavior cannot regress unnoticed.

As per coding guidelines, tests should follow Arrange, Act, Assert and cover edge cases.

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

In `@wework/src/components/chat/ChatInput.test.tsx` around lines 2169 - 2193, The
ChatInput goal-status tests do not cover an active goal when goalContinuing is
false. Add a separate test near the existing “shows an active goal as
continuing” case using the same active goal fixture, arrange
goalContinuing={false}, render ChatInput, and assert the normal active label is
shown rather than “目标继续执行中”; follow Arrange, Act, Assert structure.

Source: Coding guidelines

Comment on lines +2195 to +2222
test.each(['blocked', 'usageLimited', 'budgetLimited'] as const)(
'does not offer the pause action for a %s goal',
status => {
render(
<ChatInput
value=""
onChange={vi.fn()}
onSubmit={vi.fn()}
disabled={false}
variant="desktop"
goal={{
threadId: 'thread-1',
objective: 'Resolve the issue',
status,
tokenBudget: null,
tokensUsed: 0,
timeUsedSeconds: 0,
createdAt: 1780000000000,
updatedAt: 1780000000000,
}}
/>
)

expect(screen.queryByTestId('pause-goal-button')).not.toBeInTheDocument()
expect(screen.queryByTestId('resume-goal-button')).not.toBeInTheDocument()
}
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Include complete in the disallowed-status matrix.

GoalStatusBar suppresses toggles for complete goals too, but this table omits that status. Add it to cover every non-toggleable goal state.

As per coding guidelines, tests should cover edge cases and remain isolated.

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

In `@wework/src/components/chat/ChatInput.test.tsx` around lines 2195 - 2222,
Extend the status matrix in the “does not offer the pause action for a %s goal”
test to include the complete status alongside blocked, usageLimited, and
budgetLimited, preserving the existing isolated parameterized test coverage for
all non-toggleable goal states.

Source: Coding guidelines

Comment on lines 1272 to 1277
setError(response.error || i18n.t('workbench.goal_set_failed'))
return
}
setThreadGoal(response.goal)
commitThreadGoal(response.goal)
setGoalDraftActive(false)
const queuedMessage: RuntimePaneQueuedMessage = {

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

Stale goalContinuation not cleared on manual goal mutations.

goalContinuation is reset on task-target change, onAssistantStart, and the two server-pushed goal events (onRuntimeGoalUpdated/onRuntimeGoalCleared), but not on the three client-initiated commitThreadGoal calls: goal creation (1275), pause/resume via updateCurrentGoalStatus (1667), and manual clear (1726). Since goalContinuing is derived as goal?.status === 'active' && goalContinuation?.status === 'started', a leftover {status:'started'} from a prior turn can make a freshly created or just-resumed goal appear as "continuing" until the next assistant-start or goal event happens to clear it.

🧹 Proposed fix to reset goalContinuation alongside these commits
               commitThreadGoal(response.goal)
+              setGoalContinuation(null)
               setGoalDraftActive(false)
        if (!response.accepted) return false

        commitThreadGoal(response.goal)
+        setGoalContinuation(null)
        return true
       const response = await clearRuntimeGoal(currentRuntimeTask)
       if (!response.accepted) return false

       commitThreadGoal(null)
+      setGoalContinuation(null)
       return true

Also applies to: 1660-1669, 1715-1728

🤖 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/useWorkbenchPaneSession.ts` around lines 1272 -
1277, Clear the stale goalContinuation state alongside every client-initiated
goal mutation. In the goal creation flow near commitThreadGoal(response.goal),
and in the pause/resume updateCurrentGoalStatus flow and manual clear flow,
reset goalContinuation immediately after the successful commit so newly created,
resumed, or cleared goals cannot inherit a prior started continuation state.

Comment on lines +5714 to +5765
test('keeps an active runtime goal active while the task list is between automatic turns', async () => {
const runtimeWorkApi = createRuntimeWorkApiMock({
listRuntimeWork: vi.fn().mockResolvedValue(
createRuntimeWork({
projects: [
{
project: { id: 7, name: 'Wegent' },
deviceWorkspaces: [
{
id: 22,
projectId: 7,
deviceId: 'device-1',
deviceName: 'Project Device',
deviceStatus: 'online',
workspacePath: '/workspace/project-alpha',
mapped: true,
available: true,
tasks: [
{
taskId: 'runtime-a',
workspacePath: '/workspace/project-alpha',
title: 'Runtime A',
runtime: 'codex',
running: false,
status: 'idle',
},
],
},
],
},
],
totalTasks: 1,
})
),
getRuntimeGoal: vi.fn().mockResolvedValue({
accepted: true,
goal: createRuntimeGoal({ status: 'active' }),
}),
})
const services = createWorkbenchServices({
runtimeWorkApi: runtimeWorkApi as WorkbenchServices['runtimeWorkApi'],
})

renderWorkbench(<RuntimeOpenProbe />, services)

await userEvent.click(await screen.findByText('open runtime a'))

await waitFor(() =>
expect(screen.getByTestId('current-runtime-task-running')).toHaveTextContent('idle')
)
expect(screen.getByTestId('runtime-goal-status')).toHaveTextContent('active')
})

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Exercise the automatic-turn boundary, not just the initial idle state.

listRuntimeWork returns one static idle response and no subsequent refresh or continuation event is driven. This proves only that an initially active goal loads, not that it remains active across automatic turns. Make the fixture transition through running → idle (or emit the continuation event) and assert the goal status across that transition.

As per coding guidelines, tests should cover edge cases and remain isolated.

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

In `@wework/src/features/workbench/WorkbenchProvider.test.tsx` around lines 5714 -
5765, Update the test “keeps an active runtime goal active while the task list
is between automatic turns” to drive an actual automatic-turn boundary rather
than only loading a static idle response. Make the listRuntimeWork fixture
transition through running to idle, or trigger the relevant continuation event,
and assert that runtime-goal-status remains active after the transition; keep
the mock and assertions isolated to this test.

Source: Coding guidelines

Comment on lines +5767 to +5837
test('resumes a paused runtime goal when editing and sending its objective', async () => {
const setRuntimeGoal = vi.fn().mockResolvedValue({
accepted: true,
goal: createRuntimeGoal({ objective: '更新后的目标', status: 'active' }),
})
const runtimeWorkApi = createRuntimeWorkApiMock({
listRuntimeWork: vi.fn().mockResolvedValue(
createRuntimeWork({
projects: [
{
project: { id: 7, name: 'Wegent' },
deviceWorkspaces: [
{
id: 22,
projectId: 7,
deviceId: 'device-1',
deviceName: 'Project Device',
deviceStatus: 'online',
workspacePath: '/workspace/project-alpha',
mapped: true,
available: true,
tasks: [
{
taskId: 'runtime-a',
workspacePath: '/workspace/project-alpha',
title: 'Runtime A',
runtime: 'codex',
running: false,
},
],
},
],
},
],
totalTasks: 1,
})
),
getRuntimeGoal: vi.fn().mockResolvedValue({
accepted: true,
goal: createRuntimeGoal({ status: 'paused' }),
}),
setRuntimeGoal,
sendRuntimeMessage: vi.fn().mockResolvedValue({ accepted: true, taskId: 'runtime-a' }),
})
const services = createWorkbenchServices({
runtimeWorkApi: runtimeWorkApi as WorkbenchServices['runtimeWorkApi'],
})

renderWorkbench(<RuntimeOpenProbe />, services)

await userEvent.click(await screen.findByText('open runtime a'))
await waitFor(() =>
expect(screen.getByTestId('runtime-goal-status')).toHaveTextContent('paused')
)
await userEvent.click(screen.getByText('edit runtime goal'))
await userEvent.click(screen.getByText('set edited runtime goal'))
await userEvent.click(screen.getByText('send runtime goal'))

await waitFor(() =>
expect(setRuntimeGoal).toHaveBeenCalledWith({
address: {
deviceId: 'device-1',
workspacePath: '/workspace/project-alpha',
taskId: 'runtime-a',
},
objective: '更新后的目标',
status: 'active',
})
)
expect(screen.getByTestId('runtime-goal-status')).toHaveTextContent('active')
})

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Assert that the edited objective is actually sent.

The test verifies setRuntimeGoal, but never checks sendRuntimeMessage. It can therefore pass even if the send step is removed or skipped after the optimistic goal update. Assert that the mocked send API is called after the edit flow.

As per coding guidelines, tests should cover the complete behavior under test.

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

In `@wework/src/features/workbench/WorkbenchProvider.test.tsx` around lines 5767 -
5837, Extend the test “resumes a paused runtime goal when editing and sending
its objective” to retain a reference to the mocked sendRuntimeMessage function
and assert it is called after the edit flow with the edited objective and the
runtime task address. Keep the existing setRuntimeGoal assertion and place the
sendRuntimeMessage expectation after the user clicks “send runtime goal.”

Source: Coding guidelines

@qdaxb
qdaxb merged commit 0328cd0 into main Jul 11, 2026
19 checks passed
@qdaxb
qdaxb deleted the fix/wework-goal-continuation-status branch July 11, 2026 04:00
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