|
|
@@ -1,10 +1,12 @@
|
|
|
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 { Database } from "@opencode-ai/core/database/database"
|
|
|
import { EventV2 } from "@opencode-ai/core/event"
|
|
|
import { EventTable } from "@opencode-ai/core/event/sql"
|
|
|
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 { ProjectTable } from "@opencode-ai/core/project/sql"
|
|
|
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", () => {
|
|
|
it.effect("exposes the execution registry", () =>
|
|
|
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", () =>
|
|
|
Effect.gen(function* () {
|
|
|
yield* setup
|