diff --git a/tools/hwlab-cli/client.test.ts b/tools/hwlab-cli/client.test.ts
index f5da56b0..9770ea87 100644
--- a/tools/hwlab-cli/client.test.ts
+++ b/tools/hwlab-cli/client.test.ts
@@ -1262,6 +1262,44 @@ test("hwlab-cli agent composer status detects Web unlocked steer mode", async ()
assert.equal(result.payload.route.path, "/v1/agent/chat/steer");
});
+test("hwlab-cli agent composer status ignores stale workspace activeTrace after selected conversation completed", async () => {
+ const result = await runHwlabCli([
+ "client",
+ "agent",
+ "composer",
+ "status",
+ "--base-url",
+ "http://web.test",
+ "--cookie",
+ "hwlab_session=session-a"
+ ], {
+ fetchImpl: async () => new Response(JSON.stringify({
+ ok: true,
+ workspace: {
+ workspaceId: "wsp_composer",
+ revision: 10,
+ selectedConversationId: "cnv_completed",
+ selectedAgentSessionId: "ses_completed",
+ activeTraceId: "trc_stale_running",
+ workspace: {
+ activeTraceId: "trc_stale_running",
+ threadId: "thread-composer",
+ sessionStatus: "running",
+ messages: [{ role: "agent", status: "completed", traceId: "trc_completed", conversationId: "cnv_completed" }]
+ }
+ }
+ }), { status: 200 })
+ });
+
+ assert.equal(result.exitCode, 0);
+ assert.equal(result.payload.composer.locked, false);
+ assert.equal(result.payload.composer.disabled, false);
+ assert.equal(result.payload.composer.submitMode, "turn");
+ assert.equal(result.payload.composer.route, "/v1/agent/chat");
+ assert.equal(result.payload.composer.targetTraceId, null);
+ assert.equal(result.payload.composer.reason, "terminal-turn");
+});
+
test("hwlab-cli agent composer submit auto routes active Web turn to steer", async () => {
const calls: any[] = [];
const result = await runHwlabCli([
diff --git a/tools/src/hwlab-cli/composer-policy.ts b/tools/src/hwlab-cli/composer-policy.ts
index 25f5a194..8744f011 100644
--- a/tools/src/hwlab-cli/composer-policy.ts
+++ b/tools/src/hwlab-cli/composer-policy.ts
@@ -8,6 +8,8 @@ export function computeCodeAgentComposerState(input = {}) {
const current = input.currentRequest && typeof input.currentRequest === "object" ? input.currentRequest : null;
const workspace = input.workspace && typeof input.workspace === "object" ? input.workspace : null;
const explicitStatus = normalizedStatus(input.status ?? input.sessionStatus ?? workspace?.sessionStatus);
+ const latestStatus = normalizedStatus(latest?.status);
+ const effectiveStatus = latestStatus || explicitStatus;
const traceId = firstNonEmptyString(
input.targetTraceId,
current?.traceId,
@@ -15,7 +17,7 @@ export function computeCodeAgentComposerState(input = {}) {
workspace?.activeTraceId,
latest?.status === "running" ? latest?.traceId : null
);
- const active = Boolean(traceId) && (
+ const active = Boolean(traceId) && !terminalStatus(effectiveStatus) && (
activeStatus(current?.status) ||
activeStatus(latest?.status) ||
activeStatus(explicitStatus) ||
@@ -51,7 +53,7 @@ export function computeCodeAgentComposerState(input = {}) {
threadId: firstNonEmptyString(input.threadId, current?.threadId, latest?.threadId, workspace?.threadId),
workspaceId: firstNonEmptyString(input.workspaceId, workspace?.workspaceId),
workspaceRevision: integerOrNull(input.workspaceRevision ?? workspace?.revision),
- reason: active ? "active-turn-steer" : disabledReason ?? (terminalStatus(explicitStatus) ? "terminal-turn" : "idle-turn")
+ reason: active ? "active-turn-steer" : disabledReason ?? (terminalStatus(effectiveStatus) ? "terminal-turn" : "idle-turn")
});
}
diff --git a/web/hwlab-cloud-web/src/App.tsx b/web/hwlab-cloud-web/src/App.tsx
index 0e1ae2c4..f7c7587a 100644
--- a/web/hwlab-cloud-web/src/App.tsx
+++ b/web/hwlab-cloud-web/src/App.tsx
@@ -139,7 +139,7 @@ export function App(): ReactElement {
{route === "help" ? : null}
{route === "settings" ? : null}
-
+
{
+ const source = fs.readFileSync(sourcePath, "utf8");
+ assert.match(source, /const submitted = props\.onSubmit\(next\);/u);
+ assert.match(source, /window\.setTimeout\(\(\) => setSubmitting\(false\), 0\);/u);
+ assert.doesNotMatch(source, /await props\.onSubmit\(next\)/u);
+ assert.doesNotMatch(source, /\.finally\(\(\) => setSubmitting\(false\)\)/u);
+});
diff --git a/web/hwlab-cloud-web/src/components/command-bar/CommandBar.tsx b/web/hwlab-cloud-web/src/components/command-bar/CommandBar.tsx
index 1b8becd0..10115f20 100644
--- a/web/hwlab-cloud-web/src/components/command-bar/CommandBar.tsx
+++ b/web/hwlab-cloud-web/src/components/command-bar/CommandBar.tsx
@@ -5,6 +5,8 @@ import type { ProviderProfile } from "../../types/domain";
interface CommandBarProps {
disabled: boolean;
+ submitMode: "turn" | "steer";
+ targetTraceId: string | null;
providerProfile: ProviderProfile;
codeAgentTimeoutMs: number;
gatewayShellTimeoutMs: number;
@@ -29,7 +31,7 @@ interface CommandBarProps {
export function CommandBar({ pickedDraft, onPickedDraftConsumed, disabledReason, onTyping, ...props }: CommandBarProps): ReactElement {
const [value, setValue] = useState("");
- const [pending, setPending] = useState(false);
+ const [submitting, setSubmitting] = useState(false);
useEffect(() => {
if (pickedDraft) {
@@ -41,14 +43,12 @@ export function CommandBar({ pickedDraft, onPickedDraftConsumed, disabledReason,
async function submit(event: FormEvent): Promise {
event.preventDefault();
const next = value.trim();
- if (!next || props.disabled || pending) return;
- setPending(true);
- try {
- await props.onSubmit(next);
- setValue("");
- } finally {
- setPending(false);
- }
+ if (!next || props.disabled || submitting) return;
+ setValue("");
+ setSubmitting(true);
+ const submitted = props.onSubmit(next);
+ window.setTimeout(() => setSubmitting(false), 0);
+ void submitted.catch(() => setSubmitting(false));
}
function keyDown(event: KeyboardEvent): void {
@@ -58,7 +58,7 @@ export function CommandBar({ pickedDraft, onPickedDraftConsumed, disabledReason,
}
return (
-
-
+
);
}
+
+function sendLabel(submitMode: "turn" | "steer", submitting: boolean): string {
+ if (submitting) return submitMode === "steer" ? "引导中" : "发送中";
+ return submitMode === "steer" ? "引导" : "发送";
+}
+
+function steerTitle(submitMode: "turn" | "steer", targetTraceId: string | null): string | undefined {
+ if (submitMode !== "steer" || !targetTraceId) return undefined;
+ return `引导运行中的 turn: ${targetTraceId}`;
+}
diff --git a/web/hwlab-cloud-web/src/state/workbench-reducer.test.ts b/web/hwlab-cloud-web/src/state/workbench-reducer.test.ts
new file mode 100644
index 00000000..870dfd75
--- /dev/null
+++ b/web/hwlab-cloud-web/src/state/workbench-reducer.test.ts
@@ -0,0 +1,152 @@
+import assert from "node:assert/strict";
+import { test } from "bun:test";
+
+import { composerFromState, workbenchReducer } from "./workbench-reducer";
+import type { ChatMessage } from "../types/domain";
+import type { WorkbenchState } from "./workbench-state";
+
+test("composer stays unlocked and routes running turn input to steer", () => {
+ const state = baseState({
+ workspace: workspace("cnv_active", "trc_active"),
+ messages: [agentMessage({ conversationId: "cnv_active", traceId: "trc_active", status: "running" })],
+ chatPending: true
+ });
+
+ const composer = composerFromState(state, "cnv_active");
+
+ assert.equal(composer.disabled, false);
+ assert.equal(composer.submitMode, "steer");
+ assert.equal(composer.route, "/v1/agent/chat/steer");
+ assert.equal(composer.targetTraceId, "trc_active");
+ assert.equal(composer.conversationId, "cnv_active");
+});
+
+test("composer does not carry a running trace into another session", () => {
+ const state = baseState({
+ workspace: workspace("cnv_other", "trc_active"),
+ currentRequest: { traceId: "trc_active", conversationId: "cnv_active", sessionId: "ses_active", threadId: "thread_active", status: "running" },
+ messages: [agentMessage({ conversationId: "cnv_other", traceId: "trc_other_done", status: "completed" })],
+ chatPending: true
+ });
+
+ const composer = composerFromState(state, "cnv_other");
+
+ assert.equal(composer.disabled, false);
+ assert.equal(composer.submitMode, "turn");
+ assert.equal(composer.targetTraceId, null);
+});
+
+test("conversation selection scopes chatPending to the selected session messages", () => {
+ const state = baseState({
+ workspace: workspace("cnv_active", "trc_active"),
+ currentRequest: { traceId: "trc_active", conversationId: "cnv_active", sessionId: "ses_active", threadId: "thread_active", status: "running" },
+ messages: [agentMessage({ conversationId: "cnv_active", traceId: "trc_active", status: "running" })],
+ chatPending: true
+ });
+
+ const selected = workbenchReducer(state, {
+ type: "conversation:select",
+ conversation: {
+ conversationId: "cnv_done",
+ sessionId: "ses_done",
+ messages: [agentMessage({ conversationId: "cnv_done", traceId: "trc_done", status: "completed" })]
+ },
+ workspace: workspace("cnv_done", null)
+ });
+
+ assert.equal(selected.chatPending, false);
+ assert.equal(selected.currentRequest, null);
+ assert.equal(composerFromState(selected, "cnv_done").submitMode, "turn");
+});
+
+test("conversation selection restores steer mode when switching back to a running session", () => {
+ const state = baseState({ workspace: workspace("cnv_done", null), messages: [], chatPending: false });
+
+ const selected = workbenchReducer(state, {
+ type: "conversation:select",
+ conversation: {
+ conversationId: "cnv_active",
+ sessionId: "ses_active",
+ threadId: "thread_active",
+ messages: [agentMessage({ conversationId: "cnv_active", traceId: "trc_active", status: "running" })]
+ },
+ workspace: workspace("cnv_active", "trc_active")
+ });
+
+ const composer = composerFromState(selected, "cnv_active");
+ assert.equal(selected.chatPending, true);
+ assert.equal(composer.submitMode, "steer");
+ assert.equal(composer.targetTraceId, "trc_active");
+});
+
+test("composer routes to steer from workspace active status even before running message is restored", () => {
+ const state = baseState({
+ workspace: workspace("cnv_active", "trc_active"),
+ messages: [],
+ chatPending: false
+ });
+
+ const composer = composerFromState(state, "cnv_active");
+
+ assert.equal(composer.disabled, false);
+ assert.equal(composer.submitMode, "steer");
+ assert.equal(composer.targetTraceId, "trc_active");
+});
+
+test("composer disables only when no session or workspace exists", () => {
+ const composer = composerFromState(baseState({ workspace: null, messages: [] }), null);
+ assert.equal(composer.disabled, true);
+ assert.equal(composer.disabledReason, "session_required");
+ assert.equal(composer.submitMode, "turn");
+});
+
+function baseState(patch: Partial): WorkbenchState {
+ return {
+ workspace: workspace("cnv_active", null),
+ conversations: [],
+ messages: [],
+ selectedDevicePodId: "device-pod-test",
+ providerProfile: "deepseek",
+ codeAgentTimeoutMs: 180_000,
+ gatewayShellTimeoutMs: 120_000,
+ live: null,
+ codeAgentAvailability: null,
+ loading: false,
+ chatPending: false,
+ currentRequest: null,
+ error: null,
+ ...patch
+ };
+}
+
+function workspace(selectedConversationId: string, activeTraceId: string | null): WorkbenchState["workspace"] {
+ return {
+ workspaceId: "wsp_test",
+ revision: 1,
+ selectedConversationId,
+ selectedAgentSessionId: "ses_test",
+ activeTraceId,
+ workspace: {
+ selectedConversationId,
+ selectedAgentSessionId: "ses_test",
+ threadId: "thread_test",
+ activeTraceId,
+ sessionStatus: activeTraceId ? "running" : "completed"
+ }
+ };
+}
+
+function agentMessage(input: { conversationId: string; traceId: string; status: ChatMessage["status"] }): ChatMessage {
+ return {
+ id: `msg_${input.traceId}`,
+ role: "agent",
+ title: "Code Agent",
+ text: "message",
+ status: input.status,
+ createdAt: "2026-06-04T00:00:00.000Z",
+ traceId: input.traceId,
+ conversationId: input.conversationId,
+ sessionId: input.conversationId.replace("cnv", "ses"),
+ threadId: input.conversationId.replace("cnv", "thread")
+ };
+}
diff --git a/web/hwlab-cloud-web/src/state/workbench-reducer.ts b/web/hwlab-cloud-web/src/state/workbench-reducer.ts
index 095cd2cc..0036755f 100644
--- a/web/hwlab-cloud-web/src/state/workbench-reducer.ts
+++ b/web/hwlab-cloud-web/src/state/workbench-reducer.ts
@@ -12,7 +12,7 @@ export type Action =
| { type: "profile:set"; profile: WorkbenchState["providerProfile"] }
| { type: "timeout:set"; codeAgentTimeoutMs: number }
| { type: "gateway-timeout:set"; gatewayShellTimeoutMs: number }
- | { type: "message:pending"; user: ChatMessage; pending: ChatMessage; request: WorkbenchState["currentRequest"] }
+ | { type: "message:pending"; user: ChatMessage; pending: ChatMessage; request: NonNullable }
| { type: "message:replace"; messageId: string; message: ChatMessage }
| { type: "message:trace-status"; messageId: string; status: string }
| { type: "message:trace"; messageId: string; trace: NonNullable }
@@ -33,7 +33,7 @@ export function workbenchReducer(state: WorkbenchState, action: Action): Workben
case "profile:set": return { ...state, providerProfile: action.profile };
case "timeout:set": return { ...state, codeAgentTimeoutMs: action.codeAgentTimeoutMs };
case "gateway-timeout:set": return { ...state, gatewayShellTimeoutMs: action.gatewayShellTimeoutMs };
- case "message:pending": return { ...state, messages: [...state.messages, action.user, action.pending], chatPending: true, currentRequest: action.request };
+ case "message:pending": return { ...state, messages: [...state.messages, action.user, action.pending], chatPending: true, currentRequest: { ...action.request, status: "running" } };
case "message:replace": return { ...state, messages: replaceMessage(state.messages, action.messageId, action.message) };
case "message:trace-status": {
const updated = state.messages.map((message) => message.id === action.messageId ? { ...message, traceReplayStatus: action.status, updatedAt: new Date().toISOString() } : message);
@@ -45,8 +45,11 @@ export function workbenchReducer(state: WorkbenchState, action: Action): Workben
}
case "message:complete": return { ...state, messages: replaceMessage(state.messages, action.messageId, action.message), codeAgentAvailability: action.availability ?? state.codeAgentAvailability, workspace: action.workspace ?? state.workspace };
case "message:fail": return { ...state, messages: replaceMessage(state.messages, action.messageId, action.message) };
- case "chat:done": return { ...state, chatPending: false, currentRequest: null };
- case "conversation:select": return { ...state, workspace: action.workspace ?? state.workspace, messages: action.conversation?.messages ?? [], error: null };
+ case "chat:done": return { ...state, chatPending: false, currentRequest: state.currentRequest ? { ...state.currentRequest, status: "completed" } : null };
+ case "conversation:select": {
+ const messages = action.conversation?.messages ?? [];
+ return { ...state, workspace: action.workspace ?? state.workspace, messages, chatPending: hasRunningAgentMessage(messages), currentRequest: null, error: null };
+ }
case "conversation:list": return { ...state, conversations: action.conversations };
case "conversation:clear": return { ...state, messages: [], currentRequest: null, chatPending: false };
default: return state;
@@ -58,16 +61,56 @@ function replaceMessage(messages: ChatMessage[], messageId: string, replacement:
}
export function composerFromState(state: WorkbenchState, activeConversationId: string | null): ComposerState {
- const activeTraceId = firstNonEmptyString(state.currentRequest?.traceId, state.workspace?.activeTraceId, state.workspace?.workspace?.activeTraceId);
- const sessionStatus = firstNonEmptyString(state.messages.at(-1)?.status, state.workspace?.workspace?.sessionStatus);
- const canSteer = Boolean(activeTraceId && state.chatPending && sessionStatus !== "completed" && sessionStatus !== "failed");
+ const latestMessage = latestConversationMessage(state.messages, activeConversationId);
+ const runningMessage = findRunningAgentMessage(state.messages, activeConversationId);
+ const currentRequestInConversation = state.currentRequest && state.currentRequest.conversationId === activeConversationId ? state.currentRequest : null;
+ const activeTraceId = firstNonEmptyString(currentRequestInConversation?.traceId, runningMessage?.traceId, runningMessage?.runnerTrace?.traceId, state.workspace?.activeTraceId, state.workspace?.workspace?.activeTraceId);
+ const workspaceStatus = firstNonEmptyString(state.workspace?.workspace?.sessionStatus);
+ const sessionStatus = firstNonEmptyString(runningMessage?.status, currentRequestInConversation?.status, latestMessage?.status, workspaceStatus);
+ const latestTerminal = isTerminalStatus(latestMessage?.status);
+ const terminal = latestTerminal || isTerminalStatus(currentRequestInConversation?.status) || (!latestMessage && isTerminalStatus(workspaceStatus));
+ const activeByStatus = isActiveStatus(runningMessage?.status) || isActiveStatus(runningMessage?.runnerTrace?.status) || isActiveStatus(currentRequestInConversation?.status) || isActiveStatus(workspaceStatus) || state.chatPending;
+ const canSteer = Boolean(activeConversationId && activeTraceId && !terminal && activeByStatus);
+ const conversationId = activeConversationId;
+ const sessionId = firstNonEmptyString(runningMessage?.sessionId, currentRequestInConversation?.sessionId, state.workspace?.selectedAgentSessionId, state.workspace?.workspace?.selectedAgentSessionId);
+ const threadId = firstNonEmptyString(runningMessage?.threadId, currentRequestInConversation?.threadId, state.workspace?.workspace?.threadId);
if (canSteer) {
- return { disabled: false, disabledReason: null, submitMode: "steer", route: "/v1/agent/chat/steer", targetTraceId: activeTraceId };
+ return { disabled: false, disabledReason: null, submitMode: "steer", route: "/v1/agent/chat/steer", targetTraceId: activeTraceId, conversationId, sessionId, threadId };
}
if (!activeConversationId && !state.workspace?.workspaceId) {
- return { disabled: true, disabledReason: "session_required", submitMode: "turn", route: "/v1/agent/chat", targetTraceId: null };
+ return { disabled: true, disabledReason: "session_required", submitMode: "turn", route: "/v1/agent/chat", targetTraceId: null, conversationId, sessionId, threadId };
}
- return { disabled: false, disabledReason: null, submitMode: "turn", route: "/v1/agent/chat", targetTraceId: null };
+ return { disabled: false, disabledReason: null, submitMode: "turn", route: "/v1/agent/chat", targetTraceId: null, conversationId, sessionId, threadId };
+}
+
+function findRunningAgentMessage(messages: ChatMessage[], activeConversationId: string | null): ChatMessage | null {
+ for (const message of [...messages].reverse()) {
+ if (message.role !== "agent") continue;
+ if (message.status !== "running" && message.runnerTrace?.status !== "running") continue;
+ if (activeConversationId && message.conversationId && message.conversationId !== activeConversationId) continue;
+ return message;
+ }
+ return null;
+}
+
+function latestConversationMessage(messages: ChatMessage[], activeConversationId: string | null): ChatMessage | null {
+ for (const message of [...messages].reverse()) {
+ if (activeConversationId && message.conversationId && message.conversationId !== activeConversationId) continue;
+ return message;
+ }
+ return null;
+}
+
+function hasRunningAgentMessage(messages: ChatMessage[]): boolean {
+ return Boolean(findRunningAgentMessage(messages, null));
+}
+
+function isActiveStatus(value: unknown): boolean {
+ return ["running", "pending", "accepted", "processing"].includes(String(value ?? "").trim().toLowerCase());
+}
+
+function isTerminalStatus(value: unknown): boolean {
+ return ["completed", "failed", "blocked", "timeout", "canceled", "cancelled"].includes(String(value ?? "").trim().toLowerCase());
}
export function availabilityFromLive(live: LiveSurface): CodeAgentAvailability | null {
diff --git a/web/hwlab-cloud-web/src/state/workbench-state.ts b/web/hwlab-cloud-web/src/state/workbench-state.ts
index ce14313d..48e7a2d6 100644
--- a/web/hwlab-cloud-web/src/state/workbench-state.ts
+++ b/web/hwlab-cloud-web/src/state/workbench-state.ts
@@ -14,7 +14,7 @@ export interface WorkbenchState {
codeAgentAvailability: CodeAgentAvailability | null;
loading: boolean;
chatPending: boolean;
- currentRequest: { traceId: string; conversationId: string; sessionId: string | null; threadId: string | null } | null;
+ currentRequest: { traceId: string; conversationId: string; sessionId: string | null; threadId: string | null; status?: "running" | "completed" } | null;
error: string | null;
}
@@ -24,6 +24,9 @@ export interface ComposerState {
submitMode: "turn" | "steer";
route: "/v1/agent/chat" | "/v1/agent/chat/steer";
targetTraceId: string | null;
+ conversationId: string | null;
+ sessionId: string | null;
+ threadId: string | null;
}
export interface WorkbenchRuntimeContext {
diff --git a/web/hwlab-cloud-web/src/state/workbench.ts b/web/hwlab-cloud-web/src/state/workbench.ts
index fe9d3dd2..96968074 100644
--- a/web/hwlab-cloud-web/src/state/workbench.ts
+++ b/web/hwlab-cloud-web/src/state/workbench.ts
@@ -229,15 +229,17 @@ export function useWorkbenchStore(enabled: boolean): WorkbenchStore {
const submitMessage = useCallback(async (text: string) => {
const value = text.trim();
if (!value) return;
- const traceId = nextProtocolId("trc");
- const conversationId = activeConversationId ?? nextProtocolId("cnv");
- const sessionId = state.workspace?.selectedAgentSessionId ?? state.workspace?.workspace?.selectedAgentSessionId ?? null;
- const threadId = state.workspace?.workspace?.threadId ?? null;
+ const steerMode = composer.submitMode === "steer";
+ const traceId = steerMode && composer.targetTraceId ? composer.targetTraceId : nextProtocolId("trc");
+ const steerTraceId = steerMode ? nextProtocolId("trc_steer") : null;
+ const conversationId = composer.conversationId ?? activeConversationId ?? nextProtocolId("cnv");
+ const sessionId = composer.sessionId ?? state.workspace?.selectedAgentSessionId ?? state.workspace?.workspace?.selectedAgentSessionId ?? null;
+ const threadId = composer.threadId ?? state.workspace?.workspace?.threadId ?? null;
const user = makeMessage("user", value, "sent", { traceId, conversationId, sessionId, threadId });
const pending = makeMessage("agent", "正在处理这次 Code Agent 请求;复杂问题可能需要几分钟。", "running", { traceId, conversationId, sessionId, threadId, title: "Code Agent 处理中" });
const pendingWithRetry: ChatMessage = { ...pending, retryInput: value };
dispatch({ type: "message:pending", user, pending: pendingWithRetry, request: { traceId, conversationId, sessionId, threadId } });
- const route = composer.submitMode === "steer" ? api.steerAgentMessage : api.sendAgentMessage;
+ const route = steerMode ? api.steerAgentMessage : api.sendAgentMessage;
// The submit call itself is a "kicked off" event; treat it as activity
// so the initial POST has a fresh inactivity window.
updateActivity();
@@ -254,7 +256,8 @@ export function useWorkbenchStore(enabled: boolean): WorkbenchStore {
gatewayShellTimeoutMs: state.gatewayShellTimeoutMs,
workspaceId: state.workspace?.workspaceId,
workspaceRevision: state.workspace?.revision,
- targetTraceId: composer.targetTraceId
+ targetTraceId: steerMode ? traceId : null,
+ steerTraceId
}, state.codeAgentTimeoutMs, submitActivityRef);
const finalize = async (result: AgentChatResultResponse, availability: CodeAgentAvailability | null): Promise => {
const completed = messageFromAgentResponse(pending.id, pending, result);
@@ -287,7 +290,7 @@ export function useWorkbenchStore(enabled: boolean): WorkbenchStore {
dispatch({ type: "message:fail", messageId: pending.id, message: failed });
}
dispatch({ type: "chat:done" });
- }, [activeConversationId, composer.submitMode, composer.targetTraceId, state.codeAgentTimeoutMs, state.gatewayShellTimeoutMs, state.messages, state.providerProfile, state.workspace]);
+ }, [activeConversationId, composer.conversationId, composer.sessionId, composer.submitMode, composer.targetTraceId, composer.threadId, state.codeAgentTimeoutMs, state.gatewayShellTimeoutMs, state.messages, state.providerProfile, state.workspace]);
const cancelAgentMessage = useCallback(async (messageId: string) => {
return cancelAgentMessageAction(messageId, {