refactor(schema): apply session review decisions (#35793)
This commit is contained in:
parent
d27746e5b3
commit
ed6ad272ec
142 changed files with 4239 additions and 3174 deletions
|
|
@ -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 }
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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: "" }
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue