From 7f090e306488fa1addd431168511a2ebf8599baf Mon Sep 17 00:00:00 2001 From: James Long <17031+jlongster@users.noreply.github.com> Date: Sat, 18 Jul 2026 02:36:04 +0000 Subject: [PATCH] fix(core): remove session import cycle --- packages/core/src/effect/layer-node.ts | 6 ++---- packages/core/src/permission.ts | 18 +++++++++++------- packages/core/src/session.ts | 11 +++-------- packages/core/src/session/error.ts | 6 ++++++ .../effect/layer-node/layer-node-types.test.ts | 4 ---- .../test/effect/layer-node/layer-node.test.ts | 6 ------ 6 files changed, 22 insertions(+), 29 deletions(-) diff --git a/packages/core/src/effect/layer-node.ts b/packages/core/src/effect/layer-node.ts index dba328c27042..dc7aa972f017 100644 --- a/packages/core/src/effect/layer-node.ts +++ b/packages/core/src/effect/layer-node.ts @@ -74,7 +74,7 @@ type MakeInput< T extends Tag | undefined = undefined, > = NodeIdentity & { readonly layer: Implementation - readonly deps: (Items | (() => Items)) & CheckDependencies> + readonly deps: Items & CheckDependencies> readonly tag?: T } @@ -90,9 +90,7 @@ export function make< name: input.service !== undefined ? input.service.key : input.name, service: input.service, implementation: input.layer, - get dependencies() { - return typeof input.deps === "function" ? input.deps() : input.deps - }, + dependencies: input.deps, tag: input.tag, } } diff --git a/packages/core/src/permission.ts b/packages/core/src/permission.ts index 99743abd2f36..29a8e3720a7a 100644 --- a/packages/core/src/permission.ts +++ b/packages/core/src/permission.ts @@ -6,7 +6,8 @@ import { Permission } from "@opencode-ai/schema/permission" import { EventV2 } from "./event" import { Location } from "./location" import { AgentV2 } from "./agent" -import { SessionV2 } from "./session" +import { SessionErrors } from "./session/error" +import { SessionSchema } from "./session/schema" import { SessionStore } from "./session/store" import { Wildcard } from "./util/wildcard" import { PermissionSaved } from "./permission/saved" @@ -98,11 +99,11 @@ export function merge(...rulesets: Permission.Ruleset[]): Permission.Ruleset { } export interface Interface { - readonly ask: (input: AssertInput) => Effect.Effect - readonly assert: (input: AssertInput) => Effect.Effect + readonly ask: (input: AssertInput) => Effect.Effect + readonly assert: (input: AssertInput) => Effect.Effect readonly reply: (input: ReplyInput) => Effect.Effect readonly get: (id: ID) => Effect.Effect - readonly forSession: (sessionID: SessionV2.ID) => Effect.Effect> + readonly forSession: (sessionID: SessionSchema.ID) => Effect.Effect> readonly list: () => Effect.Effect> } @@ -142,9 +143,12 @@ const layer = Layer.effect( ) }) - const configured = Effect.fn("PermissionV2.configured")(function* (sessionID: SessionV2.ID, agentID?: AgentV2.ID) { + const configured = Effect.fn("PermissionV2.configured")(function* ( + sessionID: SessionSchema.ID, + agentID?: AgentV2.ID, + ) { const session = yield* sessions.get(sessionID) - if (!session) return yield* new SessionV2.NotFoundError({ sessionID }) + if (!session) return yield* new SessionErrors.NotFoundError({ sessionID }) const agent = yield* agents.resolve(agentID ?? session.agent) return agent?.permissions ?? missingAgentPermissions }) @@ -301,7 +305,7 @@ const layer = Layer.effect( return pending.get(id)?.request }) - const forSession = Effect.fn("PermissionV2.forSession")(function* (sessionID: SessionV2.ID) { + const forSession = Effect.fn("PermissionV2.forSession")(function* (sessionID: SessionSchema.ID) { return Array.from(pending.values(), (item) => item.request).filter((request) => request.sessionID === sessionID) }) diff --git a/packages/core/src/session.ts b/packages/core/src/session.ts index c268c789313e..d9fa0627b1d2 100644 --- a/packages/core/src/session.ts +++ b/packages/core/src/session.ts @@ -28,9 +28,9 @@ import { fromRow } from "./session/info" import { SessionRunner } from "./session/runner/index" import { SessionStore } from "./session/store" import { SessionExecution } from "./session/execution" +import { MessageDecodeError, NotFoundError } from "./session/error" import { makeGlobalNode } from "./effect/app-node" import { LocationServiceMap } from "./location-service-map" -import { MessageDecodeError } from "./session/error" import { SessionEvent } from "./session/event" import { SessionPending } from "./session/pending" import { SessionGenerate } from "./session/generate" @@ -108,10 +108,6 @@ type ForkInput = { messageID?: SessionMessage.ID } -export class NotFoundError extends Schema.TaggedErrorClass()("Session.NotFoundError", { - sessionID: SessionSchema.ID, -}) {} - export class OperationUnavailableError extends Schema.TaggedErrorClass()( "Session.OperationUnavailableError", { @@ -119,7 +115,7 @@ export class OperationUnavailableError extends Schema.TaggedErrorClass()("Session.PromptConflictError", { sessionID: SessionSchema.ID, @@ -1033,8 +1029,7 @@ const SHELL_MAX_CAPTURE_BYTES = 1024 * 1024 export const node = makeGlobalNode({ service: Service, layer: layer.pipe(Layer.orDie), - // Defer the execution node across the Session/runner module cycle until the graph is compiled. - deps: () => [ + deps: [ Job.node, Database.node, EventV2.node, diff --git a/packages/core/src/session/error.ts b/packages/core/src/session/error.ts index 3de8af3a5638..42a71500ee80 100644 --- a/packages/core/src/session/error.ts +++ b/packages/core/src/session/error.ts @@ -1,9 +1,15 @@ +export * as SessionErrors from "./error" + import { Schema } from "effect" import { Agent } from "@opencode-ai/schema/agent" import { SessionMessage } from "./message" import { SessionSchema } from "./schema" import { SessionError } from "@opencode-ai/schema/session-error" +export class NotFoundError extends Schema.TaggedErrorClass()("Session.NotFoundError", { + sessionID: SessionSchema.ID, +}) {} + export class MessageDecodeError extends Schema.TaggedErrorClass()("Session.MessageDecodeError", { sessionID: SessionSchema.ID, messageID: SessionMessage.ID, diff --git a/packages/core/test/effect/layer-node/layer-node-types.test.ts b/packages/core/test/effect/layer-node/layer-node-types.test.ts index 4deb3e6c7146..366bf746daad 100644 --- a/packages/core/test/effect/layer-node/layer-node-types.test.ts +++ b/packages/core/test/effect/layer-node/layer-node-types.test.ts @@ -32,7 +32,6 @@ const b = make({ service: B, layer: bLayer, deps: [a] }) const c = make({ service: C, layer: cLayer, deps: [a, b] }) const failing = make({ service: A, layer: failingA, deps: [] }) const dependent = make({ service: B, layer: bLayer, deps: [failing] }) -make({ service: B, layer: bLayer, deps: () => [a] }) const inputA = LayerNode.unbound(A, tags.values.app) const inputDependent = make({ service: B, layer: bLayer, deps: [inputA] }) @@ -47,9 +46,6 @@ make({ service: A, name: "a", layer: aLayer, deps: [] }) // @ts-expect-error B requires A make({ service: B, layer: bLayer, deps: [] }) -// @ts-expect-error Lazy dependencies must still provide A -make({ service: B, layer: bLayer, deps: () => [] }) - // @ts-expect-error C requires A and B make({ service: C, layer: cLayer, deps: [a] }) diff --git a/packages/core/test/effect/layer-node/layer-node.test.ts b/packages/core/test/effect/layer-node/layer-node.test.ts index 4fa9fe9877a5..b671792c59af 100644 --- a/packages/core/test/effect/layer-node/layer-node.test.ts +++ b/packages/core/test/effect/layer-node/layer-node.test.ts @@ -37,12 +37,6 @@ describe("layer node", () => { expect(await Effect.runPromise(program)).toBe("hello production") }) - test("resolves lazy dependencies when compiling", async () => { - const greeting = make({ service: Greeting, layer: greetingLayer, deps: () => [value] }) - const program = Effect.map(Greeting, (item) => item.value).pipe(Effect.provide(build(greeting))) - expect(await Effect.runPromise(program)).toBe("hello production") - }) - test("exposes roots but hides transitive dependencies", () => { const layer = build(LayerNode.group([greeting])) const check: Layer.Layer = layer