diff --git a/packages/app/e2e/session/session-composer-dock.spec.ts b/packages/app/e2e/session/session-composer-dock.spec.ts index e9cfc03e485..1151d1a298f 100644 --- a/packages/app/e2e/session/session-composer-dock.spec.ts +++ b/packages/app/e2e/session/session-composer-dock.spec.ts @@ -134,6 +134,7 @@ test("default dock shows prompt input", async ({ page, sdk, gotoSession }) => { }) test("blocked question flow unblocks after submit", async ({ page, sdk, gotoSession }) => { + test.skip(process.platform === "win32", "Skipping on Windows due interaction issues") // kilocode_change await withDockSession(sdk, "e2e composer dock question", async (session) => { await withDockSeed(sdk, session.id, async () => { await gotoSession(session.id) @@ -144,6 +145,7 @@ test("blocked question flow unblocks after submit", async ({ page, sdk, gotoSess { header: "Need input", question: "Pick one option", + multiple: true, // kilocode_change options: [ { label: "Continue", description: "Continue now" }, { label: "Stop", description: "Stop here" }, @@ -250,6 +252,7 @@ test("child session question request blocks parent dock and unblocks after submi sdk, gotoSession, }) => { + test.skip(process.platform === "win32", "Skipping on Windows due interaction issues") // kilocode_change await withDockSession(sdk, "e2e composer dock child question parent", async (session) => { await gotoSession(session.id) @@ -269,6 +272,7 @@ test("child session question request blocks parent dock and unblocks after submi { header: "Child input", question: "Pick one child option", + multiple: true, // kilocode_change options: [ { label: "Continue", description: "Continue child" }, { label: "Stop", description: "Stop child" }, @@ -375,6 +379,7 @@ test("todo dock transitions and collapse behavior", async ({ page, sdk, gotoSess }) test("keyboard focus stays off prompt while blocked", async ({ page, sdk, gotoSession }) => { + test.skip(process.platform === "win32", "Skipping on Windows due interaction issues") // kilocode_change await withDockSession(sdk, "e2e composer dock keyboard", async (session) => { await withDockSeed(sdk, session.id, async () => { await gotoSession(session.id) diff --git a/packages/opencode/src/session/index.ts b/packages/opencode/src/session/index.ts index ed791555148..69b61e1b882 100644 --- a/packages/opencode/src/session/index.ts +++ b/packages/opencode/src/session/index.ts @@ -688,6 +688,9 @@ export namespace Session { const { KiloSessions } = await import("@/kilo-sessions/kilo-sessions") await KiloSessions.remove(sessionID).catch(() => {}) // kilocode_change platformOverrides.delete(sessionID) // kilocode_change - clean up platform override + // kilocode_change start - cancel running processor before deleting to avoid FK constraint errors + SessionPrompt.cancel(sessionID) + // kilocode_change end // CASCADE delete handles messages and parts automatically Database.use((db) => { db.delete(SessionTable).where(eq(SessionTable.id, sessionID)).run() @@ -705,22 +708,32 @@ export namespace Session { export const updateMessage = fn(MessageV2.Info, async (msg) => { const time_created = msg.time.created const { id, sessionID, ...data } = msg - Database.use((db) => { - db.insert(MessageTable) - .values({ - id, - session_id: sessionID, - time_created, - data, - }) - .onConflictDoUpdate({ target: MessageTable.id, set: { data } }) - .run() - Database.effect(() => - Bus.publish(MessageV2.Event.Updated, { - info: msg, - }), - ) - }) + // kilocode_change start - ignore FK errors when session was deleted while processor was still running + try { + Database.use((db) => { + db.insert(MessageTable) + .values({ + id, + session_id: sessionID, + time_created, + data, + }) + .onConflictDoUpdate({ target: MessageTable.id, set: { data } }) + .run() + Database.effect(() => + Bus.publish(MessageV2.Event.Updated, { + info: msg, + }), + ) + }) + } catch (e: any) { + if (e?.code === "SQLITE_CONSTRAINT_FOREIGNKEY") { + log.warn("skipping message update for deleted session", { id: msg.id, sessionID: msg.sessionID }) + } else { + throw e + } + } + // kilocode_change end return msg }) @@ -774,23 +787,33 @@ export namespace Session { export const updatePart = fn(UpdatePartInput, async (part) => { const { id, messageID, sessionID, ...data } = part const time = Date.now() - Database.use((db) => { - db.insert(PartTable) - .values({ - id, - message_id: messageID, - session_id: sessionID, - time_created: time, - data, - }) - .onConflictDoUpdate({ target: PartTable.id, set: { data } }) - .run() - Database.effect(() => - Bus.publish(MessageV2.Event.PartUpdated, { - part, - }), - ) - }) + // kilocode_change start - ignore FK errors when session was deleted while processor was still running + try { + Database.use((db) => { + db.insert(PartTable) + .values({ + id, + message_id: messageID, + session_id: sessionID, + time_created: time, + data, + }) + .onConflictDoUpdate({ target: PartTable.id, set: { data } }) + .run() + Database.effect(() => + Bus.publish(MessageV2.Event.PartUpdated, { + part, + }), + ) + }) + } catch (e: any) { + if (e?.code === "SQLITE_CONSTRAINT_FOREIGNKEY") { + log.warn("skipping part update for deleted session", { id: part.id, sessionID: part.sessionID }) + } else { + throw e + } + } + // kilocode_change end return part }) diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index ca5c8a2c996..b3dbde6f37c 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -768,6 +768,7 @@ export namespace SessionPrompt { SessionCompaction.prune({ sessionID }) // kilocode_change start finished = true + abort.throwIfAborted() // kilocode_change end for await (const item of MessageV2.stream(sessionID)) { if (item.info.role === "user") continue diff --git a/packages/opencode/src/session/summary.ts b/packages/opencode/src/session/summary.ts index 349336ba788..f724067f665 100644 --- a/packages/opencode/src/session/summary.ts +++ b/packages/opencode/src/session/summary.ts @@ -101,7 +101,10 @@ export namespace SessionSummary { const messages = input.messages.filter( (m) => m.info.id === input.messageID || (m.info.role === "assistant" && m.info.parentID === input.messageID), ) - const msgWithParts = messages.find((m) => m.info.id === input.messageID)! + // kilocode_change start - session may have been deleted before summarization completed + const msgWithParts = messages.find((m) => m.info.id === input.messageID) + if (!msgWithParts) return + // kilocode_change end const userMsg = msgWithParts.info as MessageV2.User const diffs = await computeDiff({ messages }) userMsg.summary = {