diff --git a/packages/core/src/session/runner/llm.ts b/packages/core/src/session/runner/llm.ts index 3495cfdc4fb..6ed210fde18 100644 --- a/packages/core/src/session/runner/llm.ts +++ b/packages/core/src/session/runner/llm.ts @@ -410,7 +410,9 @@ const layer = Layer.effect( yield* events.publish(SessionEvent.Compaction.Failed, { sessionID, reason: "manual", - error: { type: "compaction.failed", message: Cause.pretty(compacted.cause) }, + error: Cause.hasInterruptsOnly(compacted.cause) + ? { type: "aborted", message: "Compaction cancelled" } + : { type: "compaction.failed", message: Cause.pretty(compacted.cause) }, inputID: unsettled.id, }) return yield* Effect.failCause(compacted.cause) diff --git a/packages/core/test/session-runner.test.ts b/packages/core/test/session-runner.test.ts index 39cf62e8471..6bd8b23be14 100644 --- a/packages/core/test/session-runner.test.ts +++ b/packages/core/test/session-runner.test.ts @@ -1892,6 +1892,35 @@ describe("SessionRunnerLLM", () => { }), ) + it.effect("records cancelled manual compaction without surfacing an internal failure", () => + Effect.gen(function* () { + const session = yield* setup + response = reply.text("Earlier answer", "text-manual-interrupt-history") + yield* admit(session, "Earlier question") + yield* session.resume(sessionID) + + const streamed = yield* Deferred.make() + const partial = fragmentFixture("text", "text-manual-interrupt-summary", ["Partial summary"]) + responseStream = Stream.concat( + Stream.fromIterable(partial.partialEvents), + Stream.fromEffect(Deferred.succeed(streamed, undefined)).pipe(Stream.flatMap(() => Stream.never)), + ) + const compaction = yield* session.compact({ sessionID }) + const run = yield* session.resume(sessionID).pipe(Effect.forkChild) + yield* Deferred.await(streamed) + yield* session.interrupt(sessionID) + + yield* Fiber.await(run) + expect(yield* SessionPending.compaction((yield* Database.Service).db, sessionID)).toBeUndefined() + expect((yield* session.messages({ sessionID })).find((message) => message.id === compaction.id)).toMatchObject({ + type: "compaction", + status: "failed", + reason: "manual", + error: { type: "aborted", message: "Compaction cancelled" }, + }) + }), + ) + it.effect("settles an admitted manual compaction when pre-start resolution throws", () => Effect.gen(function* () { const session = yield* setup diff --git a/packages/tui/src/routes/session/index.tsx b/packages/tui/src/routes/session/index.tsx index adeb207940b..baad5abd137 100644 --- a/packages/tui/src/routes/session/index.tsx +++ b/packages/tui/src/routes/session/index.tsx @@ -1440,9 +1440,10 @@ function CompactionMessage(props: { message: Extract props.message.status - const text = () => (props.message.status === "failed" ? props.message.error.message : props.message.summary) + const cancelled = () => props.message.status === "failed" && props.message.error.type === "aborted" + const text = () => (props.message.status === "failed" ? (cancelled() ? "" : props.message.error.message) : props.message.summary) const content = createMemo(() => text().trim()) - const color = () => (status() === "failed" ? themeV2.text.feedback.error() : themeV2.text.subdued()) + const color = () => (status() === "failed" && !cancelled() ? themeV2.text.feedback.error() : themeV2.text.subdued()) return ( @@ -1454,11 +1455,14 @@ function CompactionMessage(props: { message: Extract - + Compaction + + · cancelled +