-
Notifications
You must be signed in to change notification settings - Fork 415
fix(web): stop sending prompts into a busy turn on the web UI #1522
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
0d7484a
80e1184
e2c77d1
a22d1fa
a349247
3979f42
a0fd3cd
acf74cc
f366a41
37d33cf
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| "@moonshot-ai/kimi-code": patch | ||
| --- | ||
|
|
||
| web: Fix an occasional "another turn is active" error when sending the first message of a new conversation, and show a starting state while it is being sent. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -82,6 +82,17 @@ const pendingQuestionActions = reactive<Record<string, 'answer' | 'dismiss'>>({} | |
| const pendingApprovalActions = reactive<Record<string, true>>({}); | ||
| /** Task ids with an in-flight cancel, keyed by taskId. */ | ||
| const pendingTaskCancellations = reactive<Record<string, true>>({}); | ||
| /** | ||
| * Workspace ids whose empty-session first prompt is currently being created + | ||
| * submitted. The empty-composer path (`startSessionAndSendPrompt`) awaits | ||
| * `createDraftSession` (addWorkspace + createSession + selectSession) before | ||
| * the session id exists, so the per-session `inFlightPromptSessions` guard | ||
| * cannot cover that window — a second Enter / send-button click during it | ||
| * would otherwise fire a second concurrent POST and trip the daemon's | ||
| * `turn.agent_busy` race. Module-level singleton — matches the other | ||
| * `pending*Actions` guards above. | ||
| */ | ||
| const startingFirstPromptWorkspaces = reactive(new Set<string>()); | ||
|
|
||
| type SyncSessionResult = 'ok' | 'not-found' | 'failed'; | ||
|
|
||
|
|
@@ -815,12 +826,22 @@ export function useWorkspaceState(rawState: ExtendedState, deps: UseWorkspaceSta | |
| text: string, | ||
| attachments?: PromptAttachment[], | ||
| ): Promise<void> { | ||
| // Guard the whole "create draft session + submit first prompt" flow: the | ||
| // session id doesn't exist until `createDraftSession` resolves, so the | ||
| // per-session `inFlightPromptSessions` guard can't cover this window. A | ||
| // second Enter / send-button click in that window would otherwise fire a | ||
| // concurrent first POST for the same new session and trip the daemon's | ||
| // `turn.agent_busy` race. | ||
| if (startingFirstPromptWorkspaces.has(workspaceId)) return; | ||
| startingFirstPromptWorkspaces.add(workspaceId); | ||
| try { | ||
| const sid = await createDraftSession(workspaceId); | ||
| if (!sid) return; | ||
| await submitPromptInternal(sid, text, attachments); | ||
| } catch (err) { | ||
| pushOperationFailure('startSessionAndSendPrompt', err); | ||
| } finally { | ||
| startingFirstPromptWorkspaces.delete(workspaceId); | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -837,6 +858,11 @@ export function useWorkspaceState(rawState: ExtendedState, deps: UseWorkspaceSta | |
| skillName: string, | ||
| args?: string, | ||
| ): Promise<void> { | ||
| // Same reentry window as startSessionAndSendPrompt (see the guard there): | ||
| // draft-session creation selects the new session before the activation, | ||
| // so concurrent first actions must be dropped here. | ||
| if (startingFirstPromptWorkspaces.has(workspaceId)) return; | ||
| startingFirstPromptWorkspaces.add(workspaceId); | ||
| try { | ||
| const sid = await createDraftSession(workspaceId); | ||
| if (!sid) return; | ||
|
|
@@ -862,6 +888,7 @@ export function useWorkspaceState(rawState: ExtendedState, deps: UseWorkspaceSta | |
| : rawState.defaultModel) ?? undefined; | ||
| await persistSessionProfile( | ||
| { | ||
| model, | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When running the existing Useful? React with 👍 / 👎. |
||
| planMode, | ||
| swarmMode, | ||
| permissionMode: rawState.permission, | ||
|
|
@@ -872,6 +899,8 @@ export function useWorkspaceState(rawState: ExtendedState, deps: UseWorkspaceSta | |
| await modelProvider.activateSkill(skillName, args, sid); | ||
| } catch (err) { | ||
| pushOperationFailure('startSessionAndActivateSkill', err); | ||
| } finally { | ||
| startingFirstPromptWorkspaces.delete(workspaceId); | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -888,12 +917,17 @@ export function useWorkspaceState(rawState: ExtendedState, deps: UseWorkspaceSta | |
| workspaceId: string, | ||
| prompt?: string, | ||
| ): Promise<void> { | ||
| // Same reentry window as startSessionAndSendPrompt (see the guard there). | ||
| if (startingFirstPromptWorkspaces.has(workspaceId)) return; | ||
| startingFirstPromptWorkspaces.add(workspaceId); | ||
| try { | ||
| const sid = await createDraftSession(workspaceId); | ||
| if (!sid) return; | ||
| await sideChat.openSideChatOn(sid, prompt); | ||
| } catch (err) { | ||
| pushOperationFailure('startSessionAndOpenSideChat', err); | ||
| } finally { | ||
| startingFirstPromptWorkspaces.delete(workspaceId); | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -2186,6 +2220,14 @@ export function useWorkspaceState(rawState: ExtendedState, deps: UseWorkspaceSta | |
| searchFiles, | ||
| loadOlderMessages, | ||
| refreshSessionSidecars, | ||
| /** True while any empty-composer first prompt is being created + submitted | ||
| * (the window covered by startingFirstPromptWorkspaces). Drives the | ||
| * empty-session "starting conversation…" loading state. Intentionally | ||
| * keyed by the lock set itself rather than the current activeWorkspaceId: | ||
| * createDraftSession can swap activeWorkspaceId to a registered id | ||
| * mid-flight, and a workspace-keyed read would prematurely re-enable the | ||
| * composer and reopen the duplicate first-submit race. */ | ||
| isStartingFirstPrompt: () => startingFirstPromptWorkspaces.size > 0, | ||
| }; | ||
| } | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This only disables the centered empty Composer. During
startSessionAndSendPrompt,createDraftSessioncallsselectSession, which setsactiveSessionIdwhile it is still awaiting the snapshot, before the original prompt is submitted; at that point this pane switches toChatDock, whose Composer is not givenstarting. On a slow snapshot, a user can type/submit from the dock,sendPromptsees noinFlightPromptSessionsyet, and then the originalsubmitPromptInternalruns too, recreating the concurrent first-turn POST this change is meant to prevent.Useful? React with 👍 / 👎.