feat(core): add session snapshot and revert system (#33226)

This commit is contained in:
Dax 2026-06-24 19:41:16 -04:00 committed by GitHub
commit 9bb5370205
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
42 changed files with 2311 additions and 365 deletions

View file

@ -43,6 +43,7 @@ import { NonNegativeInt, optionalOmitUndefined } from "@opencode-ai/core/schema"
import { RuntimeFlags } from "@/effect/runtime-flags"
import { ProviderV2 } from "@opencode-ai/core/provider"
import { ModelV2 } from "@opencode-ai/core/model"
import { SessionMessageID } from "@opencode-ai/schema/session-message-id"
const runtime = makeRuntime(Database.Service, Database.defaultLayer)
@ -68,7 +69,14 @@ export function fromRow(row: SessionRow): Info {
}
: undefined
const share = row.share_url ? { url: row.share_url } : undefined
const revert = row.revert ?? undefined
const revert = row.revert
? {
messageID: MessageID.make(row.revert.messageID),
partID: row.revert.partID ? PartID.make(row.revert.partID) : undefined,
snapshot: row.revert.snapshot,
diff: row.revert.diff,
}
: undefined
return {
id: row.id,
slug: row.slug,
@ -136,7 +144,14 @@ export function toRow(info: Info) {
tokens_reasoning: (info.tokens ?? EmptyTokens).reasoning,
tokens_cache_read: (info.tokens ?? EmptyTokens).cache.read,
tokens_cache_write: (info.tokens ?? EmptyTokens).cache.write,
revert: info.revert ?? null,
revert: info.revert
? {
messageID: SessionMessageID.ID.make(info.revert.messageID),
partID: info.revert.partID,
snapshot: info.revert.snapshot,
diff: info.revert.diff,
}
: null,
permission: info.permission,
time_created: info.time.created,
time_updated: info.time.updated,

View file

@ -9,7 +9,7 @@ describe("public event manifest", () => {
expect(EventManifest.Definitions).toBe(SchemaEventManifest.Definitions)
expect(EventManifest.Latest).toBe(SchemaEventManifest.Latest)
expect(EventManifest.Durable).toBe(SchemaEventManifest.Durable)
expect(EventManifest.Latest.size).toBe(85)
expect(EventManifest.Latest.size).toBe(88)
expect(EventManifest.Latest.get("session.next.step.ended")).toBe(SessionEvent.Step.Ended)
expect(EventManifest.Latest.get("todo.updated")).toBe(Todo.Event.Updated)
expect(EventManifest.Latest.has("ide.installed")).toBe(false)

View file

@ -980,6 +980,28 @@ const scenarios: Scenario[] = [
headers: ctx.headers(),
}))
.json(404, object, "status"),
http.protected
.post("/api/session/{sessionID}/revert/stage", "v2.session.revert.stage")
.at((ctx) => ({
path: route("/api/session/{sessionID}/revert/stage", { sessionID: "ses_httpapi_missing" }),
headers: { ...ctx.headers(), "content-type": "application/json" },
body: { messageID: "msg_httpapi_missing" },
}))
.json(404, object, "status"),
http.protected
.post("/api/session/{sessionID}/revert/clear", "v2.session.revert.clear")
.at((ctx) => ({
path: route("/api/session/{sessionID}/revert/clear", { sessionID: "ses_httpapi_missing" }),
headers: ctx.headers(),
}))
.json(404, object, "status"),
http.protected
.post("/api/session/{sessionID}/revert/commit", "v2.session.revert.commit")
.at((ctx) => ({
path: route("/api/session/{sessionID}/revert/commit", { sessionID: "ses_httpapi_missing" }),
headers: ctx.headers(),
}))
.json(404, object, "status"),
http.protected
.get("/api/session/{sessionID}/message", "v2.session.messages")
.at((ctx) => ({