fix(opencode): preserve v1 database compatibility (#42444)
This commit is contained in:
@@ -12,7 +12,6 @@ import { SessionMessage } from "./message"
|
|||||||
import { SessionMessageUpdater } from "./message-updater"
|
import { SessionMessageUpdater } from "./message-updater"
|
||||||
import { SessionInput } from "./input"
|
import { SessionInput } from "./input"
|
||||||
import { WorkspaceV2 } from "../workspace"
|
import { WorkspaceV2 } from "../workspace"
|
||||||
import { SessionContextEpoch } from "./context-epoch"
|
|
||||||
import { MessageTable, PartTable, SessionInputTable, SessionMessageTable, SessionTable } from "./sql"
|
import { MessageTable, PartTable, SessionInputTable, SessionMessageTable, SessionTable } from "./sql"
|
||||||
import type { DeepMutable } from "../schema"
|
import type { DeepMutable } from "../schema"
|
||||||
|
|
||||||
@@ -253,7 +252,6 @@ const layer = Layer.effectDiscard(
|
|||||||
.where(eq(SessionTable.id, event.data.sessionID))
|
.where(eq(SessionTable.id, event.data.sessionID))
|
||||||
.run()
|
.run()
|
||||||
.pipe(Effect.orDie)
|
.pipe(Effect.orDie)
|
||||||
yield* SessionContextEpoch.reset(db, event.data.sessionID)
|
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
yield* events.project(SessionV1.Event.Deleted, (event) =>
|
yield* events.project(SessionV1.Event.Deleted, (event) =>
|
||||||
@@ -449,7 +447,6 @@ const layer = Layer.effectDiscard(
|
|||||||
.where(eq(SessionTable.id, event.data.sessionID))
|
.where(eq(SessionTable.id, event.data.sessionID))
|
||||||
.run()
|
.run()
|
||||||
.pipe(Effect.orDie)
|
.pipe(Effect.orDie)
|
||||||
yield* SessionContextEpoch.reset(db, event.data.sessionID)
|
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { describe, expect } from "bun:test"
|
import { describe, expect } from "bun:test"
|
||||||
import { DateTime, Effect, Schema } from "effect"
|
import { DateTime, Effect, Schema } from "effect"
|
||||||
import { asc, eq } from "drizzle-orm"
|
import { asc, eq, sql } from "drizzle-orm"
|
||||||
import { Database } from "@opencode-ai/core/database/database"
|
import { Database } from "@opencode-ai/core/database/database"
|
||||||
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
||||||
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
|
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
|
||||||
@@ -22,6 +22,7 @@ import { SessionInput } from "@opencode-ai/core/session/input"
|
|||||||
import { SessionInputTable, SessionMessageTable, SessionTable } from "@opencode-ai/core/session/sql"
|
import { SessionInputTable, SessionMessageTable, SessionTable } from "@opencode-ai/core/session/sql"
|
||||||
import { testEffect } from "./lib/effect"
|
import { testEffect } from "./lib/effect"
|
||||||
import { Snapshot } from "@opencode-ai/core/snapshot"
|
import { Snapshot } from "@opencode-ai/core/snapshot"
|
||||||
|
import { Location } from "@opencode-ai/core/location"
|
||||||
|
|
||||||
const it = testEffect(AppNodeBuilder.build(LayerNode.group([Database.node, EventV2.node, SessionProjector.node])))
|
const it = testEffect(AppNodeBuilder.build(LayerNode.group([Database.node, EventV2.node, SessionProjector.node])))
|
||||||
const sessionsLayer = AppNodeBuilder.build(SessionV2.node, [[SessionExecution.node, SessionExecution.noopLayer]])
|
const sessionsLayer = AppNodeBuilder.build(SessionV2.node, [[SessionExecution.node, SessionExecution.noopLayer]])
|
||||||
@@ -44,6 +45,39 @@ const assistantRow = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
describe("SessionProjector", () => {
|
describe("SessionProjector", () => {
|
||||||
|
it.effect("projects moved sessions without the transitional context epoch table", () =>
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const { db } = yield* Database.Service
|
||||||
|
const events = yield* EventV2.Service
|
||||||
|
yield* db
|
||||||
|
.insert(ProjectTable)
|
||||||
|
.values({ id: Project.ID.global, worktree: AbsolutePath.make("/project"), sandboxes: [] })
|
||||||
|
.run()
|
||||||
|
yield* db
|
||||||
|
.insert(SessionTable)
|
||||||
|
.values({
|
||||||
|
id: sessionID,
|
||||||
|
project_id: Project.ID.global,
|
||||||
|
slug: "test",
|
||||||
|
directory: "/project",
|
||||||
|
title: "test",
|
||||||
|
version: "test",
|
||||||
|
})
|
||||||
|
.run()
|
||||||
|
yield* db.run(sql`DROP TABLE session_context_epoch`)
|
||||||
|
|
||||||
|
yield* events.publish(SessionEvent.Moved, {
|
||||||
|
sessionID,
|
||||||
|
timestamp: DateTime.makeUnsafe(1),
|
||||||
|
location: Location.Ref.make({ directory: AbsolutePath.make("/project/subdir") }),
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(yield* db.select({ directory: SessionTable.directory }).from(SessionTable).get()).toEqual({
|
||||||
|
directory: "/project/subdir",
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
it.effect("projects staged, cleared, and committed reverts", () =>
|
it.effect("projects staged, cleared, and committed reverts", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const db = (yield* Database.Service).db
|
const db = (yield* Database.Service).db
|
||||||
|
|||||||
@@ -703,13 +703,6 @@ describe("SessionRunnerLLM", () => {
|
|||||||
timestamp: DateTime.makeUnsafe(1),
|
timestamp: DateTime.makeUnsafe(1),
|
||||||
location: Location.Ref.make({ directory: AbsolutePath.make("/moved") }),
|
location: Location.Ref.make({ directory: AbsolutePath.make("/moved") }),
|
||||||
})
|
})
|
||||||
expect(
|
|
||||||
yield* db
|
|
||||||
.select()
|
|
||||||
.from(SessionContextEpochTable)
|
|
||||||
.where(eq(SessionContextEpochTable.session_id, sessionID))
|
|
||||||
.get(),
|
|
||||||
).toBeUndefined()
|
|
||||||
|
|
||||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Second" }), resume: false })
|
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Second" }), resume: false })
|
||||||
const exit = yield* session.resume(sessionID).pipe(Effect.exit)
|
const exit = yield* session.resume(sessionID).pipe(Effect.exit)
|
||||||
|
|||||||
@@ -714,6 +714,7 @@ const layer = Layer.effect(
|
|||||||
})
|
})
|
||||||
|
|
||||||
const list = Effect.fn("Workspace.list")(function* (project: Project.Info) {
|
const list = Effect.fn("Workspace.list")(function* (project: Project.Info) {
|
||||||
|
if (!flags.experimentalWorkspaces) return []
|
||||||
return (yield* db
|
return (yield* db
|
||||||
.select()
|
.select()
|
||||||
.from(WorkspaceTable)
|
.from(WorkspaceTable)
|
||||||
@@ -851,6 +852,7 @@ const layer = Layer.effect(
|
|||||||
})
|
})
|
||||||
|
|
||||||
const startWorkspaceSyncing = Effect.fn("Workspace.startWorkspaceSyncing")(function* (projectID: ProjectV2.ID) {
|
const startWorkspaceSyncing = Effect.fn("Workspace.startWorkspaceSyncing")(function* (projectID: ProjectV2.ID) {
|
||||||
|
if (!flags.experimentalWorkspaces) return
|
||||||
const rows = yield* db
|
const rows = yield* db
|
||||||
.selectDistinct({ workspace: WorkspaceTable })
|
.selectDistinct({ workspace: WorkspaceTable })
|
||||||
.from(WorkspaceTable)
|
.from(WorkspaceTable)
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { Effect, Exit, Fiber, Layer, Schema } from "effect"
|
|||||||
import { HttpServer, HttpServerRequest, HttpServerResponse } from "effect/unstable/http"
|
import { HttpServer, HttpServerRequest, HttpServerResponse } from "effect/unstable/http"
|
||||||
import { eq } from "drizzle-orm"
|
import { eq } from "drizzle-orm"
|
||||||
import { GlobalBus, type GlobalEvent } from "@/bus/global"
|
import { GlobalBus, type GlobalEvent } from "@/bus/global"
|
||||||
|
import { Project } from "@/project/project"
|
||||||
import { Database } from "@opencode-ai/core/database/database"
|
import { Database } from "@opencode-ai/core/database/database"
|
||||||
import { ProjectV2 } from "@opencode-ai/core/project"
|
import { ProjectV2 } from "@opencode-ai/core/project"
|
||||||
import { ProjectTable } from "@opencode-ai/core/project/sql"
|
import { ProjectTable } from "@opencode-ai/core/project/sql"
|
||||||
@@ -133,6 +134,9 @@ const startWorkspaceSyncingWithFlag = (projectID: ProjectV2.ID, experimentalWork
|
|||||||
Workspace.use.startWorkspaceSyncing(projectID).pipe(Effect.provide(workspaceLayer(experimentalWorkspaces))),
|
Workspace.use.startWorkspaceSyncing(projectID).pipe(Effect.provide(workspaceLayer(experimentalWorkspaces))),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const listWithFlag = (project: Project.Info, experimentalWorkspaces: boolean) =>
|
||||||
|
Effect.runPromise(Workspace.use.list(project).pipe(Effect.provide(workspaceLayer(experimentalWorkspaces))))
|
||||||
|
|
||||||
function captureGlobalEvents() {
|
function captureGlobalEvents() {
|
||||||
const events: GlobalEvent[] = []
|
const events: GlobalEvent[] = []
|
||||||
const handler = (event: GlobalEvent) => events.push(event)
|
const handler = (event: GlobalEvent) => events.push(event)
|
||||||
@@ -417,6 +421,18 @@ describe("workspace CRUD", () => {
|
|||||||
{ git: true },
|
{ git: true },
|
||||||
)
|
)
|
||||||
|
|
||||||
|
it.instance(
|
||||||
|
"list is disabled by the experimental workspace flag",
|
||||||
|
() =>
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const instance = yield* requireInstance
|
||||||
|
yield* insertWorkspace(workspaceInfo(instance.project.id, "manual"))
|
||||||
|
|
||||||
|
expect(yield* Effect.promise(() => listWithFlag(instance.project, false))).toEqual([])
|
||||||
|
}),
|
||||||
|
{ git: true },
|
||||||
|
)
|
||||||
|
|
||||||
it.instance(
|
it.instance(
|
||||||
"create configures, persists, creates, starts local sync, and passes environment",
|
"create configures, persists, creates, starts local sync, and passes environment",
|
||||||
() =>
|
() =>
|
||||||
|
|||||||
Reference in New Issue
Block a user