fix(opencode): fix full session fork (#26811)

This commit is contained in:
Aiden Cline
2026-05-11 00:26:36 -05:00
committed by GitHub
parent 7235c9c9b8
commit 518264fcd9
4 changed files with 25 additions and 9 deletions
@@ -86,7 +86,7 @@ export const layer: Layer.Layer<Service, never, Project.Service | InstanceBootst
) )
const disposeContext = Effect.fn("InstanceStore.disposeContext")(function* (ctx: InstanceContext) { const disposeContext = Effect.fn("InstanceStore.disposeContext")(function* (ctx: InstanceContext) {
yield* Effect.logInfo("disposing instance", { directory: ctx.directory }) yield* Effect.logInfo("disposing instance").pipe(Effect.annotateLogs("directory", ctx.directory))
yield* Effect.promise(() => runDisposers(ctx.directory)) yield* Effect.promise(() => runDisposers(ctx.directory))
yield* emitDisposed({ directory: ctx.directory, project: ctx.project.id }) yield* emitDisposed({ directory: ctx.directory, project: ctx.project.id })
}) })
@@ -109,7 +109,7 @@ export const layer: Layer.Layer<Service, never, Project.Service | InstanceBootst
const entry: Entry = { deferred: Deferred.makeUnsafe<InstanceContext>() } const entry: Entry = { deferred: Deferred.makeUnsafe<InstanceContext>() }
cache.set(directory, entry) cache.set(directory, entry)
yield* Effect.gen(function* () { yield* Effect.gen(function* () {
yield* Effect.logInfo("creating instance", { directory }) yield* Effect.logInfo("creating instance").pipe(Effect.annotateLogs("directory", directory))
yield* completeLoad(directory, input, entry) yield* completeLoad(directory, input, entry)
}).pipe(Effect.forkIn(scope, { startImmediately: true })) }).pipe(Effect.forkIn(scope, { startImmediately: true }))
return yield* restore(Deferred.await(entry.deferred)) return yield* restore(Deferred.await(entry.deferred))
@@ -125,7 +125,7 @@ export const layer: Layer.Layer<Service, never, Project.Service | InstanceBootst
const entry: Entry = { deferred: Deferred.makeUnsafe<InstanceContext>() } const entry: Entry = { deferred: Deferred.makeUnsafe<InstanceContext>() }
cache.set(directory, entry) cache.set(directory, entry)
yield* Effect.gen(function* () { yield* Effect.gen(function* () {
yield* Effect.logInfo("reloading instance", { directory }) yield* Effect.logInfo("reloading instance").pipe(Effect.annotateLogs("directory", directory))
if (previous) { if (previous) {
yield* Deferred.await(previous.deferred).pipe(Effect.ignore) yield* Deferred.await(previous.deferred).pipe(Effect.ignore)
yield* Effect.promise(() => runDisposers(directory)) yield* Effect.promise(() => runDisposers(directory))
@@ -236,9 +236,9 @@ export const SessionApi = HttpApi.make("session")
HttpApiEndpoint.post("fork", SessionPaths.fork, { HttpApiEndpoint.post("fork", SessionPaths.fork, {
params: { sessionID: SessionID }, params: { sessionID: SessionID },
query: WorkspaceRoutingQuery, query: WorkspaceRoutingQuery,
payload: ForkPayload, payload: Schema.optional(ForkPayload),
success: described(Session.Info, "200"), success: described(Session.Info, "200"),
error: ApiNotFoundError, error: [HttpApiError.BadRequest, ApiNotFoundError],
}).annotateMerge( }).annotateMerge(
OpenApi.annotations({ OpenApi.annotations({
identifier: "session.fork", identifier: "session.fork",
@@ -187,13 +187,30 @@ export const sessionHandlers = HttpApiBuilder.group(InstanceHttpApi, "session",
const fork = Effect.fn("SessionHttpApi.fork")(function* (ctx: { const fork = Effect.fn("SessionHttpApi.fork")(function* (ctx: {
params: { sessionID: SessionID } params: { sessionID: SessionID }
payload: typeof ForkPayload.Type payload?: typeof ForkPayload.Type
}) { }) {
return yield* SessionError.mapStorageNotFound( return yield* SessionError.mapStorageNotFound(
session.fork({ sessionID: ctx.params.sessionID, messageID: ctx.payload.messageID }), session.fork({ sessionID: ctx.params.sessionID, messageID: ctx.payload?.messageID }),
) )
}) })
const forkRaw = Effect.fn("SessionHttpApi.forkRaw")(function* (ctx: {
params: { sessionID: SessionID }
request: HttpServerRequest.HttpServerRequest
}) {
const body = yield* Effect.orDie(ctx.request.text)
if (body.trim().length === 0) return yield* fork({ params: ctx.params })
const json = yield* Effect.try({
try: () => JSON.parse(body) as unknown,
catch: () => new HttpApiError.BadRequest({}),
})
const payload = yield* Schema.decodeUnknownEffect(ForkPayload)(json).pipe(
Effect.mapError(() => new HttpApiError.BadRequest({})),
)
return yield* fork({ params: ctx.params, payload })
})
const abort = Effect.fn("SessionHttpApi.abort")(function* (ctx: { params: { sessionID: SessionID } }) { const abort = Effect.fn("SessionHttpApi.abort")(function* (ctx: { params: { sessionID: SessionID } }) {
yield* promptSvc.cancel(ctx.params.sessionID) yield* promptSvc.cancel(ctx.params.sessionID)
return true return true
@@ -373,7 +390,7 @@ export const sessionHandlers = HttpApiBuilder.group(InstanceHttpApi, "session",
.handleRaw("create", createRaw) .handleRaw("create", createRaw)
.handle("remove", remove) .handle("remove", remove)
.handle("update", update) .handle("update", update)
.handle("fork", fork) .handleRaw("fork", forkRaw)
.handle("abort", abort) .handle("abort", abort)
.handle("init", init) .handle("init", init)
.handle("share", share) .handle("share", share)
@@ -356,7 +356,6 @@ describe("session HttpApi", () => {
const forked = yield* requestJson<Session.Info>(pathFor(SessionPaths.fork, { sessionID: created.id }), { const forked = yield* requestJson<Session.Info>(pathFor(SessionPaths.fork, { sessionID: created.id }), {
method: "POST", method: "POST",
headers, headers,
body: JSON.stringify({}),
}) })
expect(forked.id).not.toBe(created.id) expect(forked.id).not.toBe(created.id)