refactor(schema): apply session review decisions (#35793)

This commit is contained in:
Kit Langton 2026-07-07 22:10:11 -04:00 committed by GitHub
commit ed6ad272ec
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
142 changed files with 4239 additions and 3174 deletions

View file

@ -93,7 +93,7 @@ function permission(id: string, sessionID = "session"): PermissionRequest {
}
}
function durable(sessionID: string) {
function durable(sessionID: string): { aggregateID: string; seq: number; version: 1 } {
return { aggregateID: sessionID, seq: 0, version: 1 }
}

View file

@ -24,6 +24,12 @@ function emitEvent(events: ReturnType<typeof createEventStream>, event: V2Event)
events.emit({ ...event, location: { directory } })
}
function durable(sessionID: string, seq?: number): { aggregateID: string; seq: number; version: 1 }
function durable<const Version extends number>(
sessionID: string,
seq: number,
version: Version,
): { aggregateID: string; seq: number; version: Version }
function durable(sessionID: string, seq = 0, version = 1) {
return { aggregateID: sessionID, seq, version }
}
@ -265,7 +271,7 @@ test("applies absolute usage events without losing full session updates", async
id: "evt_usage_deleted",
created: 9,
type: "session.deleted",
durable: durable(sessionID, 9),
durable: durable(sessionID, 9, 2),
data: { sessionID },
})
await Bun.sleep(20)
@ -513,7 +519,11 @@ test("restores running manual compaction before applying live deltas", async ()
try {
await data.session.message.refresh("session-compaction")
expect(data.session.compaction("session-compaction")).toBe("Existing ")
expect(data.session.message.get("session-compaction", "message-compaction")).toMatchObject({
type: "compaction",
status: "running",
summary: "Existing ",
})
emitEvent(events, {
id: "evt_compaction_delta",
@ -524,7 +534,7 @@ test("restores running manual compaction before applying live deltas", async ()
await wait(() => {
const message = data.session.message.get("session-compaction", "message-compaction")
return message?.type === "compaction" && message.summary === "Existing summary"
return message?.type === "compaction" && message.status === "running" && message.summary === "Existing summary"
})
} finally {
app.renderer.destroy()
@ -904,7 +914,7 @@ test("tracks session status from active sessions and execution events", async ()
id: "evt_step_ended",
created: 0,
type: "session.step.ended",
durable: durable("session-live", 1, 2),
durable: durable("session-live", 1),
data: {
sessionID: "session-live",
assistantMessageID: "message-live",
@ -938,7 +948,7 @@ test("tracks session status from active sessions and execution events", async ()
id: "evt_execution_succeeded",
created: 0,
type: "session.execution.succeeded",
durable: durable("session-live", 1, 3),
durable: durable("session-live", 1),
data: { sessionID: "session-live" },
})
await wait(() => data.session.status("session-live") === "idle")
@ -969,7 +979,7 @@ test("tracks session status from active sessions and execution events", async ()
id: "evt_step_failed",
created: 0,
type: "session.step.failed",
durable: durable("session-failed", 1, 2),
durable: durable("session-failed", 1),
data: {
sessionID: "session-failed",
assistantMessageID: "message-failed",
@ -1009,7 +1019,7 @@ test("tracks session status from active sessions and execution events", async ()
id: "evt_failed_execution_failed",
created: 0,
type: "session.execution.failed",
durable: durable("session-failed", 1, 3),
durable: durable("session-failed", 1),
data: {
sessionID: "session-failed",
error: { type: "provider.content-filter", message: "Provider blocked the response" },
@ -1028,7 +1038,7 @@ test("tracks session status from active sessions and execution events", async ()
id: "evt_retry_step_started",
created: 0,
type: "session.step.started",
durable: durable("session-retry", 1, 2),
durable: durable("session-retry", 1),
data: {
sessionID: "session-retry",
assistantMessageID: "message-retry",
@ -1040,7 +1050,7 @@ test("tracks session status from active sessions and execution events", async ()
id: "evt_retry_scheduled",
created: 0,
type: "session.retry.scheduled",
durable: durable("session-retry", 1, 3),
durable: durable("session-retry", 1),
data: {
sessionID: "session-retry",
assistantMessageID: "message-retry",
@ -1058,7 +1068,7 @@ test("tracks session status from active sessions and execution events", async ()
id: "evt_retry_next_step",
created: 2_000,
type: "session.step.started",
durable: durable("session-retry", 1, 4),
durable: durable("session-retry", 1),
data: {
sessionID: "session-retry",
assistantMessageID: "message-retry",
@ -1076,7 +1086,7 @@ test("tracks session status from active sessions and execution events", async ()
id: "evt_retry_scheduled_again",
created: 2_000,
type: "session.retry.scheduled",
durable: durable("session-retry", 1, 5),
durable: durable("session-retry", 1),
data: {
sessionID: "session-retry",
assistantMessageID: "message-retry",
@ -1093,29 +1103,18 @@ test("tracks session status from active sessions and execution events", async ()
id: "evt_retry_interrupted",
created: 2_000,
type: "session.execution.interrupted",
durable: durable("session-retry", 1, 6),
durable: durable("session-retry", 1),
data: { sessionID: "session-retry", reason: "shutdown" },
})
await wait(() => data.session.status("session-retry") === "idle")
expect(data.session.message.get("session-retry", "message-retry")).not.toHaveProperty("retry")
emitEvent(events, {
id: "evt_compaction_admitted",
created: 0,
type: "session.compaction.admitted",
durable: durable("session-manual", 1),
data: { sessionID: "session-manual", inputID: "message-compaction" },
})
await wait(() => {
const message = data.session.message.get("session-manual", "message-compaction")
return message?.type === "compaction" && message.status === "queued"
})
emitEvent(events, {
id: "evt_manual_compaction_started",
created: 1,
type: "session.compaction.started",
durable: durable("session-manual", 2),
data: { sessionID: "session-manual", reason: "manual" },
data: { sessionID: "session-manual", reason: "manual", recent: "", inputID: "message-compaction" },
})
emitEvent(events, {
id: "evt_manual_compaction_delta",
@ -1125,13 +1124,13 @@ test("tracks session status from active sessions and execution events", async ()
})
await wait(() => {
const message = data.session.message.get("session-manual", "message-compaction")
return message?.type === "compaction" && message.summary === "Streamed summary"
return message?.type === "compaction" && message.status === "running" && message.summary === "Streamed summary"
})
emitEvent(events, {
id: "evt_manual_compaction_ended",
created: 3,
type: "session.compaction.ended",
durable: durable("session-manual", 3),
durable: durable("session-manual", 4),
data: { sessionID: "session-manual", reason: "manual", text: "Streamed summary", recent: "recent" },
})
await wait(() => {
@ -1147,7 +1146,7 @@ test("tracks session status from active sessions and execution events", async ()
created: 0,
type: "session.compaction.started",
durable: durable("session-live", 2),
data: { sessionID: "session-live", reason: "auto" },
data: { sessionID: "session-live", reason: "auto", recent: "" },
})
emitEvent(events, {
id: "evt_compaction_delta_1",
@ -1161,18 +1160,25 @@ test("tracks session status from active sessions and execution events", async ()
type: "session.compaction.delta",
data: { sessionID: "session-live", text: "summary" },
})
await wait(() => data.session.compaction("session-live") === "Live summary")
await wait(() => {
const message = data.session.message.get("session-live", "msg_compaction_started")
return message?.type === "compaction" && message.status === "running" && message.summary === "Live summary"
})
emitEvent(events, {
id: "evt_compaction_ended",
created: 0,
type: "session.compaction.ended",
durable: durable("session-live", 3),
durable: durable("session-live", 5),
data: { sessionID: "session-live", reason: "auto", text: "Live summary", recent: "recent" },
})
await wait(() => data.session.compaction("session-live") === undefined)
expect(data.session.message.get("session-live", "msg_compaction_ended")).toMatchObject({
await wait(() => {
const message = data.session.message.get("session-live", "msg_compaction_started")
return message?.type === "compaction" && message.status === "completed"
})
expect(data.session.message.get("session-live", "msg_compaction_started")).toMatchObject({
type: "compaction",
status: "completed",
summary: "Live summary",
})
} finally {

View file

@ -1,9 +1,9 @@
import { expect, test } from "bun:test"
import type { SessionMessage, SessionMessageAssistant } from "@opencode-ai/sdk/v2"
import type { SessionMessageAssistant, SessionMessageInfo } from "@opencode-ai/sdk/v2"
import { reduceSessionRows } from "../../../src/routes/session/rows"
test("groups exploration parts across assistant messages until a delimiter", () => {
const messages: SessionMessage[] = [
const messages: SessionMessageInfo[] = [
{ type: "user", id: "user-1", text: "Explore", time: { created: 0 } },
assistant("assistant-1", [
{ type: "text", text: "Looking" },
@ -35,7 +35,7 @@ test("groups exploration parts across assistant messages until a delimiter", ()
})
test("keeps non-exploration tools as individual part rows", () => {
const messages: SessionMessage[] = [
const messages: SessionMessageInfo[] = [
assistant("assistant-1", [
{ type: "tool", id: "read-1", name: "read", state: pending(), time: { created: 1 } },
{ type: "tool", id: "bash-1", name: "bash", state: pending(), time: { created: 2 } },
@ -63,7 +63,7 @@ test("keeps non-exploration tools as individual part rows", () => {
})
test("assigns stable kind ordinals within an assistant message", () => {
const messages: SessionMessage[] = [
const messages: SessionMessageInfo[] = [
assistant("assistant-1", [
{ type: "text", text: "First" },
{ type: "reasoning", text: "Think" },
@ -81,7 +81,7 @@ test("assigns stable kind ordinals within an assistant message", () => {
})
test("groups across empty assistant reasoning parts", () => {
const messages: SessionMessage[] = [
const messages: SessionMessageInfo[] = [
assistant("assistant-1", [
{ type: "reasoning", text: "Looking" },
{ type: "tool", id: "read-1", name: "read", state: pending(), time: { created: 2 } },
@ -112,7 +112,7 @@ test("completes exploration groups when another row follows", () => {
{ type: "tool", id: "grep-1", name: "grep", state: pending(), time: { created: 3 } },
])
finished.finish = "stop"
const messages: SessionMessage[] = [
const messages: SessionMessageInfo[] = [
assistant("assistant-1", [{ type: "tool", id: "read-1", name: "read", state: pending(), time: { created: 1 } }]),
{ type: "user", id: "user-1", text: "Continue", time: { created: 2 } },
finished,
@ -139,12 +139,11 @@ test("completes exploration groups when another row follows", () => {
})
test("hides synthetic messages without descriptions", () => {
const messages: SessionMessage[] = [
const messages: SessionMessageInfo[] = [
assistant("assistant-1", [{ type: "tool", id: "read-1", name: "read", state: pending(), time: { created: 1 } }]),
{
type: "synthetic",
id: "synthetic-1",
sessionID: "session-1",
text: "internal context",
time: { created: 2 },
},
@ -166,12 +165,11 @@ test("hides synthetic messages without descriptions", () => {
})
test("renders synthetic messages with descriptions", () => {
const messages: SessionMessage[] = [
const messages: SessionMessageInfo[] = [
assistant("assistant-1", [{ type: "tool", id: "read-1", name: "read", state: pending(), time: { created: 1 } }]),
{
type: "synthetic",
id: "synthetic-1",
sessionID: "session-1",
text: "internal context",
description: "Explicit notice",
time: { created: 2 },
@ -209,19 +207,19 @@ test("renders a footer for a pre-output retry assistant after replay", () => {
expect(reduceSessionRows([message])).toEqual([{ type: "assistant-footer", messageID: "assistant-retry" }])
})
test("places a pending compaction barrier before every queued user message", () => {
const queued = (id: string, text: string, created: number): SessionMessage => ({
test("places a running compaction barrier before every queued user message", () => {
const queued = (id: string, text: string, created: number): SessionMessageInfo => ({
type: "user",
id,
text,
time: { created },
})
const messages: SessionMessage[] = [
const messages: SessionMessageInfo[] = [
queued("user-before", "Before", 1),
{
type: "compaction",
id: "compaction",
status: "queued",
status: "running",
reason: "manual",
summary: "",
recent: "",
@ -249,5 +247,5 @@ function assistant(id: string, content: SessionMessageAssistant["content"]): Ses
}
function pending() {
return { status: "pending" as const, input: "" }
return { status: "streaming" as const, input: "" }
}