Skip to content

feat(wework): add personal task supervisor - #2392

Merged
qdaxb merged 3 commits into
mainfrom
feature/personal-supervisor
Aug 3, 2026
Merged

feat(wework): add personal task supervisor#2392
qdaxb merged 3 commits into
mainfrom
feature/personal-supervisor

Conversation

@qdaxb

@qdaxb qdaxb commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

What changed

  • Added an experimental personal supervisor for local Codex tasks.
  • Added configurable supervision mode, model, and inspection frequency.
  • Runs lightweight, ephemeral evaluations against bounded recent AI output without forking the task.
  • Delivers corrections as active-turn guidance or ordered follow-up messages.
  • Added deduplication, final-turn inspection, persisted background scheduling, i18n, documentation, unit tests, and desktop E2E coverage.
  • Prevented stopped tasks with no AI output from being polled indefinitely.

Why

Users need a proxy that can watch ongoing AI work and intervene with the same effect as a message typed into the task composer. The supervision loop must keep working when the task UI is closed, react while work is still in progress, and avoid creating a separate task or conversation.

User impact

After enabling Experimental Features, users can configure supervision from a task, choose suggestion or automatic correction, select a model and frequency, and leave the workbench UI while the executor continues inspecting active work.

Supervision stops scheduling checks once a task has finished and its final content has been inspected. The configuration remains available for the next turn until the user closes supervision.

Validation

  • Wework ESLint, TypeScript, and unit tests
  • cargo fmt --check
  • cargo test --all-features --lib
  • cargo clippy --all-features --lib -- -D warnings
  • Focused supervisor unit tests
  • Desktop E2E segment: supervisor-lifecycle

Summary by CodeRabbit

  • New Features
    • Added experimental personal task supervision with “Suggest” and “Auto” modes.
    • Configure supervision principles, review model, check frequency, and task-specific instructions.
    • Review, accept, dismiss, or automatically apply supervisor suggestions and corrections.
    • Added supervision status updates and generated guidance within task conversations.
  • Documentation
    • Added English and Simplified Chinese guidance covering setup, monitoring, correction modes, and configuration.
  • Bug Fixes
    • Improved message ordering and copy-status behavior in task conversations.

@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Changes

Personal task supervision

