mirror of
https://github.com/anomalyco/opencode.git
synced 2026-05-17 01:52:55 +00:00
fix(session): accept legacy summary diffs (#26579)
Co-authored-by: Developer <temp@example.com>
This commit is contained in:
@@ -23,10 +23,10 @@ function span(id: string, value: { value: string; start: number; end: number })
|
||||
}
|
||||
}
|
||||
|
||||
function diff(kind: string, diffs: { file: string; patch?: string }[] | undefined) {
|
||||
function diff(kind: string, diffs: { file?: string; patch?: string }[] | undefined) {
|
||||
return diffs?.map((item, i) => ({
|
||||
...item,
|
||||
file: redact(`${kind}-file`, String(i), item.file),
|
||||
file: item.file === undefined ? undefined : redact(`${kind}-file`, String(i), item.file),
|
||||
patch: item.patch === undefined ? undefined : redact(`${kind}-patch`, String(i), item.patch),
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -148,7 +148,9 @@ function stateApi(sync: ReturnType<typeof useSync>): TuiPluginApi["state"] {
|
||||
return sync.data.session.length
|
||||
},
|
||||
diff(sessionID) {
|
||||
return sync.data.session_diff[sessionID] ?? []
|
||||
return (sync.data.session_diff[sessionID] ?? []).flatMap((item) =>
|
||||
item.file === undefined ? [] : [{ ...item, file: item.file }],
|
||||
)
|
||||
},
|
||||
todo(sessionID) {
|
||||
return sync.data.todo[sessionID] ?? []
|
||||
|
||||
@@ -134,6 +134,7 @@ export const layer = Layer.effect(
|
||||
.read<Snapshot.FileDiff[]>(["session_diff", input.sessionID])
|
||||
.pipe(Effect.catch(() => Effect.succeed([] as Snapshot.FileDiff[])))
|
||||
const next = diffs.map((item) => {
|
||||
if (item.file === undefined) return item
|
||||
const file = unquoteGitPath(item.file)
|
||||
if (file === item.file) return item
|
||||
return { ...item, file }
|
||||
|
||||
@@ -20,10 +20,10 @@ export const Patch = Schema.Struct({
|
||||
export type Patch = typeof Patch.Type
|
||||
|
||||
export const FileDiff = Schema.Struct({
|
||||
file: Schema.String,
|
||||
// Optional because legacy/imported `summary_diffs` on disk may omit
|
||||
// the patch text (see #26574). Required-Schema rejected the whole
|
||||
// /session/<id>/diff response and broke session loading on Desktop.
|
||||
// file details and patch text. Required Schema rejected the whole
|
||||
// session response and broke session loading on Desktop.
|
||||
file: Schema.optional(Schema.String),
|
||||
patch: Schema.optional(Schema.String),
|
||||
additions: NonNegativeInt,
|
||||
deletions: NonNegativeInt,
|
||||
|
||||
@@ -297,6 +297,36 @@ describe("session HttpApi", () => {
|
||||
),
|
||||
)
|
||||
|
||||
it.live(
|
||||
"serves sessions with migrated summary diffs missing file details",
|
||||
withTmp({ git: true, config: { formatter: false, lsp: false } }, (tmp) =>
|
||||
Effect.gen(function* () {
|
||||
const session = yield* createSession(tmp.path, { title: "legacy diff" })
|
||||
yield* Effect.sync(() =>
|
||||
Database.use((db) =>
|
||||
db
|
||||
.update(SessionTable)
|
||||
.set({
|
||||
summary_additions: 1,
|
||||
summary_deletions: 0,
|
||||
summary_files: 1,
|
||||
summary_diffs: [{ additions: 1, deletions: 0 }],
|
||||
})
|
||||
.where(eq(SessionTable.id, session.id))
|
||||
.run(),
|
||||
),
|
||||
)
|
||||
|
||||
const response = yield* request(pathFor(SessionPaths.get, { sessionID: session.id }), {
|
||||
headers: { "x-opencode-directory": tmp.path },
|
||||
})
|
||||
|
||||
expect(response.status).toBe(200)
|
||||
expect((yield* json<Session.Info>(response)).summary?.diffs).toEqual([{ additions: 1, deletions: 0 }])
|
||||
}),
|
||||
),
|
||||
)
|
||||
|
||||
it.live(
|
||||
"serves lifecycle mutation routes",
|
||||
withTmp({ git: true, config: { formatter: false, lsp: false, share: "disabled" } }, (tmp) =>
|
||||
|
||||
@@ -83,6 +83,26 @@ describe("Session.Info", () => {
|
||||
expect(Session.Info.zod.parse(input)).toEqual(input)
|
||||
})
|
||||
|
||||
test("accepts migrated summary diffs without file details", () => {
|
||||
const input = {
|
||||
id: sessionID,
|
||||
slug: "legacy-diff",
|
||||
projectID,
|
||||
directory: "/tmp/proj",
|
||||
title: "Legacy diff",
|
||||
version: "0.1.0",
|
||||
summary: {
|
||||
additions: 1,
|
||||
deletions: 0,
|
||||
files: 1,
|
||||
diffs: [{ additions: 1, deletions: 0 }],
|
||||
},
|
||||
time: { created: 1, updated: 2 },
|
||||
}
|
||||
expect(decode(input)).toEqual(input)
|
||||
expect(Session.Info.zod.parse(input)).toEqual(input)
|
||||
})
|
||||
|
||||
test("rejects unbranded session id", () => {
|
||||
const bad = { id: "not-a-session-id" } as unknown
|
||||
expect(() => decode(bad)).toThrow()
|
||||
|
||||
@@ -238,7 +238,7 @@ it.live("tool execution produces non-empty session diff (snapshot race)", () =>
|
||||
expect(tool?.state.status).toBe("completed")
|
||||
|
||||
// Poll for diff — summarize() is fire-and-forget
|
||||
let diff: Array<{ file: string }> = []
|
||||
let diff: Array<{ file?: string }> = []
|
||||
for (let i = 0; i < 50; i++) {
|
||||
diff = yield* summary.diff({ sessionID: session.id })
|
||||
if (diff.length > 0) break
|
||||
|
||||
Reference in New Issue
Block a user