Compare commits

...

1 Commits

Author SHA1 Message Date
Kit Langton
8084f9dfd8 fix(session): distinguish idle reasons for completion and abort 2026-03-10 20:44:34 -04:00
7 changed files with 43 additions and 13 deletions

View File

@@ -376,7 +376,7 @@ export const SessionRoutes = lazy(() =>
}),
),
async (c) => {
SessionPrompt.cancel(c.req.valid("param").sessionID)
SessionPrompt.cancel(c.req.valid("param").sessionID, "aborted")
return c.json(true)
},
)

View File

@@ -381,7 +381,10 @@ export namespace SessionProcessor {
sessionID: input.assistantMessage.sessionID,
error: input.assistantMessage.error,
})
SessionStatus.set(input.sessionID, { type: "idle" })
SessionStatus.set(input.sessionID, {
type: "idle",
reason: error.name === "MessageAbortedError" ? "aborted" : "error",
})
}
}
if (snapshot) {

View File

@@ -254,17 +254,21 @@ export namespace SessionPrompt {
return s[sessionID].abort.signal
}
export function cancel(sessionID: string) {
export function cancel(sessionID: string, reason: SessionStatus.IdleReason = "aborted") {
log.info("cancel", { sessionID })
const idle = () => {
if (SessionStatus.get(sessionID).type === "idle") return
SessionStatus.set(sessionID, { type: "idle", reason })
}
const s = state()
const match = s[sessionID]
if (!match) {
SessionStatus.set(sessionID, { type: "idle" })
idle()
return
}
match.abort.abort()
delete s[sessionID]
SessionStatus.set(sessionID, { type: "idle" })
idle()
return
}
@@ -283,7 +287,8 @@ export namespace SessionPrompt {
})
}
using _ = defer(() => cancel(sessionID))
let reason: SessionStatus.IdleReason = "completed"
using _ = defer(() => cancel(sessionID, reason))
// Structured output state
// Note: On session resumption, state is reset but outputFormat is preserved
@@ -295,7 +300,10 @@ export namespace SessionPrompt {
while (true) {
SessionStatus.set(sessionID, { type: "busy" })
log.info("loop", { step, sessionID })
if (abort.aborted) break
if (abort.aborted) {
reason = "aborted"
break
}
let msgs = await MessageV2.filterCompacted(MessageV2.stream(sessionID))
let lastUser: MessageV2.User | undefined
@@ -536,7 +544,10 @@ export namespace SessionPrompt {
auto: task.auto,
overflow: task.overflow,
})
if (result === "stop") break
if (result === "stop") {
reason = abort.aborted ? "aborted" : "completed"
break
}
continue
}
@@ -698,11 +709,19 @@ export namespace SessionPrompt {
retries: 0,
}).toObject()
await Session.updateMessage(processor.message)
reason = "error"
break
}
}
if (result === "stop") break
if (result === "stop") {
if (processor.message.error?.name === "MessageAbortedError") {
reason = "aborted"
} else if (processor.message.error) {
reason = "error"
}
break
}
if (result === "compact") {
await SessionCompaction.create({
sessionID,

View File

@@ -4,10 +4,14 @@ import { Instance } from "@/project/instance"
import z from "zod"
export namespace SessionStatus {
export const IdleReason = z.enum(["completed", "aborted", "error"])
export type IdleReason = z.infer<typeof IdleReason>
export const Info = z
.union([
z.object({
type: z.literal("idle"),
reason: IdleReason.optional(),
}),
z.object({
type: z.literal("retry"),
@@ -65,9 +69,11 @@ export namespace SessionStatus {
})
if (status.type === "idle") {
// deprecated
Bus.publish(Event.Idle, {
sessionID,
})
if (!status.reason || status.reason === "completed") {
Bus.publish(Event.Idle, {
sessionID,
})
}
delete state()[sessionID]
return
}

View File

@@ -119,7 +119,7 @@ export const TaskTool = Tool.define("task", async (ctx) => {
const messageID = Identifier.ascending("message")
function cancel() {
SessionPrompt.cancel(session.id)
SessionPrompt.cancel(session.id, "aborted")
}
ctx.abort.addEventListener("abort", cancel)
using _ = defer(() => ctx.abort.removeEventListener("abort", cancel))

View File

@@ -453,6 +453,7 @@ export type EventPermissionReplied = {
export type SessionStatus =
| {
type: "idle"
reason?: "completed" | "aborted" | "error"
}
| {
type: "retry"

View File

@@ -581,6 +581,7 @@ export type EventPermissionReplied = {
export type SessionStatus =
| {
type: "idle"
reason?: "completed" | "aborted" | "error"
}
| {
type: "retry"