Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions docs/en/wegent/user-guide/coding/managing-code-tasks.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,15 @@ Click the model selector to override the agent's default model:

When you switch to a different model in an existing Wework conversation, Wework asks for confirmation first. Different models may interpret existing context differently and may vary in tool support, response style, and task continuity. After confirmation, the new model is used for the next message; a response already in progress continues with the previous model. No warning is shown when selecting a model for a new conversation or reselecting the model that is already chosen.

#### Friendly Titles

You can enable **Use friendly titles** under **Settings > General > Runtime**. It is off by default, and enabling it requires a title-generation model:

- **Same as task**: The default option. Each new task uses the model actually selected for that task to generate its title.
- **A specific model**: Used only to generate the title asynchronously; it does not change the model used by the task itself.

Title generation never blocks task submission. If a selected title model is no longer available, Wework skips title generation and still creates the task normally.

#### Knowledge Base Context

Click the context button to add knowledge bases:
Expand Down
9 changes: 9 additions & 0 deletions docs/zh/wegent/user-guide/coding/managing-code-tasks.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,15 @@ sidebar_position: 2

在 Wework 的已有对话中切换到不同模型时,系统会先提示确认。不同模型对已有上下文的理解、工具支持、回复风格和任务连续性可能存在差异。确认后,新模型从下一条消息开始使用;如果当前回复仍在进行,它会继续使用原模型。新对话首次选择模型,或重复选择已经选中的模型时,不会显示该提示。

#### 友好标题

在 **设置 > 通用 > 运行** 中可以打开“使用友好标题”。该功能默认关闭;打开时必须指定标题生成模型:

- **与任务相同**:默认选项。每次创建任务时,使用该任务实际选择的模型生成标题。
- **指定模型**:只用于异步生成标题,不会改变任务本身使用的模型。

标题生成不会阻止任务发送。若指定的标题模型已经不可用,Wework 会跳过标题生成,任务仍会正常创建。

#### 知识库上下文

