fix(core): omit running shells from forks (#44001)
This commit is contained in:
@@ -183,6 +183,7 @@ const projectFork = Effect.fn("SessionProjector.projectFork")(function* (
|
||||
lt(SessionMessageTable.seq, copiedSeq + 1),
|
||||
// Terminal events for active projections stay on the parent, so forks copy only settled history.
|
||||
sql`${SessionMessageTable.type} != 'assistant' or json_extract(${SessionMessageTable.data}, '$.time.completed') is not null`,
|
||||
sql`${SessionMessageTable.type} != 'shell' or json_extract(${SessionMessageTable.data}, '$.status') != 'running'`,
|
||||
sql`${SessionMessageTable.type} != 'compaction' or json_extract(${SessionMessageTable.data}, '$.status') != 'running'`,
|
||||
),
|
||||
)
|
||||
|
||||
@@ -4,6 +4,7 @@ import fs from "fs/promises"
|
||||
import path from "path"
|
||||
import { DateTime, Effect, Layer, Stream } from "effect"
|
||||
import { Money } from "@opencode-ai/schema/money"
|
||||
import { Shell } from "@opencode-ai/schema/shell"
|
||||
import { Agent } from "@opencode-ai/core/agent"
|
||||
import { asc, eq } from "drizzle-orm"
|
||||
import { Database } from "@opencode-ai/core/database/database"
|
||||
@@ -451,6 +452,49 @@ describe("Session.create", () => {
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("copies only settled shell messages into forks", () =>
|
||||
Effect.gen(function* () {
|
||||
const session = yield* Session.Service
|
||||
const bus = yield* Bus.Service
|
||||
const { db } = yield* Database.Service
|
||||
const parent = yield* session.create({ location })
|
||||
yield* session.prompt({ sessionID: parent.id, text: "Run a shell", resume: false })
|
||||
yield* SessionInbox.promote(db, bus, parent.id, "steer")
|
||||
const shell = Shell.Info.make({
|
||||
id: Shell.ID.make("sh_fork_running"),
|
||||
status: "running",
|
||||
command: "sleep 10",
|
||||
cwd: location.directory,
|
||||
shell: "/bin/sh",
|
||||
file: "/tmp/sh_fork_running.out",
|
||||
metadata: {},
|
||||
time: { started: 0 },
|
||||
})
|
||||
yield* bus.publish(SessionEvent.Shell.Started, { sessionID: parent.id, shell })
|
||||
|
||||
const running = yield* session.fork({ sessionID: parent.id, boundary: { type: "through" } })
|
||||
|
||||
expect(yield* session.context(parent.id)).toMatchObject([
|
||||
{ type: "user", text: "Run a shell" },
|
||||
{ type: "shell", command: "sleep 10", status: "running" },
|
||||
])
|
||||
expect(yield* session.context(running.id)).toMatchObject([{ type: "user", text: "Run a shell" }])
|
||||
|
||||
yield* bus.publish(SessionEvent.Shell.Ended, {
|
||||
sessionID: parent.id,
|
||||
shell: { ...shell, status: "exited", exit: 0, time: { started: 0, completed: 1 } },
|
||||
output: { output: "complete", cursor: 8, size: 8, truncated: false },
|
||||
})
|
||||
const completed = yield* session.fork({ sessionID: parent.id, boundary: { type: "through" } })
|
||||
|
||||
expect(yield* session.context(running.id)).toMatchObject([{ type: "user", text: "Run a shell" }])
|
||||
expect(yield* session.context(completed.id)).toMatchObject([
|
||||
{ type: "user", text: "Run a shell" },
|
||||
{ type: "shell", command: "sleep 10", status: "exited", output: { output: "complete" } },
|
||||
])
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("rejects forking an empty session", () =>
|
||||
Effect.gen(function* () {
|
||||
const session = yield* Session.Service
|
||||
|
||||
Reference in New Issue
Block a user