From e67f7137370fedb63cae70c432b71a0f51beb7b2 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Mon, 19 Oct 2026 01:14:30 -0700 Subject: [PATCH] fix(server): avoid booting unloaded locations on pending form and permission reads --- packages/app/src/session/requests/model.ts | 30 +++++++++++---- .../src/shell/layout/project-avatar-state.ts | 4 +- packages/core/src/effect/app-node-builder.ts | 4 +- packages/core/src/instance.ts | 2 +- packages/core/src/instance/service.ts | 29 +++++++++++++- packages/core/src/location-services.ts | 6 +-- packages/core/test/instance-vanilla.test.ts | 6 +-- .../test/plugin/supervisor-reload.test.ts | 6 +-- packages/core/test/plugin/supervisor.test.ts | 6 +-- packages/core/test/session-generate.test.ts | 1 + packages/core/test/session-move.test.ts | 8 +++- packages/core/test/session-owned.test.ts | 1 + packages/core/test/session-remove.test.ts | 38 ++++++++++++++++++- packages/protocol/openapi.json | 22 ++--------- packages/protocol/src/groups/permission.ts | 3 +- packages/protocol/src/groups/session.ts | 16 ++++---- packages/sdk/src/internal/instances.ts | 7 +++- packages/sdk/test/instances.test.ts | 4 +- packages/server/src/handlers/permission.ts | 8 ++-- packages/server/src/handlers/session.ts | 17 +++++++-- packages/server/test/fetch.test.ts | 10 ++--- .../server/test/session-instances.test.ts | 2 + services/www/openapi.json | 22 ++--------- services/www/public/openapi.json | 22 ++--------- 24 files changed, 154 insertions(+), 120 deletions(-) diff --git a/packages/app/src/session/requests/model.ts b/packages/app/src/session/requests/model.ts index 844223d20458..48d227dd60cb 100644 --- a/packages/app/src/session/requests/model.ts +++ b/packages/app/src/session/requests/model.ts @@ -19,6 +19,21 @@ export function createSessionRequestModel() { const data = useData() const language = useLanguage() const settings = useSettings() + + const familySessions = createMemo(() => { + const id = params.id + + if (!id) return [] + + return [...new Set([id, ...data.session.family(id)])].flatMap((sessionID) => { + const info = data.session.get(sessionID) + + return info ? [info] : [] + }) + }) + + const treeIDs = createMemo(() => sessionTreeIDs(familySessions(), params.id)) + createEffect(() => { const id = params.id @@ -28,16 +43,15 @@ export function createSessionRequestModel() { ) }) createEffect(() => { - const id = params.id + if (serverSDK.connection.status() !== "connected") return + const ids = treeIDs() - if (!id || serverSDK.connection.status() !== "connected") return - void Promise.all( - sessionTreeIDs(data.session.list(), id).map((sessionID) => data.session.form.sync(sessionID)), - ).catch(() => undefined) + if (!ids.length) return + void Promise.all(ids.map((sessionID) => data.session.form.sync(sessionID))).catch(() => undefined) }) const formRequest = createMemo((): FormInfo | undefined => { - return sessionFormRequest(data.session.list(), data.session.form.list, params.id) + return sessionFormRequest(familySessions(), data.session.form.list, params.id) }) // MCP elicitations are owned by the Location rather than a Session, so they are listed per Location. @@ -102,7 +116,7 @@ export function createSessionRequestModel() { if (settings.permissions.autoApproves(project)) return - return sessionPermissionRequest(data.session.list(), data.session.permission.list, id) + return sessionPermissionRequest(familySessions(), data.session.permission.list, id) }) const blocked = createMemo(() => { @@ -131,7 +145,7 @@ export function createSessionRequestModel() { const running = createSessionBackground({ sessionID: owner, messages: data.session.message.list, - sessions: data.session.list, + sessions: familySessions, status: data.session.status, shells: () => data.shell.list({ directory: sdk().directory }), }) diff --git a/packages/app/src/shell/layout/project-avatar-state.ts b/packages/app/src/shell/layout/project-avatar-state.ts index d4fe45146bff..01298d33773f 100644 --- a/packages/app/src/shell/layout/project-avatar-state.ts +++ b/packages/app/src/shell/layout/project-avatar-state.ts @@ -21,7 +21,6 @@ export function useSessionTabAvatarState( if (!data) return [] - if (!root?.()) return data.session.list() const id = sessionId() return [...new Set([id, ...data.session.family(id)])].flatMap((id) => { @@ -68,8 +67,7 @@ export function useSessionTabAvatarState( const busy = (id: string) => data.session.status(id) === "running" || data.session.pending.list(id).some((item) => item.type !== "synthetic") - if (root?.()) - return data.session.list().some((session) => data.session.root(session.id) === sessionId() && busy(session.id)) + if (root?.()) return [...new Set([sessionId(), ...data.session.family(sessionId())])].some(busy) return busy(sessionId()) }) diff --git a/packages/core/src/effect/app-node-builder.ts b/packages/core/src/effect/app-node-builder.ts index dfefbf8d6620..546e8becf544 100644 --- a/packages/core/src/effect/app-node-builder.ts +++ b/packages/core/src/effect/app-node-builder.ts @@ -11,9 +11,7 @@ const instances = makeGlobalNode({ Instance.Service, Effect.gen(function* () { const locations = yield* LocationServiceMap.Service - return Instance.Service.of({ - provide: (session) => Effect.provide(locations.get(session.location)), - }) + return Instance.fromLocations(locations) }), ), deps: [LocationServiceMap.node], diff --git a/packages/core/src/instance.ts b/packages/core/src/instance.ts index 9bcde2c59225..241cf5ad2862 100644 --- a/packages/core/src/instance.ts +++ b/packages/core/src/instance.ts @@ -55,7 +55,7 @@ import { ToolOutput } from "./tool-output.js" import { Vcs } from "./vcs.js" export * as Instance from "./instance.js" -export { Service, node, type Interface } from "./instance/service.js" +export { Service, fromLocations, node, provideContextEffectOption, type Interface } from "./instance/service.js" const nodes = [ Location.node, diff --git a/packages/core/src/instance/service.ts b/packages/core/src/instance/service.ts index f23d28f49f2d..9cc95644b069 100644 --- a/packages/core/src/instance/service.ts +++ b/packages/core/src/instance/service.ts @@ -1,7 +1,8 @@ export * as Instance from "./service.js" export type { Services } from "../instance.js" -import { Context, type Effect } from "effect" +import { Context, Effect, type LayerMap, Option, Scope } from "effect" +import type { Location } from "@opencode/schema/location" import type { Session } from "@opencode/schema/session" import { Node } from "@opencode/util/effect/app-node" import { LayerNode } from "@opencode/util/effect/layer-node" @@ -12,6 +13,32 @@ export interface Interface { readonly provide: ( session: Session.Info, ) => (effect: Effect.Effect) => Effect.Effect> + readonly provideIfLoaded: ( + session: Session.Info, + ) => (effect: Effect.Effect) => Effect.Effect, E, Exclude> +} + +export function provideContextEffectOption( + get: (key: K) => Effect.Effect>, E0, Scope.Scope>, + key: K, +) { + return (effect: Effect.Effect): Effect.Effect, E, Exclude> => + Effect.scopedWith((scope) => + Effect.gen(function* () { + const context = yield* get(key).pipe(Scope.provide(scope), Effect.option, Effect.map(Option.flatten)) + if (Option.isNone(context)) return Option.none() + return Option.some(yield* effect.pipe(Effect.provide(context.value))) + }), + ) +} + +export function fromLocations( + locations: Pick, "get" | "contextEffectOption">, +): Interface { + return Service.of({ + provide: (session) => Effect.provide(locations.get(session.location)), + provideIfLoaded: (session) => provideContextEffectOption(locations.contextEffectOption, session.location), + }) } export class Service extends Context.Service()("@opencode/Instance") {} diff --git a/packages/core/src/location-services.ts b/packages/core/src/location-services.ts index 8498df7f6f5b..191b44f3d6c3 100644 --- a/packages/core/src/location-services.ts +++ b/packages/core/src/location-services.ts @@ -73,11 +73,7 @@ export function buildLocationServiceMap( } // Cached instances borrow their owner instead of retaining its Layer scope. const bindings: LayerNode.Replacements = [ - Instance.node.replace( - Layer.succeed(Instance.Service, { - provide: (session) => Effect.provide(map.get(session.location)), - }), - ), + Instance.node.replace(Layer.succeed(Instance.Service, Instance.fromLocations(map))), ...replacements, LocationServiceMap.node.replace(Layer.succeed(LocationServiceMap.Service, map)), ] diff --git a/packages/core/test/instance-vanilla.test.ts b/packages/core/test/instance-vanilla.test.ts index 75d450aa56d4..78267d5d6062 100644 --- a/packages/core/test/instance-vanilla.test.ts +++ b/packages/core/test/instance-vanilla.test.ts @@ -50,11 +50,7 @@ const instances = Layer.effect( Global.node.replace(tempGlobalLayer), offlineModels, LocationServiceMap.node.replace(Layer.succeed(LocationServiceMap.Service, map)), - Instance.node.replace( - Layer.succeed(Instance.Service, { - provide: (session) => Effect.provide(map.get(session.location)), - }), - ), + Instance.node.replace(Layer.succeed(Instance.Service, Instance.fromLocations(map))), ] return map }), diff --git a/packages/core/test/plugin/supervisor-reload.test.ts b/packages/core/test/plugin/supervisor-reload.test.ts index e2ed79e23f95..f51ce3828d84 100644 --- a/packages/core/test/plugin/supervisor-reload.test.ts +++ b/packages/core/test/plugin/supervisor-reload.test.ts @@ -71,11 +71,7 @@ const instances = Layer.effect( Npm.node.replace(npmLayer), Watcher.node.replace(Layer.succeed(Watcher.Service, watcher)), LocationServiceMap.node.replace(Layer.succeed(LocationServiceMap.Service, map)), - Instance.node.replace( - Layer.succeed(Instance.Service, { - provide: (session) => Effect.provide(map.get(session.location)), - }), - ), + Instance.node.replace(Layer.succeed(Instance.Service, Instance.fromLocations(map))), ] return map }), diff --git a/packages/core/test/plugin/supervisor.test.ts b/packages/core/test/plugin/supervisor.test.ts index ffbc2f709cdd..cf40b1839aec 100644 --- a/packages/core/test/plugin/supervisor.test.ts +++ b/packages/core/test/plugin/supervisor.test.ts @@ -58,11 +58,7 @@ const instances = Layer.effect( offlineModels, ConfigPluginSource.node.replace(sourceLayer), LocationServiceMap.node.replace(Layer.succeed(LocationServiceMap.Service, map)), - Instance.node.replace( - Layer.succeed(Instance.Service, { - provide: (session) => Effect.provide(map.get(session.location)), - }), - ), + Instance.node.replace(Layer.succeed(Instance.Service, Instance.fromLocations(map))), ] return map }), diff --git a/packages/core/test/session-generate.test.ts b/packages/core/test/session-generate.test.ts index 54abd800447a..9fa2223abfaf 100644 --- a/packages/core/test/session-generate.test.ts +++ b/packages/core/test/session-generate.test.ts @@ -250,6 +250,7 @@ const setup = Effect.gen(function* () { Layer.mock(Plugin.Service, { awaitActivation: Effect.void }), ) as Layer.Layer, ), + provideIfLoaded: () => () => Effect.die("Unexpected loaded-only instance lookup"), }), } }) diff --git a/packages/core/test/session-move.test.ts b/packages/core/test/session-move.test.ts index 99dc52b16579..228b89f62083 100644 --- a/packages/core/test/session-move.test.ts +++ b/packages/core/test/session-move.test.ts @@ -126,6 +126,8 @@ const sourceProbe = (options: { execution?: boolean } = {}) => } return yield* effect.pipe(Effect.provide(locations.get(session.location))) }), + provideIfLoaded: (session) => + Instance.provideContextEffectOption(locations.contextEffectOption, session.location), }) }), ), @@ -287,7 +289,7 @@ describe("Session.move", () => { Effect.gen(function* () { const locations = yield* LocationServiceMap.Service const privateInstances = yield* LayerMap.make( - () => + (_id: Session.ID) => Instance.layer(source, { discovery: false, replacements: [ @@ -306,6 +308,10 @@ describe("Session.move", () => { ? privateInstances.get(session.id) : locations.get(session.location), ), + provideIfLoaded: (session) => + session.id === selectedID && session.location.directory === source.directory + ? Instance.provideContextEffectOption(privateInstances.contextEffectOption, session.id) + : Instance.provideContextEffectOption(locations.contextEffectOption, session.location), }) const bindings: LayerNode.Replacements = [ ...replacements, diff --git a/packages/core/test/session-owned.test.ts b/packages/core/test/session-owned.test.ts index 327071b56771..83f58352e3f6 100644 --- a/packages/core/test/session-owned.test.ts +++ b/packages/core/test/session-owned.test.ts @@ -147,6 +147,7 @@ const setup = Effect.fnUntraced(function* (options?: { const instances = Instance.Service.of({ // This fixture supplies only the instance services exercised by Session. provide: (session) => Effect.provide(servicesFor(session.location) as Layer.Layer), + provideIfLoaded: () => () => Effect.die("Unexpected loaded-only instance lookup"), }) const sessions = yield* Session.make().pipe( Effect.satisfiesServicesType< diff --git a/packages/core/test/session-remove.test.ts b/packages/core/test/session-remove.test.ts index 2e39ee6233c3..45a477337364 100644 --- a/packages/core/test/session-remove.test.ts +++ b/packages/core/test/session-remove.test.ts @@ -1,5 +1,5 @@ import { describe, expect } from "bun:test" -import { Effect, Layer, RcMap, Scope } from "effect" +import { Effect, Layer, Option, RcMap, Scope } from "effect" import { AppNodeBuilder } from "@opencode/core/effect/app-node-builder" import { LayerNode } from "@opencode/util/effect/layer-node" import { Database } from "@opencode/core/database/database" @@ -115,3 +115,39 @@ describe("Session.remove", () => { }), ) }) + +describe("Instance.provideIfLoaded", () => { + it.live("skips absent instances and scopes loaded borrows without replacing the caller's Scope", () => + Effect.gen(function* () { + const temporary = yield* tmpdirScoped() + const sessions = yield* Session.Service + const instances = yield* Instance.Service + const locations = yield* LocationServiceMap.Service + const scope = yield* Scope.Scope + const session = yield* sessions.create({ + location: Location.Ref.make({ directory: AbsolutePath.make(temporary.path) }), + }) + const absent = Effect.die("An unloaded instance must not run the effect").pipe(instances.provideIfLoaded(session)) + + expect(yield* absent).toEqual(Option.none()) + expect(Array.from(yield* RcMap.keys(locations.rcMap))).toEqual([]) + yield* Location.Service.pipe(instances.provide(session)) + expect(Array.from(yield* RcMap.keys(locations.rcMap))).toEqual([session.location]) + expect(yield* Effect.void.pipe(instances.provideIfLoaded(session))).toEqual(Option.some(undefined)) + const failure = new Error("Borrowed operation failed") + expect(yield* Effect.fail(failure).pipe(instances.provideIfLoaded(session), Effect.flip)).toBe(failure) + + const borrowed = yield* Effect.gen(function* () { + const location = yield* Location.Service + const callerScope = yield* Scope.Scope + expect(callerScope).toBe(scope) + yield* locations.invalidate(session.location) + return location.directory + }).pipe(instances.provideIfLoaded(session), Effect.satisfiesServicesType()) + + expect(borrowed).toEqual(Option.some(session.location.directory)) + expect(Array.from(yield* RcMap.keys(locations.rcMap))).toEqual([]) + expect(yield* absent).toEqual(Option.none()) + }), + ) +}) diff --git a/packages/protocol/openapi.json b/packages/protocol/openapi.json index 73a54d345cb3..d660a6184627 100644 --- a/packages/protocol/openapi.json +++ b/packages/protocol/openapi.json @@ -4382,18 +4382,11 @@ } }, "404": { - "description": "SessionNotFoundError | LocationNotFoundError", + "description": "SessionNotFoundError", "content": { "application/json": { "schema": { - "anyOf": [ - { - "$ref": "#/components/schemas/SessionNotFoundErrorEncoded" - }, - { - "$ref": "#/components/schemas/LocationNotFoundErrorEncoded" - } - ] + "$ref": "#/components/schemas/SessionNotFoundErrorEncoded" } } } @@ -9088,18 +9081,11 @@ } }, "404": { - "description": "SessionNotFoundError | LocationNotFoundError", + "description": "SessionNotFoundError", "content": { "application/json": { "schema": { - "anyOf": [ - { - "$ref": "#/components/schemas/SessionNotFoundErrorEncoded" - }, - { - "$ref": "#/components/schemas/LocationNotFoundErrorEncoded" - } - ] + "$ref": "#/components/schemas/SessionNotFoundErrorEncoded" } } } diff --git a/packages/protocol/src/groups/permission.ts b/packages/protocol/src/groups/permission.ts index 5cc0eb4b8502..e7a0baa684ba 100644 --- a/packages/protocol/src/groups/permission.ts +++ b/packages/protocol/src/groups/permission.ts @@ -8,7 +8,6 @@ import { Context, Schema } from "effect" import { HttpApiEndpoint, HttpApiGroup, HttpApiMiddleware, HttpApiSchema, OpenApi } from "effect/http-api" import { ConflictError, - LocationNotFoundError, PermissionNotFoundError, PermissionSavedNotFoundError, SessionNotFoundError, @@ -113,7 +112,7 @@ export const makePermissionGroup = < HttpApiEndpoint.get("session.permission.list", "/api/session/:sessionID/permission", { params: { sessionID: Session.ID }, success: Schema.Struct({ data: Schema.Array(Permission.Request) }), - error: [SessionNotFoundError, LocationNotFoundError], + error: SessionNotFoundError, }).annotateMerge( OpenApi.annotations({ identifier: "session.permission.list", diff --git a/packages/protocol/src/groups/session.ts b/packages/protocol/src/groups/session.ts index 95683c846c3c..bac6cf73eb12 100644 --- a/packages/protocol/src/groups/session.ts +++ b/packages/protocol/src/groups/session.ts @@ -736,15 +736,13 @@ export const makeSessionGroup = LayerNode.Replacemen ...LocationServiceMap.canonical(session.location), }) const provide = (session: Session.Info) => Effect.provide(instances.get(key(session))) + const provideIfLoaded = (session: Session.Info) => + Instance.provideContextEffectOption(instances.contextEffectOption, key(session)) + const selector = Instance.Service.of({ provide, provideIfLoaded }) const instances: LayerMap.LayerMap< ReturnType, Instance.Services, @@ -71,7 +74,7 @@ export function layer(options: Options, replacements: () => LayerNode.Replacemen ...replacements(), // Instances borrow this selector and the host's Location map instead of retaining // their Layer scopes; retaining the selector would block its shutdown on its own entries. - Instance.node.replace(Layer.succeed(Instance.Service, { provide })), + Instance.node.replace(Layer.succeed(Instance.Service, selector)), LocationServiceMap.node.replace(Layer.succeed(LocationServiceMap.Service, locations)), ], }).pipe( @@ -101,7 +104,7 @@ export function layer(options: Options, replacements: () => LayerNode.Replacemen ), { idleTimeToLive: Duration.infinity }, ) - return Instance.Service.of({ provide }) + return selector }), ) } diff --git a/packages/sdk/test/instances.test.ts b/packages/sdk/test/instances.test.ts index 87857bd08383..60bbf1adcf82 100644 --- a/packages/sdk/test/instances.test.ts +++ b/packages/sdk/test/instances.test.ts @@ -88,10 +88,10 @@ test("Promise instances are lazy, share by key and Location, and stay isolated b expect(await second.sessions.get({ sessionID })).toEqual(separateHost) expect(configured).toEqual([]) expect(setups).toEqual([]) - // Permission and form lists read instance services, so they acquire the Session's instance. + // Pending permissions and forms live only in a loaded instance, so listing them never acquires one. expect(await first.permission.list({ sessionID })).toEqual([]) expect(await first.session.form.list({ sessionID })).toEqual([]) - expect(configured).toEqual(["first:alpha"]) + expect(configured).toEqual([]) await Promise.all( [ diff --git a/packages/server/src/handlers/permission.ts b/packages/server/src/handlers/permission.ts index 68d0543a4c15..1958ab29aadc 100644 --- a/packages/server/src/handlers/permission.ts +++ b/packages/server/src/handlers/permission.ts @@ -3,11 +3,11 @@ import { Location } from "@opencode/core/location" import { Permission } from "@opencode/core/permission" import { PermissionSaved } from "@opencode/core/permission/saved" import { Session } from "@opencode/core/session" -import { Effect } from "effect" +import { Effect, Option } from "effect" import { HttpApiBuilder, HttpApiSchema } from "effect/http-api" import { Api } from "../api" import { ConflictError, PermissionNotFoundError, PermissionSavedNotFoundError } from "@opencode/protocol/errors" -import { locationErrors, response, sessionInfo } from "../location" +import { response, sessionInfo } from "../location" import { missingSession } from "./session-error" function missingRequest(id: Permission.ID) { @@ -62,8 +62,8 @@ export const PermissionHandler = HttpApiBuilder.group(Api, "server.permission", const session = yield* sessionInfo(sessions, ctx.params.sessionID) const requests = yield* Permission.Service.use((permission) => permission.forSession(ctx.params.sessionID), - ).pipe(instances.provide(session), locationErrors) - return { data: requests } + ).pipe(instances.provideIfLoaded(session)) + return { data: Option.getOrElse(requests, () => []) } }), ) .handle( diff --git a/packages/server/src/handlers/session.ts b/packages/server/src/handlers/session.ts index 0386bd44931a..063ca6727301 100644 --- a/packages/server/src/handlers/session.ts +++ b/packages/server/src/handlers/session.ts @@ -4,7 +4,9 @@ import { SessionTitle } from "@opencode/core/session/title" import { SessionTransfer } from "@opencode/core/session/transfer" import { InstructionEntry } from "@opencode/core/session/instruction-entry" import { Form } from "@opencode/core/form" -import { DateTime, Effect, Stream } from "effect" +import { Instance } from "@opencode/core/instance/service" +import { LocationServiceMap } from "@opencode/core/location-services" +import { DateTime, Effect, Option, Stream } from "effect" import { HttpApiBuilder, HttpApiSchema } from "effect/http-api" import { Api } from "../api" import { @@ -22,7 +24,7 @@ import { SkillNotFoundError, } from "@opencode/protocol/errors" import { AbsolutePath } from "@opencode/core/schema" -import { locationErrors } from "../location" +import { locationErrors, requestRef, sessionInfo } from "../location" import { failedMessageDecode, failedSnapshot, missingMessage, missingSession } from "./session-error" function missingForm(id: Form.ID) { @@ -32,6 +34,8 @@ function missingForm(id: Form.ID) { export const SessionHandler = HttpApiBuilder.group(Api, "server.session", (handlers) => Effect.gen(function* () { const session = yield* Session.Service + const instances = yield* Instance.Service + const locations = yield* LocationServiceMap.Service const transfer = yield* SessionTransfer.Service const requireOwnedForm = Effect.fnUntraced(function* (sessionID: Form.Info["sessionID"], formID: Form.ID) { const form = yield* Form.Service @@ -616,8 +620,13 @@ export const SessionHandler = HttpApiBuilder.group(Api, "server.session", (handl .handle( "session.form.list", Effect.fn(function* (ctx) { - const form = yield* Form.Service - return { data: yield* form.list({ sessionID: ctx.params.sessionID }) } + const current = + ctx.params.sessionID === "global" ? undefined : yield* sessionInfo(session, ctx.params.sessionID) + const read = Form.Service.use((form) => form.list({ sessionID: ctx.params.sessionID })) + const forms = yield* current + ? read.pipe(instances.provideIfLoaded(current)) + : read.pipe(Instance.provideContextEffectOption(locations.contextEffectOption, requestRef(ctx.request))) + return { data: Option.getOrElse(forms, () => []) } }), ) .handle( diff --git a/packages/server/test/fetch.test.ts b/packages/server/test/fetch.test.ts index c2be48131ed4..a98ad0c6289d 100644 --- a/packages/server/test/fetch.test.ts +++ b/packages/server/test/fetch.test.ts @@ -41,10 +41,10 @@ it.live("returns LocationNotFoundError for a missing folder and recovers once it const endpoints = [ "/api/model", "/api/integration", - `/api/session/${session.id}/permission`, + `/api/session/${session.id}/permission/per_missing`, `/api/experimental/session/${session.id}/instructions/entries`, - `/api/session/${session.id}/form`, - "/api/session/global/form", + `/api/session/${session.id}/form/frm_missing`, + "/api/session/global/form/frm_missing", ] for (const endpoint of endpoints) { const response = yield* Effect.promise(() => @@ -466,7 +466,7 @@ it.live("serves the session view operation and missing-session error", () => }), ) -it.live("routes pending requests through the Session's instance", () => +it.live("routes pending requests by Session without loading an instance", () => Effect.gen(function* () { const config = yield* Effect.acquireDisposable(Effect.promise(() => tmpdir("opencode-pending-read-"))) const handler = yield* ServerFetch.make({ @@ -518,7 +518,7 @@ it.live("routes pending requests through the Session's instance", () => ) expect(global.status).toBe(200) expect(yield* Effect.promise(() => global.json())).toEqual({ data: [] }) - expect(yield* loaded()).toEqual([{ directory: process.cwd() }]) + expect(yield* loaded()).toEqual([]) const createdForm = yield* Effect.promise(() => handler( diff --git a/packages/server/test/session-instances.test.ts b/packages/server/test/session-instances.test.ts index 8ddc3601f94b..64ce25f4a2bb 100644 --- a/packages/server/test/session-instances.test.ts +++ b/packages/server/test/session-instances.test.ts @@ -145,6 +145,8 @@ it.live( ) const selector = Instance.Service.of({ provide: (session) => Effect.provide(instances.get(session.id)), + provideIfLoaded: (session) => + Instance.provideContextEffectOption(instances.contextEffectOption, session.id), }) const bindings: LayerNode.Replacements = [ ...replacements, diff --git a/services/www/openapi.json b/services/www/openapi.json index 73a54d345cb3..d660a6184627 100644 --- a/services/www/openapi.json +++ b/services/www/openapi.json @@ -4382,18 +4382,11 @@ } }, "404": { - "description": "SessionNotFoundError | LocationNotFoundError", + "description": "SessionNotFoundError", "content": { "application/json": { "schema": { - "anyOf": [ - { - "$ref": "#/components/schemas/SessionNotFoundErrorEncoded" - }, - { - "$ref": "#/components/schemas/LocationNotFoundErrorEncoded" - } - ] + "$ref": "#/components/schemas/SessionNotFoundErrorEncoded" } } } @@ -9088,18 +9081,11 @@ } }, "404": { - "description": "SessionNotFoundError | LocationNotFoundError", + "description": "SessionNotFoundError", "content": { "application/json": { "schema": { - "anyOf": [ - { - "$ref": "#/components/schemas/SessionNotFoundErrorEncoded" - }, - { - "$ref": "#/components/schemas/LocationNotFoundErrorEncoded" - } - ] + "$ref": "#/components/schemas/SessionNotFoundErrorEncoded" } } } diff --git a/services/www/public/openapi.json b/services/www/public/openapi.json index 73a54d345cb3..d660a6184627 100644 --- a/services/www/public/openapi.json +++ b/services/www/public/openapi.json @@ -4382,18 +4382,11 @@ } }, "404": { - "description": "SessionNotFoundError | LocationNotFoundError", + "description": "SessionNotFoundError", "content": { "application/json": { "schema": { - "anyOf": [ - { - "$ref": "#/components/schemas/SessionNotFoundErrorEncoded" - }, - { - "$ref": "#/components/schemas/LocationNotFoundErrorEncoded" - } - ] + "$ref": "#/components/schemas/SessionNotFoundErrorEncoded" } } } @@ -9088,18 +9081,11 @@ } }, "404": { - "description": "SessionNotFoundError | LocationNotFoundError", + "description": "SessionNotFoundError", "content": { "application/json": { "schema": { - "anyOf": [ - { - "$ref": "#/components/schemas/SessionNotFoundErrorEncoded" - }, - { - "$ref": "#/components/schemas/LocationNotFoundErrorEncoded" - } - ] + "$ref": "#/components/schemas/SessionNotFoundErrorEncoded" } } }