点击上下文按钮添加知识库:
Expand Down
117 changes: 77 additions & 40 deletions wework/e2e/desktop/task-flow.e2e.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -238,38 +238,38 @@ const DEFAULT_MODEL_LABEL = 'GPT 5.6 Luna'
const LOCAL_MODEL_CASES = [
{
protocol: 'responses',
optionId: 'local-model:desktop-e2e-responses',
label: 'Desktop E2E Responses',
optionIds: ['wework-custom-desktop-e2e-responses', 'local-model:desktop-e2e-responses'],
labels: ['wework-custom-desktop-e2e-responses', 'Desktop E2E Responses'],
modelId: 'desktop-e2e-responses-model',
},
{
protocol: 'chat',
optionId: 'local-model:desktop-e2e-chat',
label: 'Desktop E2E Chat',
optionIds: ['wework-custom-desktop-e2e-chat', 'local-model:desktop-e2e-chat'],
labels: ['wework-custom-desktop-e2e-chat', 'Desktop E2E Chat'],
modelId: 'desktop-e2e-chat-model',
},
{
protocol: 'anthropic',
optionId: 'local-model:desktop-e2e-anthropic',
label: 'Desktop E2E Anthropic',
optionIds: ['wework-custom-desktop-e2e-anthropic', 'local-model:desktop-e2e-anthropic'],
labels: ['wework-custom-desktop-e2e-anthropic', 'Desktop E2E Anthropic'],
modelId: 'desktop-e2e-anthropic-model',
},
]
const MODEL_PROTOCOLS = ['responses', 'chat', 'anthropic']
const CLOUD_MODEL_CASES = MODEL_PROTOCOLS.map(protocol => ({
source: 'cloud',
protocol,
optionId: `desktop-e2e-cloud-${protocol}`,
label: protocol === 'chat' ? 'moonshot-kimi-k3' : `desktop-e2e-cloud-${protocol}`,
optionIds: [`desktop-e2e-cloud-${protocol}`],
labels: [protocol === 'chat' ? 'moonshot-kimi-k3' : `desktop-e2e-cloud-${protocol}`],
modelId: protocol === 'chat' ? 'moonshot-kimi-k3' : `desktop-e2e-cloud-${protocol}-upstream`,
}))
const MODEL_PROTOCOL_MATRIX_CASES = [
...LOCAL_MODEL_CASES.map(model => ({ ...model, source: 'local' })),
...MODEL_PROTOCOLS.map(protocol => ({
source: 'codex',
protocol,
optionId: DEFAULT_MODEL_ID,
label: DEFAULT_MODEL_LABEL,
optionIds: [DEFAULT_MODEL_ID],
labels: [DEFAULT_MODEL_LABEL],
modelId: DEFAULT_MODEL_ID,
})),
...CLOUD_MODEL_CASES,
Expand Down Expand Up @@ -311,8 +311,14 @@ const LOCAL_MODEL_SWITCH_COMPLETE = 'WEWORK_LOCAL_MODEL_SWITCH_COMPLETE'
const LOCAL_MODEL_SWITCH_INVALID_CALL_ID = 'functions.exec_command:0'
const LOCAL_MODEL_SWITCH_ARTIFACT = 'wework-model-switch-protocol.txt'
const LOCAL_MODEL_SWITCH_ARTIFACT_CONTENT = 'WEWORK_MODEL_SWITCH_PROTOCOL_EXEC_COMMAND'
const PROVIDER_SWITCH_LUNA_OPTION_ID = 'local-model:desktop-e2e-luna-overseas'
const PROVIDER_SWITCH_LUNA_LABEL = 'GPT 5.6 Luna (海外)'
const PROVIDER_SWITCH_LUNA_OPTION_IDS = [
'wework-custom-desktop-e2e-luna-overseas',
'local-model:desktop-e2e-luna-overseas',
]
const PROVIDER_SWITCH_LUNA_LABELS = [
'wework-custom-desktop-e2e-luna-overseas',
'GPT 5.6 Luna (海外)',
]
const PROVIDER_SWITCH_LUNA_MODEL_ID = 'gpt-5.6-luna'
// The local E2E Codex catalog is classified as third-party (custom provider), so
// the official option is served from the cloud model catalog with a canonical
Expand Down Expand Up @@ -3308,6 +3314,10 @@ async function verifyExpandedToolDetail(
}

async function ensureToggleExpanded(control, selector) {
await control.command('waitFor', selector, {
visible: true,
timeoutMs: DEFAULT_STEP_TIMEOUT_MS,
})
const expandedCount = Number(
await control.command('getElementCount', `${selector}[aria-expanded="true"]`)
)
Expand Down Expand Up @@ -5548,7 +5558,10 @@ async function verifyAutomationLifecycle(control, workspacePath) {
),
'The existing-task selector did not list the pinned local task'
)
await control.command('click', '[data-testid="automation-target-task-select"]')
await control.command(
'click',
`[data-testid="automation-target-task-select-option-local-device:${manualTaskId}"]`
)
await control.command('click', '[data-testid="automation-repeat-menu"]')
await control.command('click', '[data-testid="automation-repeat-menu-option-one_time"]')
const scheduledFor = new Date(Date.now() + 5_000)
Expand Down Expand Up @@ -5999,10 +6012,21 @@ async function revealGroupedModelOption(control, targetOptionId) {
return false
}

async function ensureModelOptionVisible(control, targetOptionId) {
function modelOptionIdCandidates(modelIds) {
return (Array.isArray(modelIds) ? modelIds : [modelIds]).map(modelId =>
modelId.startsWith('model-option-') ? modelId : `model-option-${modelId}`
)
}

function hasModelOption(menu, targetOptionIds) {
return targetOptionIds.some(targetOptionId => menu.testIds.includes(targetOptionId))
}

async function ensureModelOptionVisible(control, modelIds) {
const targetOptionIds = modelOptionIdCandidates(modelIds)
for (let attempt = 0; attempt < 8; attempt += 1) {
let menu = JSON.parse(await control.command('snapshot', 'body'))
if (menu.testIds.includes(targetOptionId)) return menu
if (hasModelOption(menu, targetOptionIds)) return menu
if (menu.testIds.includes('model-control-menu-model')) {
await control
.command('hover', '[data-testid="model-control-menu-model"]', {
Expand All @@ -6025,13 +6049,15 @@ async function ensureModelOptionVisible(control, targetOptionId) {
}
await new Promise(resolvePromise => setTimeout(resolvePromise, 150))
menu = JSON.parse(await control.command('snapshot', 'body'))
if (menu.testIds.includes(targetOptionId)) return menu
if (await revealGroupedModelOption(control, targetOptionId)) {
return JSON.parse(await control.command('snapshot', 'body'))
if (hasModelOption(menu, targetOptionIds)) return menu
for (const targetOptionId of targetOptionIds) {
if (await revealGroupedModelOption(control, targetOptionId)) {
return JSON.parse(await control.command('snapshot', 'body'))
}
}
}

throw new Error(`Model option ${targetOptionId} did not become visible`)
throw new Error(`Model options ${targetOptionIds.join(', ')} did not become visible`)
}

async function confirmLocalProjectName(control, name) {
Expand Down Expand Up @@ -6072,24 +6098,28 @@ async function createSingleRootLocalProject(control, workspacePath, name) {

async function selectE2EModel(
control,
modelId = DEFAULT_MODEL_ID,
modelLabel = DEFAULT_MODEL_LABEL
modelIds = DEFAULT_MODEL_ID,
modelLabels = DEFAULT_MODEL_LABEL
) {
const labels = Array.isArray(modelLabels) ? modelLabels : [modelLabels]
await control.command('waitFor', '[data-testid="model-selector-button"]', {
timeoutMs: WORKBENCH_READY_TIMEOUT_MS,
})
const selectedModelLabel = await control.command(
'getText',
'[data-testid="model-selector-button"]'
)
if (selectedModelLabel.includes(modelLabel)) return
if (labels.some(label => selectedModelLabel.includes(label))) return

const targetOptionId = `model-option-${modelId}`
await ensureModelOptionVisible(control, targetOptionId)
await control.command('waitFor', `[data-testid="model-option-${modelId}"]`, {
const selectionMenu = await ensureModelOptionVisible(control, modelIds)
const targetOptionId = modelOptionIdCandidates(modelIds).find(optionId =>
selectionMenu.testIds.includes(optionId)
)
assert.ok(targetOptionId, `No visible model option matched ${modelOptionIdCandidates(modelIds)}`)
await control.command('waitFor', `[data-testid="${targetOptionId}"]`, {
timeoutMs: DEFAULT_STEP_TIMEOUT_MS,
})
await control.command('click', `[data-testid="model-option-${modelId}"]`)
await control.command('click', `[data-testid="${targetOptionId}"]`)
const selectionSnapshot = JSON.parse(await control.command('snapshot', 'body'))
if (selectionSnapshot.testIds.includes('model-switch-warning-dialog')) {
await control.command(
Expand All @@ -6100,10 +6130,7 @@ async function selectE2EModel(
}
)
}
await control.command('waitFor', '[data-testid="model-selector-button"]', {
text: modelLabel,
timeoutMs: DEFAULT_STEP_TIMEOUT_MS,
})
await waitForE2EModelLabel(control, labels)
await control.command('press', 'body', { key: 'Escape' })
await waitForSnapshot(
control,
Expand All @@ -6112,13 +6139,26 @@ async function selectE2EModel(
)
}

async function waitForE2EModelLabel(control, labels) {
const startedAt = Date.now()
while (Date.now() - startedAt < DEFAULT_STEP_TIMEOUT_MS) {
const selectedModelLabel = await control.command(
'getText',
'[data-testid="model-selector-button"]'
)
if (labels.some(label => selectedModelLabel.includes(label))) return
await new Promise(resolvePromise => setTimeout(resolvePromise, 100))
}
throw new Error(`Model selector did not display one of: ${labels.join(', ')}`)
}

async function verifyCrossProviderSwitchRetry(control, composerSelector) {
control.setScenario('provider_switch_retry')
await control.command('click', '[data-testid="new-chat-button"]')
await control.command('waitFor', composerSelector, {
timeoutMs: WORKBENCH_READY_TIMEOUT_MS,
})
await selectE2EModel(control, PROVIDER_SWITCH_LUNA_OPTION_ID, PROVIDER_SWITCH_LUNA_LABEL)
await selectE2EModel(control, PROVIDER_SWITCH_LUNA_OPTION_IDS, PROVIDER_SWITCH_LUNA_LABELS)
await sendPrompt(control, composerSelector, PROVIDER_SWITCH_PROMPT)
await control.command('waitFor', ACTIVE_SWITCH_MODEL_RETRY_SELECTOR, {
visible: true,
Expand Down Expand Up @@ -7278,7 +7318,7 @@ async function verifyAnthropicEmptyResponseRecovery({ composerSelector, control
await control.command('waitFor', composerSelector, {
timeoutMs: WORKBENCH_READY_TIMEOUT_MS,
})
await selectE2EModel(control, anthropicModel.optionId, anthropicModel.label)
await selectE2EModel(control, anthropicModel.optionIds, anthropicModel.labels)
await sendPromptUntilScenarioRequest(
control,
composerSelector,
Expand Down Expand Up @@ -8415,7 +8455,7 @@ class RealCloudEnvironment {

async seedCloudProtocolModels() {
const items = CLOUD_MODEL_CASES.map(model => ({
name: model.optionId,
name: model.optionIds[0],
env: {
model: model.protocol === 'anthropic' ? 'claude' : 'openai',
model_id: model.modelId,
Expand Down Expand Up @@ -13167,7 +13207,7 @@ async function verifyModelProtocolMatrix({
stableMs: COMPOSER_READY_STABILITY_MS,
timeoutMs: WORKBENCH_READY_TIMEOUT_MS,
})
await selectE2EModel(control, model.optionId, model.label)
await selectE2EModel(control, model.optionIds, model.labels)

const confirmCloudModelCatalogSync =
!hasConfirmedCatalogSync && model.execution === 'cloud' && model.source === 'local'
Expand Down Expand Up @@ -14662,7 +14702,7 @@ last_updated = "2026-07-30T00:00:00Z"`
await control.command('waitFor', composerSelector, {
timeoutMs: WORKBENCH_READY_TIMEOUT_MS,
})
await selectE2EModel(control, sourceModel.optionId, sourceModel.label)
await selectE2EModel(control, sourceModel.optionIds, sourceModel.labels)
await sendPrompt(control, composerSelector, LOCAL_MODEL_SWITCH_INITIAL_PROMPT)
await control.command('waitFor', '[data-testid="message-assistant"]', {
text: LOCAL_MODEL_SWITCH_INITIAL_COMPLETE,
Expand Down Expand Up @@ -14698,7 +14738,7 @@ last_updated = "2026-07-30T00:00:00Z"`
'[data-testid="model-selector-menu"]'
)
}
await selectE2EModel(control, targetModel.optionId, targetModel.label)
await selectE2EModel(control, targetModel.optionIds, targetModel.labels)
if (switchIndex === 0) {
await captureVerificationScreenshot(
control,
Expand Down Expand Up @@ -14726,10 +14766,7 @@ last_updated = "2026-07-30T00:00:00Z"`
'model_switch_target_complete',
`${switchCase.id} did not complete the automatic same-conversation retry`
)
await control.command('waitFor', '[data-testid="model-selector-button"]', {
text: targetModel.label,
timeoutMs: DEFAULT_STEP_TIMEOUT_MS,
})
await waitForE2EModelLabel(control, targetModel.labels)
await waitForSnapshot(
control,
snapshot => !/下一轮|Next/.test(snapshot.text),
Expand Down
20 changes: 10 additions & 10 deletions wework/src/api/local/localServices.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1105,15 +1105,15 @@ describe('createLocalAppServices', () => {
})

test('uses local model settings for create and continue execution requests', async () => {
saveLocalModelConfig({
const ollama = saveLocalModelConfig({
id: 'ollama',
displayName: 'Ollama GPT',
modelId: 'gpt-oss:20b',
baseUrl: 'http://localhost:11434/v1',
contextWindow: 128000,
catalogReady: true,
})
saveLocalModelConfig({
const lmstudio = saveLocalModelConfig({
id: 'lmstudio',
displayName: 'LM Studio',
modelId: 'qwen3-coder',
Expand All @@ -1123,7 +1123,7 @@ describe('createLocalAppServices', () => {
imageGenerationEnabled: true,
catalogReady: true,
})
saveLocalModelConfig({
const custom = saveLocalModelConfig({
id: 'custom',
displayName: 'Custom Gateway',
modelId: 'custom-model',
Expand All @@ -1146,7 +1146,7 @@ describe('createLocalAppServices', () => {
runtime: 'codex',
message: 'hello',
title: 'Hello',
modelId: 'local-model:ollama',
modelId: ollama.codexCatalogModelId,
})
await services.runtimeWorkApi?.sendRuntimeMessage({
address: {
Expand All @@ -1155,7 +1155,7 @@ describe('createLocalAppServices', () => {
taskId: 'task-1',
},
message: 'continue',
modelId: 'local-model:ollama',
modelId: ollama.codexCatalogModelId,
})
await services.runtimeWorkApi?.sendRuntimeMessage({
address: {
Expand All @@ -1164,7 +1164,7 @@ describe('createLocalAppServices', () => {
taskId: 'task-1',
},
message: 'secure continue',
modelId: 'local-model:lmstudio',
modelId: lmstudio.codexCatalogModelId,
})
await services.runtimeWorkApi?.sendRuntimeMessage({
address: {
Expand All @@ -1173,7 +1173,7 @@ describe('createLocalAppServices', () => {
taskId: 'task-1',
},
message: 'custom continue',
modelId: 'local-model:custom',
modelId: custom.codexCatalogModelId,
})

const createPayload = request.mock.calls.find(
Expand All @@ -1189,9 +1189,9 @@ describe('createLocalAppServices', () => {

expect(continueModelConfig).toEqual(createModelConfig)
expect(sendPayloads.map(payload => payload.modelSelection)).toEqual([
{ modelName: 'local-model:ollama', modelType: null, options: {} },
{ modelName: 'local-model:lmstudio', modelType: null, options: {} },
{ modelName: 'local-model:custom', modelType: null, options: {} },
{ modelName: ollama.codexCatalogModelId, modelType: null, options: {} },
{ modelName: lmstudio.codexCatalogModelId, modelType: null, options: {} },
{ modelName: custom.codexCatalogModelId, modelType: null, options: {} },
])
expect(createModelConfig).toEqual(
expect.objectContaining({
Expand Down
5 changes: 3 additions & 2 deletions wework/src/components/layout/useWorkbenchPaneSession.ts
Original file line number Diff line number Diff line change
Expand Up @@ -469,17 +469,18 @@ export function useWorkbenchPaneSession({ currentRuntimeTask }: WorkbenchPaneSes
.then(response => {
if (!cancelled) {
const loadedGoal = response.accepted ? response.goal : null
const resolvedGoal = loadedGoal ?? seededGoal?.goal ?? null
if (import.meta.env.VITE_WEWORK_RUNTIME_DEBUG === '1') {
console.info('[Wework] Runtime goal hydration resolved', {
address: runtimeAddressDebug(runtimeTaskLoadTarget.address),
accepted: response.accepted,
goalStatus: loadedGoal?.status ?? null,
})
Comment on lines 473 to 478

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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

Log the resolved goal status.

When the API returns no goal and the seed supplies the fallback, Line 477 logs null even though Lines 480-483 store and report the seeded status. Change loadedGoal?.status to resolvedGoal?.status.

Proposed fix
-              goalStatus: loadedGoal?.status ?? null,
+              goalStatus: resolvedGoal?.status ?? null,
📝 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 (import.meta.env.VITE_WEWORK_RUNTIME_DEBUG === '1') {
console.info('[Wework] Runtime goal hydration resolved', {
address: runtimeAddressDebug(runtimeTaskLoadTarget.address),
accepted: response.accepted,
goalStatus: loadedGoal?.status ?? null,
})
if (import.meta.env.VITE_WEWORK_RUNTIME_DEBUG === '1') {
console.info('[Wework] Runtime goal hydration resolved', {
address: runtimeAddressDebug(runtimeTaskLoadTarget.address),
accepted: response.accepted,
goalStatus: resolvedGoal?.status ?? 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/layout/useWorkbenchPaneSession.ts` around lines 473 -
478, Update the Runtime goal hydration debug log in the resolved hydration flow
to use resolvedGoal?.status instead of loadedGoal?.status, so the logged goal
status matches the fallback-seeded value stored and reported by the subsequent
logic.

}
setRuntimeConversationGoal(runtimeTaskLoadTarget.address, loadedGoal)
setRuntimeConversationGoal(runtimeTaskLoadTarget.address, resolvedGoal)
Comment on lines +472 to +480

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C 6 \
  'getRuntimeGoal|setRuntimeConversationGoal|clearRuntimeGoal|clearCurrentGoal|updateCurrentGoalStatus|clearRuntimePaneGoalSeed' \
  wework/src/components/layout/useWorkbenchPaneSession.ts

Repository: wecode-ai/Wegent

Length of output: 9206


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== outline around hook state/mutations =="
ast-grep outline wework/src/components/layout/useWorkbenchPaneSession.ts \
  --match useWorkbenchPaneSession \
  --view expanded || true

echo "== goal/state declarations and imports =="
sed -n '1,145p' wework/src/components/layout/useWorkbenchPaneSession.ts
sed -n '340,512p' wework/src/components/layout/useWorkbenchPaneSession.ts
sed -n '2035,2160p' wework/src/components/layout/useWorkbenchPaneSession.ts
sed -n '2330,2365p' wework/src/components/layout/useWorkbenchPaneSession.ts

Repository: wecode-ai/Wegent

Length of output: 15799


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== runtimeConversationCache definitions for cache/lifecycle APIs =="
rg -n -C 8 'export .*setRuntimeConversationGoal|export .*updateRuntimeConversation|goalStatusReceived|clearRuntimeGoal|setRuntimeGoal|getRuntimeGoal|setRuntimeGoal|RuntimeConversationGoal|GoalStatus' wework/src/features/workbench wework/src  \
  -g '*runtimeConversationCache*' -g '*.ts' -g '*.tsx'

echo "== candidate cache file list =="
fd -i 'runtimeConversation|conversationCache|goal' wework/src | sed -n '1,80p'

Repository: wecode-ai/Wegent

Length of output: 50373


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== runtimeConversationCache main exports =="
fd -i 'runtimeConversationCache' wework/src
CACHE="$(fd -i 'runtimeConversationCache' wework/src | head -n 1)"
wc -l "$CACHE"
sed -n '1,240p' "$CACHE"

Repository: wecode-ai/Wegent

Length of output: 7405


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== local cache/store usages for currentRuntimeTask =="
rg -n -C 4 'setRuntimeConversationGoal|lifecycleStore\.goalStatusReceived|clearRuntimePaneGoalSeed|pendingRuntimeGoalState|getRuntimeConversationMetadata|setRuntimeConversationTaskPlan' wework/src/features/workbench/useWorkbenchRuntimeTasks.ts wework/src/features/workbench/runtimeTaskLifecycle wework/src/features/workbench/runtimeConversationCache* -g '*ts'

Repository: wecode-ai/Wegent

Length of output: 8087


Prevent stale hydrated goals from overriding later mutations.

clearCurrentGoal and updateCurrentGoalStatus can write null or a newer status while getRuntimeGoal is pending. If that deferred response then returns goal: null, resolvedGoal falls back to the earlier seededGoal and overwrites the cache/lifecycle store with old state. Track a goal-mutation generation or ignore stale hydration results before applying them. Add a regression test for clear or status change before the deferred request resolves.

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

In `@wework/src/components/layout/useWorkbenchPaneSession.ts` around lines 472 -
480, Prevent the async hydration block around getRuntimeGoal and
setRuntimeConversationGoal from applying stale results after clearCurrentGoal or
updateCurrentGoalStatus mutates the goal. Track a per-goal mutation generation
(or equivalent request validity guard), verify it before resolving and storing
loadedGoal/seededGoal, and ignore responses from earlier generations; add a
regression test covering a clear or status update before the deferred request
resolves.

lifecycleStore.goalStatusReceived(
runtimeTaskLoadTarget.address,
loadedGoal?.status ?? seededGoal?.goal.status ?? null
resolvedGoal?.status ?? null
)
if (loadedGoal?.status === 'active') {
void refreshWorkListsRef.current().catch(() => undefined)
Expand Down
Loading
Loading