Layer / File(s) Summary
Supervisor state and preference contracts
executor/src/runtime_work/response.rs, wework/src/types/api.ts, wework/src/tauri/*, wework/src-tauri/src/lib.rs
Added supervisor state, suggestion, request, event, task-summary, and preference models.
Supervisor evaluation and correction engine
executor/src/runtime_work/handler/*, executor/src/agents/codex.rs
Added scheduled read-only evaluation, suggestion persistence, duplicate suppression, and automatic correction delivery.
Runtime API and event transport
wework/src/api/*, wework/src/stream/*, wework/src/features/workbench/WorkbenchProvider.tsx
Added supervisor operations and update-event routing across local, hybrid, HTTP, and stream APIs.
Supervisor settings and workbench controls
wework/src/components/layout/*, wework/src/components/settings/*, wework/src/i18n/locales/*
Added configuration controls, suggestion cards, experimental supervisor principles, localized strings, and component tests.
Generated correction message handling
executor/src/runtime_work/handler/helpers/transcript.rs, wework/src/features/workbench/*
Added ordering, transcript attachment, and stream handling for supervisor-generated user messages and guidance.
Documentation and lifecycle validation
docs/*, wework/e2e/desktop/*
Documented supervision and added a desktop lifecycle scenario for automatic evaluation and correction.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant Workbench
  participant RuntimeWork
  participant Codex
  participant Task
  User->>Workbench: configure supervisor
  Workbench->>RuntimeWork: set supervisor state
  RuntimeWork->>Codex: run read-only evaluation
  Codex-->>RuntimeWork: return structured result
  RuntimeWork->>Task: create suggestion or send correction
  Task-->>Workbench: emit supervisor update and generated message
  Workbench-->>User: display status or correction
Loading

Possibly related PRs

Suggested reviewers: icycrystal4, micro66

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 28.75% 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 and concisely describes the main change: adding a personal task supervisor to Wework.
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 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/personal-supervisor

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 August 3, 2026 13:11
@qdaxb
qdaxb enabled auto-merge August 3, 2026 13:11

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

Caution

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

⚠️ Outside diff range comments (1)
wework/src/features/workbench/runtimePaneMessages.ts (1)

149-180: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Dispatch the supervisor-generated user message after identity validation.

onChatStart dispatches user_added for payload.runtimeGeneratedUserMessage before calling runtimeStreamTaskSubtaskIdentity(payload). If identity resolution fails, the handler warns and returns without emitting assistant_started, onAssistantStart, or onRefreshWorkLists — but the user_added action already ran. This leaves an orphaned supervisor correction message in the local cache with no associated assistant turn.

Move the runtimeGeneratedUserMessage block after the identity check so both actions are emitted together, or neither is.

🐛 Proposed fix
     onChatStart: payload => {
       if (!isRuntimeTaskStreamPayload(address, payload)) return
-      if (payload.runtimeGeneratedUserMessage) {
-        handlers.onMessageAction({
-          type: 'user_added',
-          message: {
-            id: payload.runtimeGeneratedUserMessage.id,
-            taskId: address.taskId,
-            role: 'user',
-            content: payload.runtimeGeneratedUserMessage.message,
-            status: 'done',
-            source: payload.runtimeGeneratedUserMessage.source as MessageSource,
-            createdAt: new Date(payload.runtimeGeneratedUserMessage.createdAt).toISOString(),
-          },
-        })
-      }
       const identity = runtimeStreamTaskSubtaskIdentity(payload)
       if (!identity) {
         warnAndDropRuntimeStreamEvent('chat:start', address, payload)
         return
       }
+      if (payload.runtimeGeneratedUserMessage) {
+        handlers.onMessageAction({
+          type: 'user_added',
+          message: {
+            id: payload.runtimeGeneratedUserMessage.id,
+            taskId: address.taskId,
+            role: 'user',
+            content: payload.runtimeGeneratedUserMessage.message,
+            status: 'done',
+            source: payload.runtimeGeneratedUserMessage.source as MessageSource,
+            createdAt: new Date(payload.runtimeGeneratedUserMessage.createdAt).toISOString(),
+          },
+        })
+      }
       debugRuntimeStreamEvent('chat:start', address, payload, true)
🤖 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/runtimePaneMessages.ts` around lines 149 - 180,
Move the runtimeGeneratedUserMessage user_added dispatch in onChatStart to after
runtimeStreamTaskSubtaskIdentity(payload) succeeds and the invalid-identity
early return. Keep the existing message construction unchanged so the supervisor
message is emitted only alongside the validated assistant_started flow.
🧹 Nitpick comments (7)
wework/src/components/layout/TaskSupervisorControl.test.tsx (1)

111-127: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add coverage for the rejection path.

The current test only covers a resolved onAccept. Add a case where onAccept rejects, and assert that the card shows an error message. This locks in the error handling requested in TaskSupervisorControl.tsx lines 285-295. Add a matching case for a rejected onSet in TaskSupervisorControl, which already renders 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/TaskSupervisorControl.test.tsx` around lines 111
- 127, Extend the tests around SupervisorSuggestionCards to cover a rejected
onAccept promise and assert that the card displays the resulting error message.
Add a corresponding TaskSupervisorControl test with a rejected onSet promise,
verifying its existing error rendering behavior; keep the current successful
action coverage unchanged.
wework/src/components/layout/TaskSupervisorControl.tsx (1)

94-138: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a dismissal path for the configuration panel.

The panel opens on click and stays open until the user clicks the toggle again. Keyboard users cannot close it with Escape, and a click outside the panel does not close it. Add an Escape key handler and an outside-click handler so the popover behaves like the other workbench popovers.

If a shared popover primitive already exists in wework/src/components/ui, reuse it instead of adding local handlers.

🤖 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/TaskSupervisorControl.tsx` around lines 94 -
138, The configuration panel controlled by the toggle’s open state lacks
dismissal behavior. Reuse the existing popover primitive from the UI components,
if available, around the toggle and panel so Escape closes it and clicks outside
dismiss it; otherwise add handlers tied to the panel’s open state and preserve
the existing toggle behavior and panel content.
executor/src/runtime_work/handler/supervisor.rs (4)

564-566: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Split the system prompt across lines.

The prompt is a single ~1400-character line. SUPERVISOR_PROMPT_VERSION shows the prompt will be revised, and every revision will produce a one-line diff that reviewers cannot read.

Use a concat! of per-sentence literals or an indented raw string so future prompt edits produce readable diffs.

🤖 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/handler/supervisor.rs` around lines 564 - 566,
Update supervisor_system_prompt() to format the long prompt across readable
lines using concat! with per-sentence string literals or an indented raw string,
while preserving the exact prompt content and returned String behavior. Keep
SUPERVISOR_PROMPT_VERSION compatibility unchanged.

36-49: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Bound instructions length.

mode and interval_seconds are validated, but instructions is accepted at any length and is interpolated verbatim into every evaluation prompt by supervisor_prompt. Every other prompt input in this file is bounded, by SUPERVISOR_LATEST_CONTENT_CHARS and SUPERVISOR_CONTEXT_CONTENT_CHARS. An oversized value inflates the token cost of every scheduled evaluation, at the configured interval, for the lifetime of the task.

Reject or truncate instructions above an explicit character limit.

🤖 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/handler/supervisor.rs` around lines 36 - 49, Bound
the instructions value parsed in the supervisor handler before it is stored or
passed to supervisor_prompt. Add an explicit character limit consistent with the
existing prompt-input limits, and reject or truncate values exceeding it while
preserving normal processing for valid instructions.

751-791: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a test for the interval path of an active task.

The two scheduling tests cover inactive tasks only: completion between ticks, and a stopped task with no AI content. The primary path, an active task that becomes due after interval_seconds, has no test. That path also contains the interval_seconds arithmetic flagged at Line 627.

Add two cases with active = true: one where now - last_evaluated_at is just below the interval and the check returns false, and one where it is at or above the interval and the check returns true.

🤖 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/handler/supervisor.rs` around lines 751 - 791, Add
tests for the active-task interval path in supervisor_needs_scheduled_check:
with active=true and a last_evaluated_at timestamp, verify a now value just
below interval_seconds returns false, and a value at or beyond the interval
returns true. Keep the existing completion and stopped-task cases unchanged.

165-189: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Avoid scanning all tasks every supervisor tick.

start_supervisor_scheduler calls local_task_links(false) every 5 seconds, and local_task_links delegates to RuntimeWorkStore::list_task_summaries, which refreshes the task index at runtime. Devices with supervisor disabled still incur this store refresh and sort each tick because supervisor filtering happens only after listing. Skip the listing when no supervision state exists, or use a small tracked set of active supervisor task IDs updated by supervisor enable/clear operations.

🤖 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/handler/supervisor.rs` around lines 165 - 189,
Update start_supervisor_scheduler to avoid calling local_task_links(false) on
every tick when no supervisor state exists; add a lightweight guard or tracked
active supervisor-task ID set maintained by supervisor enable/clear operations,
and only evaluate those tasks while preserving supervisor_needs_scheduled_check
behavior.
wework/src/stream/responseApiStream.ts (1)

781-787: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Skip last_content_hash from the supervisor contract.

RuntimeSupervisorState and RuntimeSupervisorSuggestion already serialize with camelCase, so the field names match TypeScript. last_content_hash is only used for internal deduplication and has no declaration in RuntimeSupervisorState; add #[serde(skip_serializing)] or remove it so the emitted payload matches the declared contract.

🤖 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/stream/responseApiStream.ts` around lines 781 - 787, Update the
serialization definition for the supervisor state or suggestion type used by the
runtime supervisor update flow to exclude last_content_hash from emitted
payloads, using the existing serde skip attribute or removing the field.
Preserve the camelCase serialization of all declared RuntimeSupervisorState and
RuntimeSupervisorSuggestion fields.
🤖 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/handler/supervisor.rs`:
- Around line 551-562: Update parse_supervisor_evaluation to bind the result of
prefix stripping and use that intermediate value as the fallback when removing
the closing fence, preserving the stripped content for unterminated fences. Add
a test covering truncated ```json input that still parses successfully.
- Around line 508-519: Update record_supervisor_error and
supervisor_needs_scheduled_check to track consecutive failures and a next-retry
timestamp on supervisor state, applying exponential backoff between retries and
stopping scheduling after a bounded failure limit. Preserve the failure status
and error details for UI visibility, and reset the failure/backoff state when a
supervisor evaluation succeeds or is re-enabled.
- Around line 622-631: Update the interval threshold calculation in the
supervisor activity check to use saturating multiplication after the i64
conversion, preventing overflow when an out-of-range persisted interval becomes
i64::MAX. Preserve the existing fallback and comparison behavior for valid
intervals.
- Around line 232-243: Update the unchanged-content branch in the supervisor
evaluation flow to call emit_supervisor_updated after store.update_task
refreshes last_evaluated_at, last_error, and status. Preserve the existing early
return while ensuring the emitted event carries the refreshed supervisor state.
- Around line 109-126: Update the suggestion-status handler around update_task
so it verifies that suggestion_id matched an entry and returns a not_found error
when no suggestion is found. Preserve the existing status and resolved_at
updates for matches, and only emit supervisor_updated and return the accepted
supervisor_response after a successful modification.

In `@wework/e2e/desktop/task-flow.e2e.mjs`:
- Around line 6662-6671: Remove the redundant withTimeout wrappers around the
two awaitScenarioRequestCount calls in the supervisor flow, since that method
already applies the default timeout. Await each request count directly and
preserve the specific diagnostic messages through a follow-up assertion or
otherwise ensure failures retain equivalent context; do not keep competing
timeouts with the same duration.
- Around line 9042-9055: Update the evaluator-request assertions in the visible
request-checking branch to track whether this is the first evaluation. Require
SUPERVISOR_COMPLETION_TEXT only for the first evaluator request; for subsequent
requests, accept either SUPERVISOR_COMPLETION_TEXT or
SUPERVISOR_CORRECTION_COMPLETION_TEXT while preserving the schema and
original-transcript assertions.
- Line 6640: Update verifyTaskSupervisorLifecycle around
control.setScenario('supervisor') to save the prior scenario, wrap the lifecycle
steps in try/finally, restore control.scenario in finally, and disable
supervision before returning by clicking the toggle button and
task-supervisor-disable-button.

In `@wework/src/api/runtimeWork.ts`:
- Around line 158-173: Update getRuntimeSupervisor, setRuntimeSupervisor,
clearRuntimeSupervisor, and resolveRuntimeSupervisor to reject immediately
before calling client.post, matching compactRuntimeTask’s unsupported cloud-task
handling. Preserve the existing request signatures and response types while
ensuring none of the /runtime-work/supervisor/* HTTP calls are issued.

In `@wework/src/components/layout/TaskSupervisorControl.tsx`:
- Around line 285-295: Update TaskSupervisorControl’s resolve function to catch
rejected action calls, store a user-facing error message in dedicated
resolve-error state, and clear that state when starting a new resolution or
after successful completion. Render the stored message in the suggestion card
near the rationale only when no resolution is active, while preserving the
existing resolvingId cleanup and handling both onAccept and onDismiss failures.

In `@wework/src/components/settings/ContextSettingsPage.tsx`:
- Around line 121-128: Prevent in-flight edits from being overwritten in
handleSaveSupervisorPrinciples and the corresponding save flow around lines
197-203: disable the supervisor-principles textarea while saving, or track and
preserve edits made after the request begins instead of unconditionally applying
nextPreferences.supervisorPrinciples. Ensure the user’s latest input remains
intact when the request completes.

In `@wework/src/features/workbench/runtimeConversationCache.ts`:
- Around line 403-417: Validate that payload.appliedAtMs is a finite number
before the fallback guidance object is constructed in the guidance-message flow.
Ensure the non-queued executor-originated path never calls
Date(...).toISOString() with an invalid timestamp, while preserving the existing
queued guidance behavior and valid-timestamp fallback.

---

Outside diff comments:
In `@wework/src/features/workbench/runtimePaneMessages.ts`:
- Around line 149-180: Move the runtimeGeneratedUserMessage user_added dispatch
in onChatStart to after runtimeStreamTaskSubtaskIdentity(payload) succeeds and
the invalid-identity early return. Keep the existing message construction
unchanged so the supervisor message is emitted only alongside the validated
assistant_started flow.

---

Nitpick comments:
In `@executor/src/runtime_work/handler/supervisor.rs`:
- Around line 564-566: Update supervisor_system_prompt() to format the long
prompt across readable lines using concat! with per-sentence string literals or
an indented raw string, while preserving the exact prompt content and returned
String behavior. Keep SUPERVISOR_PROMPT_VERSION compatibility unchanged.
- Around line 36-49: Bound the instructions value parsed in the supervisor
handler before it is stored or passed to supervisor_prompt. Add an explicit
character limit consistent with the existing prompt-input limits, and reject or
truncate values exceeding it while preserving normal processing for valid
instructions.
- Around line 751-791: Add tests for the active-task interval path in
supervisor_needs_scheduled_check: with active=true and a last_evaluated_at
timestamp, verify a now value just below interval_seconds returns false, and a
value at or beyond the interval returns true. Keep the existing completion and
stopped-task cases unchanged.
- Around line 165-189: Update start_supervisor_scheduler to avoid calling
local_task_links(false) on every tick when no supervisor state exists; add a
lightweight guard or tracked active supervisor-task ID set maintained by
supervisor enable/clear operations, and only evaluate those tasks while
preserving supervisor_needs_scheduled_check behavior.

In `@wework/src/components/layout/TaskSupervisorControl.test.tsx`:
- Around line 111-127: Extend the tests around SupervisorSuggestionCards to
cover a rejected onAccept promise and assert that the card displays the
resulting error message. Add a corresponding TaskSupervisorControl test with a
rejected onSet promise, verifying its existing error rendering behavior; keep
the current successful action coverage unchanged.

In `@wework/src/components/layout/TaskSupervisorControl.tsx`:
- Around line 94-138: The configuration panel controlled by the toggle’s open
state lacks dismissal behavior. Reuse the existing popover primitive from the UI
components, if available, around the toggle and panel so Escape closes it and
clicks outside dismiss it; otherwise add handlers tied to the panel’s open state
and preserve the existing toggle behavior and panel content.

In `@wework/src/stream/responseApiStream.ts`:
- Around line 781-787: Update the serialization definition for the supervisor
state or suggestion type used by the runtime supervisor update flow to exclude
last_content_hash from emitted payloads, using the existing serde skip attribute
or removing the field. Preserve the camelCase serialization of all declared
RuntimeSupervisorState and RuntimeSupervisorSuggestion fields.
🪄 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: 4b4591d1-4570-48a5-8937-0624806a443a

📥 Commits

Reviewing files that changed from the base of the PR and between 8037534 and a7c36bf.

📒 Files selected for processing (41)
  • docs/en/wework/settings.md
  • docs/en/wework/tasks.md
  • docs/zh/wework/settings.md
  • docs/zh/wework/tasks.md
  • executor/src/agents/codex.rs
  • executor/src/agents/codex/tests.rs
  • executor/src/runtime_work/events.rs
  • executor/src/runtime_work/handler.rs
  • executor/src/runtime_work/handler/helpers/transcript.rs
  • executor/src/runtime_work/handler/supervisor.rs
  • executor/src/runtime_work/handler/tests.rs
  • executor/src/runtime_work/response.rs
  • executor/src/runtime_work/worktrees.rs
  • wework/e2e/desktop/checkpoints.mjs
  • wework/e2e/desktop/task-flow.e2e.mjs
  • wework/src-tauri/src/lib.rs
  • wework/src/App.tsx
  • wework/src/api/executorAccess.ts
  • wework/src/api/hybrid/hybridServices.ts
  • wework/src/api/local/localServices.test.ts
  • wework/src/api/local/localServices.ts
  • wework/src/api/runtime/runtimeChatStream.ts
  • wework/src/api/runtimeWork.ts
  • wework/src/components/layout/DesktopWorkbenchMain.tsx
  • wework/src/components/layout/TaskSupervisorControl.test.tsx
  • wework/src/components/layout/TaskSupervisorControl.tsx
  • wework/src/components/settings/ContextSettingsPage.test.tsx
  • wework/src/components/settings/ContextSettingsPage.tsx
  • wework/src/features/workbench/WorkbenchProvider.tsx
  • wework/src/features/workbench/runtimeConversationCache.test.ts
  • wework/src/features/workbench/runtimeConversationCache.ts
  • wework/src/features/workbench/runtimePaneMessages.test.ts
  • wework/src/features/workbench/runtimePaneMessages.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/tauri/appPreferences.test.ts
  • wework/src/tauri/appPreferences.ts
  • wework/src/types/api.ts

Comment on lines +109 to +126
self.store.update_task(&link.local_task_id, |task| {
let Some(supervisor) = task.supervisor.as_mut() else {
return;
};
if let Some(suggestion) = supervisor
.suggestions
.iter_mut()
.find(|suggestion| suggestion.id == suggestion_id)
{
suggestion.status = status.clone();
suggestion.resolved_at = Some(now_ms());
}
task.updated_at = now_ms();
});
self.emit_supervisor_updated(&link.local_task_id);
Ok(supervisor_response(
&self.local_task_link(&link.local_task_id).unwrap_or(link),
))

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Confirm update_task invokes the closure synchronously.
ast-grep run --pattern 'pub fn update_task($$$) { $$$ }' --lang rust executor/src
rg -nP --type=rust -C8 '\bfn update_task\s*[<(]' executor/src

Repository: wecode-ai/Wegent

Length of output: 3769


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== candidate files =="
fd -a 'supervisor\.rs|store\.rs|error|ipc' . | sed 's#^\./##' | head -200

echo "== supervisor handler snippet =="
sed -n '80,140p' executor/src/runtime_work/handler/supervisor.rs

echo "== runtime work store update functions =="
sed -n '120,190p' executor/src/runtime_work/store.rs

echo "== runtime work supervisor definitions/usages in supervisor.rs =="
sed -n '1,80p' executor/src/runtime_work/handler/supervisor.rs
sed -n '140,220p' executor/src/runtime_work/handler/supervisor.rs

echo "== AppIpcError definitions/usages =="
rg -n "AppIpcError|new\\(" executor/src -g '*.rs' | head -80

echo "== tests mentioning resolve_task_supervisor or supervisor suggestion =="
rg -n "resolve_task_supervisor|suggestions|supervisor_response|suggestionId|accepted" . -g '*.rs' | head -200

Repository: wecode-ai/Wegent

Length of output: 38144


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== response definitions =="
sed -n '1,90p' executor/src/runtime_work/response.rs
sed -n '560,615p' executor/src/runtime_work/handler/supervisor.rs

echo "== AppIpcError definition =="
sed -n '1,110p' executor/src/local/app_ipc.rs

echo "== resolve endpoint contract/route context =="
sed -n '30,50p' executor/src/runtime_work/handler.rs

echo "== frontend/backend references to supervisor.resolve =="
rg -n "supervisor\.resolve|runtime\.tasks\.supervisor\.resolve|suggestionId|suggestion_id|resolved_at|accepted:\s*false|accepted: true" . --glob '!node_modules' --glob '!dist' --glob '!build' | head -240

Repository: wecode-ai/Wegent

Length of output: 25639


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== AppIpcError implementation =="
sed -n '1,120p' executor/src/local/app_ipc.rs | sed -n '5,35p'

echo "== resolve task call site =="
sed -n '640,675p' wework/src/components/layout/DesktopWorkbenchMain.tsx

echo "== runtimeWork implementation around resolve ==
sed -n '2234,2254p' wework/src/features/workbench/WorkbenchProvider.tsx

echo "== focused frontend try-catch patterns for runtimeWork calls =="
rg -n "RuntimeWork|runtimeWork|accepted:\s*false|accepted: false|RuntimeWorkError|catch|try {|resolved_at|suggestionId" wework/src/components/layout/DesktopWorkbenchMain.tsx wework/src -g '*.ts' -g '*.tsx' | head -220

Repository: wecode-ai/Wegent

Length of output: 2522


Return not_found when suggestionId has no match.

update_task returns a value instead of the closure result, so this always emits accepted: true even when no suggestion is modified. For a stale/wrong suggestionId, return an error instead.

🤖 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/handler/supervisor.rs` around lines 109 - 126,
Update the suggestion-status handler around update_task so it verifies that
suggestion_id matched an entry and returns a not_found error when no suggestion
is found. Preserve the existing status and resolved_at updates for matches, and
only emit supervisor_updated and return the accepted supervisor_response after a
successful modification.

Comment on lines +232 to +243
let content_hash = content_hash(&visible_ai_progress);
if supervisor.last_content_hash.as_deref() == Some(content_hash.as_str()) {
self.store.update_task(local_task_id, |task| {
let Some(current) = task.supervisor.as_mut() else {
return;
};
current.last_evaluated_at = Some(now_ms());
current.last_error = None;
current.status = "active".to_owned();
});
return Ok(());
}

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

The unchanged-content path updates last_evaluated_at without emitting an update.

Every other write path in this file calls emit_supervisor_updated after update_task. This branch does not. The UI therefore keeps showing the previous lastEvaluatedAt for as long as the AI output does not change, which makes active supervision look stalled.

🐛 Proposed fix to emit the refreshed state
             current.last_evaluated_at = Some(now_ms());
             current.last_error = None;
             current.status = "active".to_owned();
         });
+        self.emit_supervisor_updated(local_task_id);
         return Ok(());
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let content_hash = content_hash(&visible_ai_progress);
if supervisor.last_content_hash.as_deref() == Some(content_hash.as_str()) {
self.store.update_task(local_task_id, |task| {
let Some(current) = task.supervisor.as_mut() else {
return;
};
current.last_evaluated_at = Some(now_ms());
current.last_error = None;
current.status = "active".to_owned();
});
return Ok(());
}
let content_hash = content_hash(&visible_ai_progress);
if supervisor.last_content_hash.as_deref() == Some(content_hash.as_str()) {
self.store.update_task(local_task_id, |task| {
let Some(current) = task.supervisor.as_mut() else {
return;
};
current.last_evaluated_at = Some(now_ms());
current.last_error = None;
current.status = "active".to_owned();
});
self.emit_supervisor_updated(local_task_id);
return Ok(());
}
🤖 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/handler/supervisor.rs` around lines 232 - 243,
Update the unchanged-content branch in the supervisor evaluation flow to call
emit_supervisor_updated after store.update_task refreshes last_evaluated_at,
last_error, and status. Preserve the existing early return while ensuring the
emitted event carries the refreshed supervisor state.

Comment on lines +508 to +519
fn record_supervisor_error(&self, local_task_id: &str, error: String) {
self.store.update_task(local_task_id, |task| {
let Some(supervisor) = task.supervisor.as_mut() else {
return;
};
supervisor.status = "error".to_owned();
supervisor.last_error = Some(error.clone());
supervisor.last_evaluated_at = Some(now_ms());
supervisor.last_content_hash = None;
});
self.emit_supervisor_updated(local_task_id);
}

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.

🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

A failing evaluation retries every 5 seconds forever, with no backoff.

record_supervisor_error sets last_content_hash = None. supervisor_needs_scheduled_check returns true whenever last_content_hash is None (Line 619). The scheduler ticks every 5 seconds. A supervisor that fails for a persistent reason therefore re-enters evaluate_task_supervisor on every tick for the lifetime of the task.

Two failure shapes make this expensive:

  • Fast failures, for example "runtime task session is not ready" at Line 204, produce one attempt per 5 seconds indefinitely.
  • Model failures produce a new read-only Codex turn per attempt, each with a 60-second timeout, so a broken model configuration bills a turn roughly every minute per supervised task.

Record consecutive failure count and next-retry time on the supervisor state, apply exponential backoff, and stop scheduling after a bounded number of consecutive failures while leaving the state visible to the UI so the user can fix the configuration and re-enable it.

🤖 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/handler/supervisor.rs` around lines 508 - 519,
Update record_supervisor_error and supervisor_needs_scheduled_check to track
consecutive failures and a next-retry timestamp on supervisor state, applying
exponential backoff between retries and stopping scheduling after a bounded
failure limit. Preserve the failure status and error details for UI visibility,
and reset the failure/backoff state when a supervisor evaluation succeeds or is
re-enabled.

Comment on lines +551 to +562
fn parse_supervisor_evaluation(content: &str) -> Result<SupervisorEvaluation, String> {
let trimmed = content.trim();
let json_text = trimmed
.strip_prefix("```json")
.or_else(|| trimmed.strip_prefix("```"))
.unwrap_or(trimmed)
.strip_suffix("```")
.unwrap_or(trimmed)
.trim();
serde_json::from_str(json_text)
.map_err(|error| format!("invalid supervisor evaluation: {error}"))
}

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

The suffix fallback discards the prefix stripping.

.strip_suffix("```").unwrap_or(trimmed) falls back to the original trimmed, not to the value produced by the prefix strip. For output that opens a fence but does not close it, for example a truncated "```json\n{...}", the opening fence is re-added and serde_json::from_str fails.

Bind the intermediate value so each step falls back to the previous step.

🐛 Proposed fix for the fence stripping
 fn parse_supervisor_evaluation(content: &str) -> Result<SupervisorEvaluation, String> {
     let trimmed = content.trim();
-    let json_text = trimmed
-        .strip_prefix("```json")
-        .or_else(|| trimmed.strip_prefix("```"))
-        .unwrap_or(trimmed)
-        .strip_suffix("```")
-        .unwrap_or(trimmed)
-        .trim();
+    let without_prefix = trimmed
+        .strip_prefix("```json")
+        .or_else(|| trimmed.strip_prefix("```"))
+        .unwrap_or(trimmed);
+    let json_text = without_prefix
+        .strip_suffix("```")
+        .unwrap_or(without_prefix)
+        .trim();
     serde_json::from_str(json_text)
         .map_err(|error| format!("invalid supervisor evaluation: {error}"))
 }

Please also add a test for the unterminated-fence input.

🤖 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/handler/supervisor.rs` around lines 551 - 562,
Update parse_supervisor_evaluation to bind the result of prefix stripping and
use that intermediate value as the fallback when removing the closing fence,
preserving the stripped content for unterminated fences. Add a test covering
truncated ```json input that still parses successfully.

Comment on lines +622 to +631
if active
&& supervisor
.last_evaluated_at
.is_some_and(|last_evaluated_at| {
now.saturating_sub(last_evaluated_at)
>= i64::try_from(supervisor.interval_seconds).unwrap_or(i64::MAX) * 1_000
})
{
return true;
}

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.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

i64::MAX * 1_000 can overflow.

i64::try_from(supervisor.interval_seconds).unwrap_or(i64::MAX) yields i64::MAX for an out-of-range value, and the following * 1_000 then overflows. set_task_supervisor validates the interval on the write path, but this value is also read back from the persisted store, so a stale or hand-edited record reaches this multiplication. In a debug build the overflow panics inside the scheduler task.

Use saturating_mul.

🐛 Proposed fix for the multiplication
             .is_some_and(|last_evaluated_at| {
                 now.saturating_sub(last_evaluated_at)
-                    >= i64::try_from(supervisor.interval_seconds).unwrap_or(i64::MAX) * 1_000
+                    >= i64::try_from(supervisor.interval_seconds)
+                        .unwrap_or(i64::MAX)
+                        .saturating_mul(1_000)
             })
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if active
&& supervisor
.last_evaluated_at
.is_some_and(|last_evaluated_at| {
now.saturating_sub(last_evaluated_at)
>= i64::try_from(supervisor.interval_seconds).unwrap_or(i64::MAX) * 1_000
})
{
return true;
}
if active
&& supervisor
.last_evaluated_at
.is_some_and(|last_evaluated_at| {
now.saturating_sub(last_evaluated_at)
>= i64::try_from(supervisor.interval_seconds)
.unwrap_or(i64::MAX)
.saturating_mul(1_000)
})
{
return true;
}
🤖 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/handler/supervisor.rs` around lines 622 - 631,
Update the interval threshold calculation in the supervisor activity check to
use saturating multiplication after the i64 conversion, preventing overflow when
an out-of-range persisted interval becomes i64::MAX. Preserve the existing
fallback and comparison behavior for valid intervals.

Comment on lines +9042 to +9055
if (requestText.includes('Current visible progress snapshot (JSON):')) {
assert.ok(
requestText.includes('correction'),
'The supervisor evaluator request did not include its structured output schema'
)
assert.ok(
requestText.includes(SUPERVISOR_COMPLETION_TEXT),
'The supervisor evaluator did not receive the latest assistant progress'
)
assert.equal(
requestText.includes(SUPERVISOR_PROMPT),
false,
'The supervisor evaluator received the original user transcript instead of recent AI content'
)

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.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

The evaluator branch assumes only one evaluation happens.

The branch asserts that every evaluator request contains SUPERVISOR_COMPLETION_TEXT. The evaluator sends a bounded slice of recent AI content. After the correction turn completes, a further evaluation carries SUPERVISOR_CORRECTION_COMPLETION_TEXT and may no longer carry SUPERVISOR_COMPLETION_TEXT. That request would then fail this assertion and report a misleading cause.

Scope the strict assertion to the first evaluator request, and accept either completion marker for later ones.

🛡️ Proposed guard
       if (requestText.includes('Current visible progress snapshot (JSON):')) {
+        const evaluatorRequests = this.scenarioRequests
+          .get('supervisor')
+          .filter(item =>
+            JSON.stringify(item.body).includes('Current visible progress snapshot (JSON):')
+          ).length
         assert.ok(
           requestText.includes('correction'),
           'The supervisor evaluator request did not include its structured output schema'
         )
         assert.ok(
-          requestText.includes(SUPERVISOR_COMPLETION_TEXT),
+          evaluatorRequests > 1 ||
+            requestText.includes(SUPERVISOR_COMPLETION_TEXT),
           'The supervisor evaluator did not receive the latest assistant progress'
         )
🤖 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 9042 - 9055, Update the
evaluator-request assertions in the visible request-checking branch to track
whether this is the first evaluation. Require SUPERVISOR_COMPLETION_TEXT only
for the first evaluator request; for subsequent requests, accept either
SUPERVISOR_COMPLETION_TEXT or SUPERVISOR_CORRECTION_COMPLETION_TEXT while
preserving the schema and original-transcript assertions.

Comment on lines +158 to +173
getRuntimeSupervisor(data: RuntimeSupervisorGetRequest): Promise<RuntimeSupervisorResponse> {
return client.post('/runtime-work/supervisor/get', data)
},
setRuntimeSupervisor(data: RuntimeSupervisorSetRequest): Promise<RuntimeSupervisorResponse> {
return client.post('/runtime-work/supervisor/set', data)
},
clearRuntimeSupervisor(
data: RuntimeSupervisorClearRequest
): Promise<RuntimeSupervisorResponse> {
return client.post('/runtime-work/supervisor/clear', data)
},
resolveRuntimeSupervisor(
data: RuntimeSupervisorResolveRequest
): Promise<RuntimeSupervisorResponse> {
return client.post('/runtime-work/supervisor/resolve', data)
},

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.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Look for backend routes that serve the supervisor endpoints.
rg -n --iglob '!**/node_modules/**' -C3 'runtime-work/supervisor|supervisor/(get|set|clear|resolve)'
rg -n -C3 'runtime-work/goal/set' --iglob '!**/node_modules/**' --iglob '!wework/src/**'

Repository: wecode-ai/Wegent

Length of output: 154


🏁 Script executed:

#!/bin/bash
set -u

echo "== repo files matching runtimeWork =="
fd -a 'runtimeWork|hybridServices|common.*api|api' . | sed 's#^\./##' | head -200

echo
echo "== runtime-work/supervisor references in repo =="
rg -n --iglob '!**/node_modules/**' --iglob '!**/.git/**' 'runtime-work/supervisor|runtime_work/supervisor|/supervisor/(get|set|clear|resolve)|supervisor' . | head -300

echo
echo "== related API endpoint references excluding wework/src =="
rg -n --iglob '!**/node_modules/**' --iglob '!**/.git/**' --iglob '!wework/src/**' 'runtime-work/(goal|supervisor)|runtime_work/(goal|supervisor)' . | head -300

echo
echo "== inspect candidate backend route patterns =="
rg -n --iglob '!**/node_modules/**' --iglob '!**/.git/**' -C2 "post\(\s*/['\"]runtime-work/(goal|supervisor)|runtime-work/(goal|supervisor)|runtime.work|runtimeWork" . | head -400

Repository: wecode-ai/Wegent

Length of output: 50372


🏁 Script executed:

#!/bin/bash
set -u

echo "== inspect wework runtimeWork.ts relevant section =="
sed -n '1,220p' wework/src/api/runtimeWork.ts

echo
echo "== inspect hybridServices.ts routing relevant section =="
sed -n '1,260p' wework/src/api/hybrid/hybridServices.ts

echo
echo "== inspect backend runtime-work endpoint tests mentioning supervisor =="
rg -n -C3 'supervisor|"/runtime-work/supervisor|/runtime-work/supervisor' backend/backend/app backend tests || true

echo
echo "== inspect executor supervisor router registration =="
sed -n '380,438p' executor/src/runtime_work/handler.rs
sed -n '1,140p' executor/src/runtime_work/handler/supervisor.rs

echo
echo "== inspect localServices supervisor implementations =="
sed -n '2210,2250p' wework/src/api/local/localServices.ts

Repository: wecode-ai/Wegent

Length of output: 28812


🏁 Script executed:

#!/bin/bash
set -u

echo "== exact supervisor route string references excluding tests/docs =="
rg -n --glob '!**/*.md' --glob '!**/wework/src/*.rs' --glob '!executor/tests/**' --glob '!wework/e2e/**' --glob '!**/package-lock.json' '"/runtime-work/supervisor|runtime-work/supervisor|/supervisor/get|/supervisor/set|/supervisor/clear|/supervisor/resolve' .

echo
echo "== backend api.py top imports/router registration =="
sed -n '1,260p' backend/app/api/api.py

echo
echo "== backend endpoints containing runtime-work =="
rg -n --iglob '!**/node_modules/**' --iglob '!**/.git/**' -C3 '"runtime-work|runtime-work/' backend/app/backend app backend/wework --glob 'backend/**/*.py' --glob 'app/**/*.py' --glob '!**/tests/**' | head -300

Repository: wecode-ai/Wegent

Length of output: 9582


🏁 Script executed:

#!/bin/bash
set -u

echo "== backend api.py remainder around supervisor routes =="
rg -n -C4 'runtime-work|supervisor|router|prefix=' backend/app/api/api.py | sed -n '240,360p'

echo
echo "== backend runtime_work endpoint file =="
sed -n '1,260p' backend/app/api/endpoints/runtime_work.py

echo
echo "== backend runtime_work supervisor references =="
rg -n -C3 'supervisor|RuntimeSupervisor|/supervisor|supervisor' backend/app/api/endpoints/runtime_work.py backend/app backend/app/api/endpoints/runtime_work.py tests backend/tests 2>/dev/null | sed -n '1,240p'

Repository: wecode-ai/Wegent

Length of output: 9998


Reject supervisor calls outside local Codex tasks.

These routes are only registered for the local executor IPC path (runtime.tasks.supervisor.*), while /runtime-work/supervisor/* is not served by the backend runtime-work API. Reject getRuntimeSupervisor, setRuntimeSupervisor, clearRuntimeSupervisor, and resolveRuntimeSupervisor before issuing these HTTP calls, like compactRuntimeTask does for unsupported cloud-task APIs.

🤖 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/runtimeWork.ts` around lines 158 - 173, Update
getRuntimeSupervisor, setRuntimeSupervisor, clearRuntimeSupervisor, and
resolveRuntimeSupervisor to reject immediately before calling client.post,
matching compactRuntimeTask’s unsupported cloud-task handling. Preserve the
existing request signatures and response types while ensuring none of the
/runtime-work/supervisor/* HTTP calls are issued.

Comment on lines +285 to +295
const resolve = async (
suggestion: RuntimeSupervisorSuggestion,
action: (suggestion: RuntimeSupervisorSuggestion) => Promise<void>
) => {
setResolvingId(suggestion.id)
try {
await action(suggestion)
} finally {
setResolvingId(null)
}
}

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.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Handle rejections from onAccept and onDismiss.

resolve awaits action(suggestion) without a catch. The callers in DesktopWorkbenchMain.tsx (resolveTaskSupervisorSuggestion, lines 656-672) throw a localized Error when the API response is not accepted, and submitPaneInput can also reject. Both button handlers call void resolve(...), so a rejection becomes an unhandled promise rejection and the user sees no feedback. The suggestion stays pending with no explanation.

Capture the error and render it in the card, in the same way TaskSupervisorControl renders its error state.

🛡️ Proposed fix to surface resolve failures
 export function SupervisorSuggestionCards({
   suggestions,
   onAccept,
   onDismiss,
 }: SupervisorSuggestionCardsProps) {
   const { t } = useTranslation('common')
   const [resolvingId, setResolvingId] = useState<string | null>(null)
+  const [resolveError, setResolveError] = useState<string | null>(null)
   const visible = suggestions.filter(suggestion => suggestion.status === 'pending').slice(-2)
   if (visible.length === 0) return null
 
   const resolve = async (
     suggestion: RuntimeSupervisorSuggestion,
     action: (suggestion: RuntimeSupervisorSuggestion) => Promise<void>
   ) => {
     setResolvingId(suggestion.id)
+    setResolveError(null)
     try {
       await action(suggestion)
+    } catch (error) {
+      setResolveError(error instanceof Error ? error.message : String(error))
     } finally {
       setResolvingId(null)
     }
   }

Then render the message inside the card, for example after the rationale paragraph:

{resolveError && resolvingId === null && (
  <p className="mt-1 text-xs text-red-600">{resolveError}</p>
)}
🤖 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/TaskSupervisorControl.tsx` around lines 285 -
295, Update TaskSupervisorControl’s resolve function to catch rejected action
calls, store a user-facing error message in dedicated resolve-error state, and
clear that state when starting a new resolution or after successful completion.
Render the stored message in the suggestion card near the rationale only when no
resolution is active, while preserving the existing resolvingId cleanup and
handling both onAccept and onDismiss failures.

Comment on lines +121 to +128
const handleSaveSupervisorPrinciples = async () => {
setSaving(true)
setError(null)
try {
const nextPreferences = await updateAppPreferences({ supervisorPrinciples })
setPreferences(nextPreferences)
setSupervisorPrinciples(nextPreferences.supervisorPrinciples)
setSavedSupervisorPrinciples(nextPreferences.supervisorPrinciples)

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

Prevent in-flight edits from being overwritten.

The textarea accepts edits while saving is true. The request at Line 125 contains the earlier value. When it completes, Lines 127-128 replace any later input. Disable the textarea during the save, or preserve edits made after the request starts.

Proposed fix
             <textarea
               data-testid="context-supervisor-principles-textarea"
               value={supervisorPrinciples}
+              disabled={saving}
               onChange={event => setSupervisorPrinciples(event.target.value)}

Also applies to: 197-203

🤖 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/settings/ContextSettingsPage.tsx` around lines 121 -
128, Prevent in-flight edits from being overwritten in
handleSaveSupervisorPrinciples and the corresponding save flow around lines
197-203: disable the supervisor-principles textarea while saving, or track and
preserve edits made after the request begins instead of unconditionally applying
nextPreferences.supervisorPrinciples. Ensure the user’s latest input remains
intact when the request completes.

Comment on lines +403 to +417
const queuedGuidance = takeAppliedRuntimeConversationGuidance(address, payload)
const guidanceMessage =
queuedGuidance ??
(payload.clientGuidanceId && payload.message
? {
id: payload.clientGuidanceId,
content: payload.message,
status: 'sending',
deliveryMode: 'guidance',
createdAt: new Date(payload.appliedAtMs).toISOString(),
}
: null)
if (!guidanceMessage) return null

if (takeInterruptedRuntimeConversationGuidance(address, guidanceMessage.id)) {
if (queuedGuidance && takeInterruptedRuntimeConversationGuidance(address, guidanceMessage.id)) {

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.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Guard against a non-finite appliedAtMs in the new fallback branch.

When no queued guidance exists, this branch builds a message using new Date(payload.appliedAtMs).toISOString(). If appliedAtMs is not a finite number, toISOString() throws RangeError: Invalid time value.

The queued path already guards this exact scenario elsewhere (see the existing test "uses a valid timestamp when the runtime guidance timestamp is non-finite"), but that test does not exercise this new branch, because it pre-populates a queued message. This new branch is precisely the path used for executor-originated corrections that never touch the local queue, so it is the most likely to encounter an unexpected timestamp value in practice.

Validate appliedAtMs before constructing the Date.

🐛 Proposed fix
   const queuedGuidance = takeAppliedRuntimeConversationGuidance(address, payload)
   const guidanceMessage =
     queuedGuidance ??
     (payload.clientGuidanceId && payload.message
       ? {
           id: payload.clientGuidanceId,
           content: payload.message,
           status: 'sending',
           deliveryMode: 'guidance',
-          createdAt: new Date(payload.appliedAtMs).toISOString(),
+          createdAt: new Date(
+            Number.isFinite(payload.appliedAtMs) ? payload.appliedAtMs : Date.now()
+          ).toISOString(),
         }
       : null)

Based on coding guidelines, "fix the primary flow rather than hiding defects behind fallbacks" supports validating the timestamp at its source instead of letting an unhandled exception propagate.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const queuedGuidance = takeAppliedRuntimeConversationGuidance(address, payload)
const guidanceMessage =
queuedGuidance ??
(payload.clientGuidanceId && payload.message
? {
id: payload.clientGuidanceId,
content: payload.message,
status: 'sending',
deliveryMode: 'guidance',
createdAt: new Date(payload.appliedAtMs).toISOString(),
}
: null)
if (!guidanceMessage) return null
if (takeInterruptedRuntimeConversationGuidance(address, guidanceMessage.id)) {
if (queuedGuidance && takeInterruptedRuntimeConversationGuidance(address, guidanceMessage.id)) {
const queuedGuidance = takeAppliedRuntimeConversationGuidance(address, payload)
const guidanceMessage =
queuedGuidance ??
(payload.clientGuidanceId && payload.message
? {
id: payload.clientGuidanceId,
content: payload.message,
status: 'sending',
deliveryMode: 'guidance',
createdAt: new Date(
Number.isFinite(payload.appliedAtMs) ? payload.appliedAtMs : Date.now()
).toISOString(),
}
: null)
if (!guidanceMessage) return null
if (queuedGuidance && takeInterruptedRuntimeConversationGuidance(address, guidanceMessage.id)) {
🤖 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/runtimeConversationCache.ts` around lines 403 -
417, Validate that payload.appliedAtMs is a finite number before the fallback
guidance object is constructed in the guidance-message flow. Ensure the
non-queued executor-originated path never calls Date(...).toISOString() with an
invalid timestamp, while preserving the existing queued guidance behavior and
valid-timestamp fallback.

Source: Coding guidelines

@qdaxb
qdaxb added this pull request to the merge queue Aug 3, 2026
Merged via the queue into main with commit cba043e Aug 3, 2026
47 checks passed
@qdaxb
qdaxb deleted the feature/personal-supervisor branch August 3, 2026 14:05
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