feat(wework): generate friendly task titles - #2471
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughThe pull request adds configurable asynchronous friendly-title generation, runtime title-update events, linked-task synchronization, title-update animation, standalone-chat handling, and Codex model catalog updates. ChangesFriendly title workflow
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant User
participant Workbench
participant Executor
participant Codex
participant TaskTracking
User->>Workbench: Create runtime task
Workbench->>Executor: Send friendly title request
Executor->>Codex: Run ephemeral title turn
Codex-->>Executor: Return generated title
Executor-->>Workbench: Emit title update
Workbench->>TaskTracking: Synchronize linked task title
Workbench-->>User: Render updated title shimmer
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
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 (1)
wework/src/api/local/localServices.ts (1)
2521-2524: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winPrepare the selected title model before task creation.
Line 2523 prepares only
data.modelId. The title request uses the separatedata.friendlyTitle.modelIdat Lines 1610-1612. If that model requires catalog synchronization, the primary task can start while title generation fails with an unavailable model.Prepare each distinct configured model ID before sending
runtime.tasks.create.🤖 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/local/localServices.ts` around lines 2521 - 2524, Update createRuntimeTask to prepare every distinct configured model before task creation, including data.friendlyTitle.modelId in addition to data.modelId when present. Reuse prepareRuntimeModel for each unique ID and throw modelCatalogSyncCancelled if any preparation fails, before runtime.tasks.create is invoked.
🧹 Nitpick comments (7)
wework/src/components/layout/DesktopSidebar.tsx (2)
1645-1658: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDuplicated shimmer title markup in both
RuntimeTaskRowlayout branches. Both branches write the same title element, the sameruntime-task-title/is-updatedclass toggle, the samearia-hiddenshimmer overlay, and the same twodata-testidvalues. Only the layout utility classes differ.
wework/src/components/layout/DesktopSidebar.tsx#L1645-L1658: extract a localrenderTaskTitle(extraClassName: string)helper inRuntimeTaskRowand call it here with'truncate'.wework/src/components/layout/DesktopSidebar.tsx#L1681-L1694: call the same helper here with'min-w-0 flex-1 truncate'and delete the duplicated markup.As per coding guidelines: "extract shared logic instead of duplicating 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 `@wework/src/components/layout/DesktopSidebar.tsx` around lines 1645 - 1658, In RuntimeTaskRow, extract the duplicated task-title and shimmer markup into a local renderTaskTitle(extraClassName: string) helper. Update wework/src/components/layout/DesktopSidebar.tsx lines 1645-1658 to call it with 'truncate', and lines 1681-1694 to call it with 'min-w-0 flex-1 truncate', removing both duplicated markup blocks while preserving their shared classes, accessibility attribute, and test IDs.Source: Coding guidelines
1502-1513: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winKeep the runtime task title shimmer duration in a shared constant.
The timer currently uses a hardcoded
760ms, while.runtime-task-title-shimmerusesruntime-task-title-sheen 760ms. When animation timing changes, update both sources from a single constant, such asRUNTIME_TASK_TITLE_SHIMMER_MS, to keep the overlay lifetime aligned with the CSS animation.🤖 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/DesktopSidebar.tsx` around lines 1502 - 1513, The runtime task title shimmer duration is duplicated between the JavaScript timeout in the task-title useEffect and the `.runtime-task-title-shimmer` CSS animation. Define a shared RUNTIME_TASK_TITLE_SHIMMER_MS constant and use it for both the setTimeout delay and the runtime-task-title-sheen animation duration, preserving their synchronized timing.wework/src/tauri/appPreferences.test.ts (1)
39-40: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd coverage for
normalizeFriendlyTaskTitleModel.The fixture only records the new defaults. The normalizer added in
wework/src/tauri/appPreferences.tscontains branch logic: it rejects arrays and non-objects, requires non-emptymodelNameandexecutionModelId, restricts model types, and drops non-stringoptionsentries. Add cases for an incomplete config, an unsupportedmodelType, and mixed-typeoptionsso regressions in persisted preferences are caught.🤖 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/tauri/appPreferences.test.ts` around lines 39 - 40, Add test cases for normalizeFriendlyTaskTitleModel covering incomplete configurations, unsupported modelType values, and options arrays containing non-string entries. Assert invalid configurations are rejected and mixed options are filtered to strings, while preserving valid modelName and executionModelId requirements.executor/src/runtime_work/handler/tasks.rs (3)
1121-1133: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd a truncation boundary test.
The two tests cover quoted multilingual input and whitespace-only input.
normalize_friendly_titlealso truncates to 48 characters and then strips trailing punctuation. Add a case with input longer than 48 characters, including multi-byte characters, so the character-based limit stays verified.🤖 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/tasks.rs` around lines 1121 - 1133, Add a unit test alongside normalizes_model_title_to_one_short_line and rejects_empty_model_title that passes normalize_friendly_title an input exceeding 48 Unicode characters, including multi-byte characters, and asserts the result is truncated to 48 characters before trailing punctuation is removed. Verify the expected output uses character count rather than byte length.
335-374: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winExtract and alias the friendly-title queueing path.
Move the payload parsing, logging, and task spawning at
executor/src/runtime_work/handler/tasks.rs:335-374into a private helper such asspawn_friendly_title_generation. Also readfriendlyTitleExecutionRequestorfriendly_title_execution_requestso the existing snake_case convention does not silently skip a valid producer payload.🤖 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/tasks.rs` around lines 335 - 374, Extract the friendly-title payload parsing, event logging, and tokio task spawning from the current handler into a private helper such as spawn_friendly_title_generation, then invoke that helper from the existing flow. Have the helper accept either friendlyTitleExecutionRequest or friendly_title_execution_request, while preserving the current success, rejection, and skipped logging behavior.
30-33: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winNo change needed for duration handling.
run_codex_app_server_turn_on_shared_clientbounds turn startup and notification polling withstartup_deadline, and it cancels the turn viacancellation;CodexAppServerTurnOptionsdoes not expose an additional user-supplied timeout field. The main work remains to name the25/200polling constants.🤖 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/tasks.rs` around lines 30 - 33, Keep run_codex_app_server_turn_on_shared_client’s existing startup_deadline and cancellation-based duration handling unchanged; do not add a timeout to CodexAppServerTurnOptions. Focus the requested change on replacing the nearby polling constants 25 and 200 with clearly named constants.wework/src/components/layout/DesktopSidebar.test.tsx (1)
1784-1844: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAssert that the shimmer is removed, and align the test name with the implementation term.
The test verifies that
is-updatedand the shimmer element appear. It does not verify that both are removed after the 760 ms timer inRuntimeTaskRow. A regression that leaves the shimmer permanently visible would pass. Usevi.useFakeTimers(), advance past the duration insideact, and assert removal.The test name uses "sweeps" while the implementation and test IDs use "shimmer". Use "shimmer" so the same term names the same concept.
🤖 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/DesktopSidebar.test.tsx` around lines 1784 - 1844, Update the test name for the runtime task title change to use “shimmer” instead of “sweeps”. In the test around RuntimeTaskLifecycleStore and RuntimeTaskLifecycleProvider, enable fake timers, advance them past RuntimeTaskRow’s 760 ms shimmer duration inside act after asserting the updated state, then assert both the is-updated class and shimmer test element are removed.
🤖 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/tasks.rs`:
- Around line 83-132: Replace the final latest_link mutation and
upsert_local_task call in the friendly-title generation flow with an in-place
self.store.update_task mutation. Inside its closure, re-read and verify the
stored task title still equals source_title, then update only title and
updated_at; otherwise return the existing skipped result. Preserve concurrent
fields such as thread_id, runtime_handle mappings, and completed_at, and
propagate or map update errors consistently with the surrounding handler.
In `@wework/src/api/local/localDelivery.ts`:
- Around line 577-586: Update updateTaskTrackingTitle so the
runtime_tasks.context request returns null only when the failure confirms that
the task binding is missing; propagate transient IPC, authorization, and other
unexpected errors instead of catching them as successful no-ops. Preserve the
existing missing-binding behavior while allowing the provider to clear its
signature and retry after non-missing failures.
In `@wework/src/api/local/localServices.ts`:
- Around line 1597-1621: Update the friendlyTitleExecutionRequest construction
in buildLocalRuntimeExecutionRequest to create a text-only request: disable tool
access and omit workspace-related context such as workspacePath,
workspaceSource, and branch. Preserve the existing title prompt and model
configuration while ensuring the user-controlled message cannot operate on the
task workspace.
In `@wework/src/components/settings/GeneralSettingsPage.tsx`:
- Line 303: Update the friendlyTitleModels assignment in GeneralSettingsPage to
filter projectChat.models using the same active and compatibility checks as
supervisorModels: retain only models where isActive is not false and
compatibilityDisabled is not true. Apply the identical filtering to the
additional friendly-title model list referenced by the same component.
- Around line 453-466: Update the select element in the friendly task title
model control to enforce a minimum 44px height on mobile, while preserving its
existing desktop sizing and other classes. Use the responsive min-height utility
pattern already applied to the nearby popout shortcut button.
- Around line 270-285: Update saveFriendlyTaskTitles around selectedModel
resolution to detect when the persisted friendlyTaskTitleModel does not match
any workbench.projectChat.models entry, and report that missing model to the
user instead of silently disabling the setting and clearing it. Preserve the
existing patch behavior for valid models, while using the component’s
established error-message or unavailable-model selector mechanism.
---
Outside diff comments:
In `@wework/src/api/local/localServices.ts`:
- Around line 2521-2524: Update createRuntimeTask to prepare every distinct
configured model before task creation, including data.friendlyTitle.modelId in
addition to data.modelId when present. Reuse prepareRuntimeModel for each unique
ID and throw modelCatalogSyncCancelled if any preparation fails, before
runtime.tasks.create is invoked.
---
Nitpick comments:
In `@executor/src/runtime_work/handler/tasks.rs`:
- Around line 1121-1133: Add a unit test alongside
normalizes_model_title_to_one_short_line and rejects_empty_model_title that
passes normalize_friendly_title an input exceeding 48 Unicode characters,
including multi-byte characters, and asserts the result is truncated to 48
characters before trailing punctuation is removed. Verify the expected output
uses character count rather than byte length.
- Around line 335-374: Extract the friendly-title payload parsing, event
logging, and tokio task spawning from the current handler into a private helper
such as spawn_friendly_title_generation, then invoke that helper from the
existing flow. Have the helper accept either friendlyTitleExecutionRequest or
friendly_title_execution_request, while preserving the current success,
rejection, and skipped logging behavior.
- Around line 30-33: Keep run_codex_app_server_turn_on_shared_client’s existing
startup_deadline and cancellation-based duration handling unchanged; do not add
a timeout to CodexAppServerTurnOptions. Focus the requested change on replacing
the nearby polling constants 25 and 200 with clearly named constants.
In `@wework/src/components/layout/DesktopSidebar.test.tsx`:
- Around line 1784-1844: Update the test name for the runtime task title change
to use “shimmer” instead of “sweeps”. In the test around
RuntimeTaskLifecycleStore and RuntimeTaskLifecycleProvider, enable fake timers,
advance them past RuntimeTaskRow’s 760 ms shimmer duration inside act after
asserting the updated state, then assert both the is-updated class and shimmer
test element are removed.
In `@wework/src/components/layout/DesktopSidebar.tsx`:
- Around line 1645-1658: In RuntimeTaskRow, extract the duplicated task-title
and shimmer markup into a local renderTaskTitle(extraClassName: string) helper.
Update wework/src/components/layout/DesktopSidebar.tsx lines 1645-1658 to call
it with 'truncate', and lines 1681-1694 to call it with 'min-w-0 flex-1
truncate', removing both duplicated markup blocks while preserving their shared
classes, accessibility attribute, and test IDs.
- Around line 1502-1513: The runtime task title shimmer duration is duplicated
between the JavaScript timeout in the task-title useEffect and the
`.runtime-task-title-shimmer` CSS animation. Define a shared
RUNTIME_TASK_TITLE_SHIMMER_MS constant and use it for both the setTimeout delay
and the runtime-task-title-sheen animation duration, preserving their
synchronized timing.
In `@wework/src/tauri/appPreferences.test.ts`:
- Around line 39-40: Add test cases for normalizeFriendlyTaskTitleModel covering
incomplete configurations, unsupported modelType values, and options arrays
containing non-string entries. Assert invalid configurations are rejected and
mixed options are filtered to strings, while preserving valid modelName and
executionModelId requirements.
🪄 Autofix
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: 4d688d48-31a9-4221-8223-07a26f9582b0
📒 Files selected for processing (36)
docs/en/wework/settings.mddocs/zh/wework/settings.mdexecutor/src/runtime_work/handler/tasks.rswework/e2e/desktop/task-flow.e2e.mjswework/src-tauri/src/lib.rswework/src/App.plugins.test.tsxwework/src/api/deliveries.test.tswework/src/api/deliveries.tswework/src/api/hybrid/hybridServices.tswework/src/api/local/codexOfficialModels.tswework/src/api/local/localDelivery.test.tswework/src/api/local/localDelivery.tswework/src/api/local/localServices.test.tswework/src/api/local/localServices.tswework/src/api/runtime/runtimeChatStream.tswework/src/components/layout/DesktopSidebar.test.tsxwework/src/components/layout/DesktopSidebar.tsxwework/src/components/layout/DesktopWorkbenchMain.tsxwework/src/components/settings/GeneralSettingsPage.tsxwework/src/components/settings/ModelSettingsPage.tsxwework/src/features/model-settings/codexOfficialModels.test.tswework/src/features/model-settings/codexOfficialModels.tswework/src/features/workbench/WorkbenchProvider.test.tsxwework/src/features/workbench/WorkbenchProvider.tsxwework/src/features/workbench/runtimePaneMessages.test.tswework/src/features/workbench/runtimePaneMessages.tswework/src/features/workbench/useWorkbenchRuntimeMessaging.tswework/src/features/workbench/workbenchReducer.test.tswework/src/features/workbench/workbenchReducer.tswework/src/stream/chatStream.tswework/src/stream/responseApiStream.test.tswework/src/stream/responseApiStream.tswework/src/styles/globals.csswework/src/tauri/appPreferences.test.tswework/src/tauri/appPreferences.tswework/src/types/api.ts
💤 Files with no reviewable changes (2)
- wework/src/components/settings/ModelSettingsPage.tsx
- wework/e2e/desktop/task-flow.e2e.mjs
| let mut latest_link = self.task_link_from_payload(&payload, false).await?; | ||
| for _ in 0..25 { | ||
| if latest_link.title != source_title { | ||
| log_executor_event( | ||
| "friendly task title generation skipped", | ||
| &[ | ||
| ("local_task_id", local_task_id.clone()), | ||
| ("reason", "title_changed_during_generation".to_owned()), | ||
| ], | ||
| ); | ||
| return Ok(json!({"success": true, "skipped": true})); | ||
| } | ||
| if latest_link.thread_id.is_some() { | ||
| break; | ||
| } | ||
| sleep(Duration::from_millis(200)).await; | ||
| latest_link = self.task_link_from_payload(&payload, false).await?; | ||
| } | ||
| if latest_link.title != source_title { | ||
| log_executor_event( | ||
| "friendly task title generation skipped", | ||
| &[ | ||
| ("local_task_id", local_task_id.clone()), | ||
| ("reason", "title_changed_before_update".to_owned()), | ||
| ], | ||
| ); | ||
| return Ok(json!({"success": true, "skipped": true})); | ||
| } | ||
| if let Some(thread_id) = latest_link.thread_id.as_deref() { | ||
| if let Err(error) = self | ||
| .call_codex_thread_method( | ||
| "thread/name/set", | ||
| json!({"threadId": thread_id, "name": title}), | ||
| ) | ||
| .await | ||
| { | ||
| log_executor_event( | ||
| "friendly task title generation failed", | ||
| &[ | ||
| ("local_task_id", local_task_id.clone()), | ||
| ("reason", "thread_name_update_failed".to_owned()), | ||
| ("error", error.clone()), | ||
| ], | ||
| ); | ||
| return Ok(json!({"success": false, "error": error})); | ||
| } | ||
| } | ||
| latest_link.title = title.clone(); | ||
| latest_link.updated_at = now_ms(); | ||
| self.upsert_local_task(latest_link); |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🔴 Critical | 🏗️ Heavy lift
Replace the whole-link upsert with an in-place update_task mutation.
latest_link is read at Line 99 (or Line 83) and written back in full at Line 132. Friendly-title generation runs concurrently with the main turn spawned by create_task, and that turn mutates the same stored link through mark_task_running_for_send and record_runtime_turn_id. Any field those paths write between the read and upsert_local_task is reverted by this stale snapshot, including thread_id, runtime_handle turn mappings, updated_at, and completed_at. The title check at Line 101 is also a time-of-check/time-of-use gap: a rename that lands after the check is overwritten.
Mutate only the title through self.store.update_task, and re-verify the source title inside the closure so the guard and the write are atomic.
🐛 Proposed fix
- latest_link.title = title.clone();
- latest_link.updated_at = now_ms();
- self.upsert_local_task(latest_link);
+ let applied = self
+ .store
+ .update_task(&local_task_id, |link| {
+ if link.title != source_title {
+ return;
+ }
+ link.title = title.clone();
+ link.updated_at = now_ms();
+ })
+ .is_some();
+ if !applied {
+ log_executor_event(
+ "friendly task title generation skipped",
+ &[
+ ("local_task_id", local_task_id.clone()),
+ ("reason", "task_missing_before_update".to_owned()),
+ ],
+ );
+ return Ok(json!({"success": true, "skipped": true}));
+ }📝 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.
| let mut latest_link = self.task_link_from_payload(&payload, false).await?; | |
| for _ in 0..25 { | |
| if latest_link.title != source_title { | |
| log_executor_event( | |
| "friendly task title generation skipped", | |
| &[ | |
| ("local_task_id", local_task_id.clone()), | |
| ("reason", "title_changed_during_generation".to_owned()), | |
| ], | |
| ); | |
| return Ok(json!({"success": true, "skipped": true})); | |
| } | |
| if latest_link.thread_id.is_some() { | |
| break; | |
| } | |
| sleep(Duration::from_millis(200)).await; | |
| latest_link = self.task_link_from_payload(&payload, false).await?; | |
| } | |
| if latest_link.title != source_title { | |
| log_executor_event( | |
| "friendly task title generation skipped", | |
| &[ | |
| ("local_task_id", local_task_id.clone()), | |
| ("reason", "title_changed_before_update".to_owned()), | |
| ], | |
| ); | |
| return Ok(json!({"success": true, "skipped": true})); | |
| } | |
| if let Some(thread_id) = latest_link.thread_id.as_deref() { | |
| if let Err(error) = self | |
| .call_codex_thread_method( | |
| "thread/name/set", | |
| json!({"threadId": thread_id, "name": title}), | |
| ) | |
| .await | |
| { | |
| log_executor_event( | |
| "friendly task title generation failed", | |
| &[ | |
| ("local_task_id", local_task_id.clone()), | |
| ("reason", "thread_name_update_failed".to_owned()), | |
| ("error", error.clone()), | |
| ], | |
| ); | |
| return Ok(json!({"success": false, "error": error})); | |
| } | |
| } | |
| latest_link.title = title.clone(); | |
| latest_link.updated_at = now_ms(); | |
| self.upsert_local_task(latest_link); | |
| let mut latest_link = self.task_link_from_payload(&payload, false).await?; | |
| for _ in 0..25 { | |
| if latest_link.title != source_title { | |
| log_executor_event( | |
| "friendly task title generation skipped", | |
| &[ | |
| ("local_task_id", local_task_id.clone()), | |
| ("reason", "title_changed_during_generation".to_owned()), | |
| ], | |
| ); | |
| return Ok(json!({"success": true, "skipped": true})); | |
| } | |
| if latest_link.thread_id.is_some() { | |
| break; | |
| } | |
| sleep(Duration::from_millis(200)).await; | |
| latest_link = self.task_link_from_payload(&payload, false).await?; | |
| } | |
| if latest_link.title != source_title { | |
| log_executor_event( | |
| "friendly task title generation skipped", | |
| &[ | |
| ("local_task_id", local_task_id.clone()), | |
| ("reason", "title_changed_before_update".to_owned()), | |
| ], | |
| ); | |
| return Ok(json!({"success": true, "skipped": true})); | |
| } | |
| if let Some(thread_id) = latest_link.thread_id.as_deref() { | |
| if let Err(error) = self | |
| .call_codex_thread_method( | |
| "thread/name/set", | |
| json!({"threadId": thread_id, "name": title}), | |
| ) | |
| .await | |
| { | |
| log_executor_event( | |
| "friendly task title generation failed", | |
| &[ | |
| ("local_task_id", local_task_id.clone()), | |
| ("reason", "thread_name_update_failed".to_owned()), | |
| ("error", error.clone()), | |
| ], | |
| ); | |
| return Ok(json!({"success": false, "error": error})); | |
| } | |
| } | |
| let applied = self | |
| .store | |
| .update_task(&local_task_id, |link| { | |
| if link.title != source_title { | |
| return; | |
| } | |
| link.title = title.clone(); | |
| link.updated_at = now_ms(); | |
| }) | |
| .is_some(); | |
| if !applied { | |
| log_executor_event( | |
| "friendly task title generation skipped", | |
| &[ | |
| ("local_task_id", local_task_id.clone()), | |
| ("reason", "task_missing_before_update".to_owned()), | |
| ], | |
| ); | |
| return Ok(json!({"success": true, "skipped": 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/tasks.rs` around lines 83 - 132, Replace
the final latest_link mutation and upsert_local_task call in the friendly-title
generation flow with an in-place self.store.update_task mutation. Inside its
closure, re-read and verify the stored task title still equals source_title,
then update only title and updated_at; otherwise return the existing skipped
result. Preserve concurrent fields such as thread_id, runtime_handle mappings,
and completed_at, and propagate or map update errors consistently with the
surrounding handler.
| async updateTaskTrackingTitle(task: RuntimeTaskAddress, title: string) { | ||
| let binding: LocalTaskBindingRecord | ||
| try { | ||
| binding = await request<LocalTaskBindingRecord>('runtime_tasks.context', { | ||
| device_id: task.deviceId, | ||
| task_id: task.taskId, | ||
| }) | ||
| } catch { | ||
| return null | ||
| } |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Do not convert IPC failures into successful no-ops.
Line 584 returns null for every runtime_tasks.context failure. The provider treats that result as fulfilled and retains the title signature. A transient IPC or authorization failure then prevents retrying the same title.
Return null only for a confirmed missing binding. Propagate other failures so the provider clears its signature and retries on a later event.
As per coding guidelines, “correct the primary path” and do not hide defects behind fallbacks.
🤖 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/local/localDelivery.ts` around lines 577 - 586, Update
updateTaskTrackingTitle so the runtime_tasks.context request returns null only
when the failure confirms that the task binding is missing; propagate transient
IPC, authorization, and other unexpected errors instead of catching them as
successful no-ops. Preserve the existing missing-binding behavior while allowing
the provider to clear its signature and retry after non-missing failures.
Source: Coding guidelines
| const friendlyTitleExecutionRequest = normalizedData.friendlyTitle | ||
| ? buildLocalRuntimeExecutionRequest({ | ||
| taskId: `friendly-title-${normalizedData.taskId ?? turnSeed}-${createRuntimeTurnSeed()}`, | ||
| runtime: 'codex', | ||
| teamId: normalizedData.teamId, | ||
| title: 'Generate friendly task title', | ||
| message: [ | ||
| '为下面的用户请求生成一个简洁、具体、适合作为任务标题的中文标题。', | ||
| '只输出标题本身,不要引号、标点、解释或换行;最多 24 个汉字。', | ||
| '', | ||
| `用户请求:${normalizedData.message}`, | ||
| ].join('\n'), | ||
| turnSeed: createRuntimeTurnSeed(), | ||
| modelId: normalizedData.friendlyTitle.modelId, | ||
| modelType: normalizedData.friendlyTitle.modelType, | ||
| modelOptions: normalizedData.friendlyTitle.modelOptions, | ||
| cloudModelGateway, | ||
| localDeviceId, | ||
| workspacePath: runtimeWorkspace.workspacePath, | ||
| workspaceSource: runtimeWorkspace.workspaceSource, | ||
| branch: runtimeWorkspace.branch, | ||
| newSession: true, | ||
| ephemeral: true, | ||
| user, | ||
| }) |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
Disable tools for the friendly-title request.
Line 1607 inserts user-controlled text into a second model turn. buildLocalRuntimeExecutionRequest enables tools at Line 1457 and attaches the task workspace at Lines 1462-1470. A prompt-injected title request can therefore operate on the user workspace.
Create a text-only title request. Disable tools and omit workspace access for this request.
🤖 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/local/localServices.ts` around lines 1597 - 1621, Update the
friendlyTitleExecutionRequest construction in buildLocalRuntimeExecutionRequest
to create a text-only request: disable tool access and omit workspace-related
context such as workspacePath, workspaceSource, and branch. Preserve the
existing title prompt and model configuration while ensuring the user-controlled
message cannot operate on the task workspace.
| const selectedModel = workbench?.projectChat.models.find( | ||
| model => `${model.type}:${model.name}` === `${modelType ?? ''}:${modelName}` | ||
| ) | ||
| const execution = selectedModel ? selectedModelExecutionFields(selectedModel, {}) : null | ||
| const patch: AppPreferencesPatch = { | ||
| friendlyTaskTitlesEnabled: enabled && Boolean(selectedModel), | ||
| friendlyTaskTitleModel: selectedModel | ||
| ? { | ||
| modelName: selectedModel.name, | ||
| modelType: selectedModel.type, | ||
| executionModelId: execution?.modelId ?? '', | ||
| executionModelType: execution?.modelType ?? null, | ||
| options: execution?.modelOptions, | ||
| } | ||
| : null, | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Report a message when the stored title model no longer exists.
saveFriendlyTaskTitles resolves selectedModel from workbench?.projectChat.models. If the persisted friendlyTaskTitleModel refers to a model that is no longer listed, the patch writes friendlyTaskTitlesEnabled: false and friendlyTaskTitleModel: null. The switch then returns to the off position and the user receives no explanation. Set an error message in that case, or keep the selector open with the missing model shown as unavailable.
🛠️ Proposed fix
const execution = selectedModel ? selectedModelExecutionFields(selectedModel, {}) : null
+ if (enabled && !selectedModel) {
+ setError(t('workbench.friendly_task_titles_model_missing', '标题模型不可用,请重新选择模型。'))
+ }
const patch: AppPreferencesPatch = {📝 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.
| const selectedModel = workbench?.projectChat.models.find( | |
| model => `${model.type}:${model.name}` === `${modelType ?? ''}:${modelName}` | |
| ) | |
| const execution = selectedModel ? selectedModelExecutionFields(selectedModel, {}) : null | |
| const patch: AppPreferencesPatch = { | |
| friendlyTaskTitlesEnabled: enabled && Boolean(selectedModel), | |
| friendlyTaskTitleModel: selectedModel | |
| ? { | |
| modelName: selectedModel.name, | |
| modelType: selectedModel.type, | |
| executionModelId: execution?.modelId ?? '', | |
| executionModelType: execution?.modelType ?? null, | |
| options: execution?.modelOptions, | |
| } | |
| : null, | |
| } | |
| const selectedModel = workbench?.projectChat.models.find( | |
| model => `${model.type}:${model.name}` === `${modelType ?? ''}:${modelName}` | |
| ) | |
| const execution = selectedModel ? selectedModelExecutionFields(selectedModel, {}) : null | |
| if (enabled && !selectedModel) { | |
| setError(t('workbench.friendly_task_titles_model_missing', '标题模型不可用,请重新选择模型。')) | |
| } | |
| const patch: AppPreferencesPatch = { | |
| friendlyTaskTitlesEnabled: enabled && Boolean(selectedModel), | |
| friendlyTaskTitleModel: selectedModel | |
| ? { | |
| modelName: selectedModel.name, | |
| modelType: selectedModel.type, | |
| executionModelId: execution?.modelId ?? '', | |
| executionModelType: execution?.modelType ?? null, | |
| options: execution?.modelOptions, | |
| } | |
| : null, | |
| } |
🤖 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/GeneralSettingsPage.tsx` around lines 270 -
285, Update saveFriendlyTaskTitles around selectedModel resolution to detect
when the persisted friendlyTaskTitleModel does not match any
workbench.projectChat.models entry, and report that missing model to the user
instead of silently disabling the setting and clearing it. Preserve the existing
patch behavior for valid models, while using the component’s established
error-message or unavailable-model selector mechanism.
|
|
||
| const friendlyTitleModel = preferences.friendlyTaskTitleModel | ||
| const friendlyTitleModelKey = `${friendlyTitleModel?.modelType ?? ''}:${friendlyTitleModel?.modelName ?? ''}` | ||
| const friendlyTitleModels = workbench?.projectChat.models ?? [] |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Filter unusable models out of the title model list.
friendlyTitleModels passes every entry of workbench.projectChat.models to the selector. DesktopWorkbenchMain.tsx builds supervisorModels with model.isActive !== false && !model.compatibilityDisabled for the same catalog. Without that filter, the user can select an inactive or compatibility-disabled model, and every asynchronous title turn then fails at the executor.
♻️ Proposed fix
- const friendlyTitleModels = workbench?.projectChat.models ?? []
+ const friendlyTitleModels = (workbench?.projectChat.models ?? []).filter(
+ model => model.isActive !== false && !model.compatibilityDisabled
+ )Also applies to: 471-478
🤖 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/GeneralSettingsPage.tsx` at line 303, Update
the friendlyTitleModels assignment in GeneralSettingsPage to filter
projectChat.models using the same active and compatibility checks as
supervisorModels: retain only models where isActive is not false and
compatibilityDisabled is not true. Apply the identical filtering to the
additional friendly-title model list referenced by the same component.
| <select | ||
| data-testid="friendly-task-title-model-select" | ||
| value={friendlyTitleModelKey} | ||
| disabled={loading || saving} | ||
| onChange={event => { | ||
| const [modelType, ...nameParts] = event.target.value.split(':') | ||
| const modelName = nameParts.join(':') | ||
| void saveFriendlyTaskTitles( | ||
| preferences.friendlyTaskTitlesEnabled && Boolean(modelName), | ||
| modelName, | ||
| modelName ? (modelType as UnifiedModel['type']) : null | ||
| ) | ||
| }} | ||
| className="h-8 w-full rounded-md border border-border bg-background px-2 text-sm text-text-primary md:w-[220px]" |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Raise the select height on mobile to at least 44px.
The <select> uses h-8 (32px). On mobile the row switches to max-sm:flex-col max-sm:items-stretch, so this control becomes the primary touch target at 32px height. The popout shortcut button in this same file applies max-md:min-h-11 for this reason.
🛠️ Proposed fix
- className="h-8 w-full rounded-md border border-border bg-background px-2 text-sm text-text-primary md:w-[220px]"
+ className="h-8 w-full rounded-md border border-border bg-background px-2 text-sm text-text-primary max-md:h-11 md:w-[220px]"As per coding guidelines: "Mobile controls must be at least 44px × 44px."
📝 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.
| <select | |
| data-testid="friendly-task-title-model-select" | |
| value={friendlyTitleModelKey} | |
| disabled={loading || saving} | |
| onChange={event => { | |
| const [modelType, ...nameParts] = event.target.value.split(':') | |
| const modelName = nameParts.join(':') | |
| void saveFriendlyTaskTitles( | |
| preferences.friendlyTaskTitlesEnabled && Boolean(modelName), | |
| modelName, | |
| modelName ? (modelType as UnifiedModel['type']) : null | |
| ) | |
| }} | |
| className="h-8 w-full rounded-md border border-border bg-background px-2 text-sm text-text-primary md:w-[220px]" | |
| <select | |
| data-testid="friendly-task-title-model-select" | |
| value={friendlyTitleModelKey} | |
| disabled={loading || saving} | |
| onChange={event => { | |
| const [modelType, ...nameParts] = event.target.value.split(':') | |
| const modelName = nameParts.join(':') | |
| void saveFriendlyTaskTitles( | |
| preferences.friendlyTaskTitlesEnabled && Boolean(modelName), | |
| modelName, | |
| modelName ? (modelType as UnifiedModel['type']) : null | |
| ) | |
| }} | |
| className="h-8 w-full rounded-md border border-border bg-background px-2 text-sm text-text-primary max-md:h-11 md:w-[220px]" |
🤖 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/GeneralSettingsPage.tsx` around lines 453 -
466, Update the select element in the friendly task title model control to
enforce a minimum 44px height on mobile, while preserving its existing desktop
sizing and other classes. Use the responsive min-height utility pattern already
applied to the nearby popout shortcut button.
Source: Coding guidelines
What changed
Why
Users should not have to manually rename every task, but title generation must not block task creation, create a visible conversation, or incorrectly show a completed task as running.
The status regression was caused by title-update events triggering full runtime-work refreshes while the main turn was still settling. The title event now applies a targeted title-only state update instead.
Validation
cargo fmt --check,cargo test --all-features --lib, and ClippySummary by CodeRabbit
New Features
Documentation