mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-21 00:02:30 +00:00
fix: keep unsent input, init and chat rotation safe from runtime eviction
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
62e9cac57b
commit
3e70d0c8ef
@@ -378,6 +378,7 @@
|
||||
)
|
||||
$effect(() => {
|
||||
chatHost.setComposerStaged(composerKey, editingMessageIndex, stagedBytes)
|
||||
chatHost.setComposerHasDraft(composerKey, !draft.isEmpty || pendingFileBytes > 0)
|
||||
})
|
||||
$effect(() => () => chatHost.clearComposerStaged(composerKey))
|
||||
|
||||
|
||||
@@ -2822,6 +2822,27 @@ export class AIChatManager implements ChatViewHost {
|
||||
|
||||
clearComposerStaged(key: string) {
|
||||
this.#composerStaged.delete(key)
|
||||
this.#composersWithDraft.delete(key)
|
||||
}
|
||||
|
||||
// Composers whose draft is non-empty. The draft itself is component-local, so
|
||||
// this is the only way to know an unmount would lose unsent input.
|
||||
#composersWithDraft = new SvelteSet<string>()
|
||||
|
||||
setComposerHasDraft(key: string, hasDraft: boolean) {
|
||||
if (hasDraft) this.#composersWithDraft.add(key)
|
||||
else this.#composersWithDraft.delete(key)
|
||||
}
|
||||
|
||||
/** Unsent input an unmount of this chat would lose: a composer draft, or a
|
||||
* queued message (whose text may be empty when it carries only attachments
|
||||
* or context). */
|
||||
get hasUnsentInput(): boolean {
|
||||
return (
|
||||
this.#composersWithDraft.size > 0 ||
|
||||
!this.#queuedDraft.isEmpty ||
|
||||
this.queuedContext !== undefined
|
||||
)
|
||||
}
|
||||
|
||||
/** Release the outgoing-files reservation identified by `key` (a per-send token).
|
||||
|
||||
@@ -85,6 +85,8 @@ export interface ChatViewHost {
|
||||
) => void
|
||||
dequeueMessage: () => void
|
||||
setComposerStaged: (key: string, editingIndex: number | null, bytes: number) => void
|
||||
/** Whether this composer holds anything unsent (text, pastes, attachments, reads in flight). */
|
||||
setComposerHasDraft: (key: string, hasDraft: boolean) => void
|
||||
clearComposerStaged: (key: string) => void
|
||||
attachmentBytesExcluding: (selfKey: string) => number
|
||||
|
||||
|
||||
@@ -470,6 +470,7 @@ export class FlowChatViewHost implements ChatViewHost {
|
||||
return taken
|
||||
}
|
||||
setComposerStaged = () => {}
|
||||
setComposerHasDraft = () => {}
|
||||
clearComposerStaged = () => {}
|
||||
attachmentBytesExcluding = () => 0
|
||||
|
||||
|
||||
@@ -228,6 +228,8 @@ export interface SessionRuntime {
|
||||
}
|
||||
|
||||
const runtimes = new SvelteMap<string, SessionRuntime>()
|
||||
// Sessions whose runtime is still restoring its chat (initRuntime pending).
|
||||
const initializing = new Set<string>()
|
||||
|
||||
function emptyFlow(): Flow {
|
||||
return {
|
||||
@@ -990,7 +992,10 @@ export function getOrCreateRuntime(session: Session): SessionRuntime {
|
||||
if (!runtime) {
|
||||
runtime = createRuntime(session)
|
||||
runtimes.set(session.id, runtime)
|
||||
initRuntime(runtime, session).catch((e) => console.error('Failed to init session runtime', e))
|
||||
initializing.add(session.id)
|
||||
initRuntime(runtime, session)
|
||||
.catch((e) => console.error('Failed to init session runtime', e))
|
||||
.finally(() => initializing.delete(session.id))
|
||||
}
|
||||
return runtime
|
||||
}
|
||||
@@ -1040,6 +1045,9 @@ onRemoteTurnEnd((sessionId, chatId) => {
|
||||
async function applyRemoteTurnEnd(sessionId: string, chatId: string): Promise<void> {
|
||||
const runtime = runtimes.get(sessionId)
|
||||
if (!runtime) {
|
||||
// The driving tab may have rotated to a new chat; follow it, or the next
|
||||
// read and a later runtime would open the old one.
|
||||
setSessionChatId(sessionId, chatId)
|
||||
peeks.delete(sessionId)
|
||||
return
|
||||
}
|
||||
@@ -1455,10 +1463,13 @@ const visitOrder: string[] = []
|
||||
function isEvictable(runtime: SessionRuntime): boolean {
|
||||
const m = runtime.manager
|
||||
return (
|
||||
// initRuntime has no cancellation: evicted mid-way, it would go on to restore
|
||||
// the chat's jobs into a manager nothing owns, which then polls them.
|
||||
!initializing.has(runtime.sessionId) &&
|
||||
!m.loading &&
|
||||
!m.sendInFlight &&
|
||||
m.instructions.trim() === '' &&
|
||||
m.queuedMessage.trim() === '' &&
|
||||
!m.hasUnsentInput &&
|
||||
!m.backgroundJobs.some(isLiveJob) &&
|
||||
!runtime.hasEditorCells()
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user