fix(core): commit staged revert before admitting new prompt (#34338)
This commit is contained in:
@@ -370,7 +370,11 @@ export const layer = Layer.effect(
|
|||||||
prompt: Effect.fn("V2Session.prompt")((input) =>
|
prompt: Effect.fn("V2Session.prompt")((input) =>
|
||||||
Effect.uninterruptible(
|
Effect.uninterruptible(
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
yield* result.get(input.sessionID)
|
const session = yield* result.get(input.sessionID)
|
||||||
|
// A staged revert must be committed before admitting new input so the prompt
|
||||||
|
// continues from the reverted boundary rather than stale post-boundary history.
|
||||||
|
if (session.revert)
|
||||||
|
yield* SessionRevert.commit(session).pipe(Effect.provideService(EventV2.Service, events))
|
||||||
const prompt = resolvePrompt(input.prompt)
|
const prompt = resolvePrompt(input.prompt)
|
||||||
const messageID = input.id ?? SessionMessage.ID.create()
|
const messageID = input.id ?? SessionMessage.ID.create()
|
||||||
const delivery = input.delivery ?? "steer"
|
const delivery = input.delivery ?? "steer"
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
import { describe, expect } from "bun:test"
|
import { describe, expect } from "bun:test"
|
||||||
import { DateTime, Effect, Fiber, Layer, Stream } from "effect"
|
import { DateTime, Effect, Fiber, Layer, Schema, Stream } from "effect"
|
||||||
import { eq } from "drizzle-orm"
|
import { eq } from "drizzle-orm"
|
||||||
import { Database } from "@opencode-ai/core/database/database"
|
import { Database } from "@opencode-ai/core/database/database"
|
||||||
import { EventV2 } from "@opencode-ai/core/event"
|
import { EventV2 } from "@opencode-ai/core/event"
|
||||||
import { EventTable } from "@opencode-ai/core/event/sql"
|
import { EventTable } from "@opencode-ai/core/event/sql"
|
||||||
import { SessionEvent } from "@opencode-ai/core/session/event"
|
import { SessionEvent } from "@opencode-ai/core/session/event"
|
||||||
|
import { ModelV2 } from "@opencode-ai/core/model"
|
||||||
|
import { ProviderV2 } from "@opencode-ai/core/provider"
|
||||||
import { Project } from "@opencode-ai/core/project"
|
import { Project } from "@opencode-ai/core/project"
|
||||||
import { ProjectTable } from "@opencode-ai/core/project/sql"
|
import { ProjectTable } from "@opencode-ai/core/project/sql"
|
||||||
import { AbsolutePath } from "@opencode-ai/core/schema"
|
import { AbsolutePath } from "@opencode-ai/core/schema"
|
||||||
@@ -110,6 +112,21 @@ const eventCount = (type: string) =>
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const encodeMessage = Schema.encodeSync(SessionMessage.Message)
|
||||||
|
const assistantRow = (id: SessionMessage.ID, seq: number) => {
|
||||||
|
const { id: _, type, ...data } = encodeMessage(
|
||||||
|
SessionMessage.Assistant.make({
|
||||||
|
id,
|
||||||
|
type: "assistant",
|
||||||
|
agent: "build",
|
||||||
|
model: { id: ModelV2.ID.make("model"), providerID: ProviderV2.ID.make("provider") },
|
||||||
|
content: [],
|
||||||
|
time: { created: DateTime.makeUnsafe(0) },
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
return { id, session_id: sessionID, type, seq, time_created: 0, data }
|
||||||
|
}
|
||||||
|
|
||||||
describe("SessionV2.prompt", () => {
|
describe("SessionV2.prompt", () => {
|
||||||
it.effect("exposes the execution registry", () =>
|
it.effect("exposes the execution registry", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
@@ -201,6 +218,39 @@ describe("SessionV2.prompt", () => {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
it.effect("commits a staged revert before admitting a new prompt", () =>
|
||||||
|
Effect.gen(function* () {
|
||||||
|
yield* setup
|
||||||
|
const session = yield* SessionV2.Service
|
||||||
|
const events = yield* EventV2.Service
|
||||||
|
const { db } = yield* Database.Service
|
||||||
|
|
||||||
|
const boundary = yield* session.prompt({
|
||||||
|
sessionID,
|
||||||
|
prompt: Prompt.make({ text: "boundary" }),
|
||||||
|
resume: false,
|
||||||
|
})
|
||||||
|
yield* SessionInput.promoteSteers(db, events, sessionID, Number.MAX_SAFE_INTEGER)
|
||||||
|
const stale = SessionMessage.ID.make("msg_stale_assistant")
|
||||||
|
yield* db.insert(SessionMessageTable).values(assistantRow(stale, 100)).run().pipe(Effect.orDie)
|
||||||
|
yield* events.publish(SessionEvent.RevertEvent.Staged, {
|
||||||
|
sessionID,
|
||||||
|
timestamp: yield* DateTime.now,
|
||||||
|
revert: { messageID: boundary.id, files: [] },
|
||||||
|
})
|
||||||
|
expect((yield* session.get(sessionID)).revert?.messageID).toBe(boundary.id)
|
||||||
|
|
||||||
|
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "after revert" }), resume: false })
|
||||||
|
|
||||||
|
expect((yield* session.get(sessionID)).revert).toBeUndefined()
|
||||||
|
expect(
|
||||||
|
(yield* db.select({ id: SessionMessageTable.id }).from(SessionMessageTable).all().pipe(Effect.orDie)).map(
|
||||||
|
(row) => row.id,
|
||||||
|
),
|
||||||
|
).not.toContain(stale)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
it.effect("resolves attachment MIME before admission", () =>
|
it.effect("resolves attachment MIME before admission", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
yield* setup
|
yield* setup
|
||||||
|
|||||||
Reference in New Issue
Block a user