Sfoglia il codice sorgente

refactor(core): rename app node modules (#34238)

James Long 2 mesi fa
parent
commit
a76c6918d2
94 ha cambiato i file con 317 aggiunte e 329 eliminazioni
  1. 2 2
      packages/core/package.json
  2. 1 1
      packages/core/src/agent.ts
  3. 1 1
      packages/core/src/aisdk.ts
  4. 1 1
      packages/core/src/background-job.ts
  5. 1 1
      packages/core/src/catalog.ts
  6. 1 1
      packages/core/src/command.ts
  7. 1 1
      packages/core/src/config.ts
  8. 1 1
      packages/core/src/credential.ts
  9. 2 2
      packages/core/src/cross-spawn-spawner.ts
  10. 1 1
      packages/core/src/database/database.ts
  11. 7 7
      packages/core/src/effect/app-node-builder.ts
  12. 2 2
      packages/core/src/effect/app-node-platform.ts
  13. 1 1
      packages/core/src/effect/app-node.ts
  14. 177 31
      packages/core/src/effect/layer-node.ts
  15. 0 2
      packages/core/src/effect/layer-node/index.ts
  16. 0 147
      packages/core/src/effect/layer-node/layer-node-tree.ts
  17. 1 1
      packages/core/src/event.ts
  18. 1 1
      packages/core/src/file-mutation.ts
  19. 1 1
      packages/core/src/filesystem.ts
  20. 1 1
      packages/core/src/filesystem/search.ts
  21. 1 1
      packages/core/src/filesystem/watcher.ts
  22. 2 2
      packages/core/src/fs-util.ts
  23. 1 1
      packages/core/src/git.ts
  24. 1 1
      packages/core/src/global.ts
  25. 1 1
      packages/core/src/image.ts
  26. 1 1
      packages/core/src/instruction-context.ts
  27. 1 1
      packages/core/src/integration.ts
  28. 1 1
      packages/core/src/location-mutation.ts
  29. 1 1
      packages/core/src/location-service-map.ts
  30. 6 6
      packages/core/src/location-services.ts
  31. 1 1
      packages/core/src/location.ts
  32. 2 2
      packages/core/src/models-dev.ts
  33. 2 2
      packages/core/src/npm.ts
  34. 1 1
      packages/core/src/permission.ts
  35. 1 1
      packages/core/src/permission/saved.ts
  36. 1 1
      packages/core/src/plugin.ts
  37. 2 2
      packages/core/src/plugin/internal.ts
  38. 1 1
      packages/core/src/policy.ts
  39. 1 1
      packages/core/src/process.ts
  40. 1 1
      packages/core/src/project.ts
  41. 1 1
      packages/core/src/project/copy.ts
  42. 1 1
      packages/core/src/project/directories.ts
  43. 1 1
      packages/core/src/pty.ts
  44. 1 1
      packages/core/src/pty/ticket.ts
  45. 1 1
      packages/core/src/question.ts
  46. 1 1
      packages/core/src/reference.ts
  47. 1 1
      packages/core/src/reference/guidance.ts
  48. 1 1
      packages/core/src/repository-cache.ts
  49. 1 1
      packages/core/src/ripgrep.ts
  50. 2 2
      packages/core/src/ripgrep/binary.ts
  51. 1 1
      packages/core/src/session.ts
  52. 1 1
      packages/core/src/session/execution.ts
  53. 1 1
      packages/core/src/session/execution/local.ts
  54. 1 1
      packages/core/src/session/projector.ts
  55. 2 2
      packages/core/src/session/runner/llm.ts
  56. 1 1
      packages/core/src/session/runner/model.ts
  57. 1 1
      packages/core/src/session/store.ts
  58. 1 1
      packages/core/src/session/todo.ts
  59. 1 1
      packages/core/src/skill.ts
  60. 2 2
      packages/core/src/skill/discovery.ts
  61. 1 1
      packages/core/src/skill/guidance.ts
  62. 1 1
      packages/core/src/snapshot.ts
  63. 1 1
      packages/core/src/system-context/builtins.ts
  64. 1 1
      packages/core/src/system-context/registry.ts
  65. 1 1
      packages/core/src/tool-output-store.ts
  66. 1 1
      packages/core/src/tool/application-tools.ts
  67. 2 2
      packages/core/src/tool/builtins.ts
  68. 1 1
      packages/core/src/tool/read-filesystem.ts
  69. 1 1
      packages/core/src/tool/registry.ts
  70. 1 1
      packages/core/src/util/effect-flock.ts
  71. 2 3
      packages/core/test/effect/layer-node/layer-node-types.test.ts
  72. 10 11
      packages/core/test/effect/layer-node/layer-node.test.ts
  73. 4 4
      packages/core/test/effect/layer-node/node-build.test.ts
  74. 2 1
      packages/core/test/process/process.test.ts
  75. 1 1
      packages/opencode/src/account/account.ts
  76. 1 1
      packages/opencode/src/config/config.ts
  77. 1 1
      packages/opencode/src/control-plane/workspace.ts
  78. 1 1
      packages/opencode/src/installation/index.ts
  79. 2 3
      packages/opencode/src/server/routes/instance/httpapi/server.ts
  80. 1 1
      packages/opencode/src/session/instruction.ts
  81. 1 1
      packages/opencode/src/session/llm.ts
  82. 1 1
      packages/opencode/src/share/share-next.ts
  83. 1 1
      packages/opencode/src/skill/discovery.ts
  84. 1 1
      packages/opencode/src/tool/registry.ts
  85. 1 1
      packages/opencode/src/worktree/index.ts
  86. 2 3
      packages/opencode/test/git/git.test.ts
  87. 2 3
      packages/opencode/test/session/messages-pagination.test.ts
  88. 3 4
      packages/opencode/test/session/processor-effect.test.ts
  89. 2 3
      packages/opencode/test/session/retry.test.ts
  90. 1 2
      packages/opencode/test/session/snapshot-tool-race.test.ts
  91. 4 5
      packages/opencode/test/share/share-next.test.ts
  92. 2 3
      packages/opencode/test/tool/registry.test.ts
  93. 1 1
      packages/server/src/pty-environment.ts
  94. 4 4
      packages/server/src/routes.ts

+ 2 - 2
packages/core/package.json

@@ -16,8 +16,8 @@
     "opencode": "./bin/opencode"
   },
   "exports": {
-    "./effect/layer-node": "./src/effect/layer-node/index.ts",
-    "./effect/node": "./src/effect/node.ts",
+    "./effect/layer-node": "./src/effect/layer-node.ts",
+    "./effect/app-node": "./src/effect/app-node.ts",
     "./session/runner": "./src/session/runner/index.ts",
     "./system-context": "./src/system-context/index.ts",
     "./*": "./src/*.ts"

+ 1 - 1
packages/core/src/agent.ts

@@ -1,6 +1,6 @@
 export * as AgentV2 from "./agent"
 
-import { makeLocationNode } from "./effect/node"
+import { makeLocationNode } from "./effect/app-node"
 import { Array, Context, Effect, Layer, Types } from "effect"
 import { Agent } from "@opencode-ai/schema/agent"
 import { State } from "./state"

+ 1 - 1
packages/core/src/aisdk.ts

@@ -1,6 +1,6 @@
 export * as AISDK from "./aisdk"
 
-import { makeLocationNode } from "./effect/node"
+import { makeLocationNode } from "./effect/app-node"
 import type { LanguageModelV3 } from "@ai-sdk/provider"
 import { Cause, Context, Effect, Layer, Schema, Scope } from "effect"
 import { ModelV2 } from "./model"

+ 1 - 1
packages/core/src/background-job.ts

@@ -2,7 +2,7 @@ export * as BackgroundJob from "./background-job"
 
 import { Cause, Clock, Context, Deferred, Effect, Exit, Layer, Scope, SynchronizedRef } from "effect"
 import { Identifier } from "./id/id"
-import { makeGlobalNode } from "./effect/node"
+import { makeGlobalNode } from "./effect/app-node"
 
 export type Status = "running" | "completed" | "error" | "cancelled"
 

+ 1 - 1
packages/core/src/catalog.ts

@@ -1,6 +1,6 @@
 export * as Catalog from "./catalog"
 
-import { makeLocationNode } from "./effect/node"
+import { makeLocationNode } from "./effect/app-node"
 import { Array, Context, Effect, Layer, Option, Order, pipe, Schema } from "effect"
 import { Catalog } from "@opencode-ai/schema/catalog"
 import { ModelV2 } from "./model"

+ 1 - 1
packages/core/src/command.ts

@@ -1,6 +1,6 @@
 export * as CommandV2 from "./command"
 
-import { makeLocationNode } from "./effect/node"
+import { makeLocationNode } from "./effect/app-node"
 import { Context, Effect, Layer, Types } from "effect"
 import { Command } from "@opencode-ai/schema/command"
 import { State } from "./state"

+ 1 - 1
packages/core/src/config.ts

@@ -1,6 +1,6 @@
 export * as Config from "./config"
 
-import { makeLocationNode } from "./effect/node"
+import { makeLocationNode } from "./effect/app-node"
 import path from "path"
 import { type ParseError, parse } from "jsonc-parser"
 import { Context, Effect, Layer, Option, Schema } from "effect"

+ 1 - 1
packages/core/src/credential.ts

@@ -5,7 +5,7 @@ import { Context, Effect, Layer, Schema } from "effect"
 import { Credential } from "@opencode-ai/schema/credential"
 import { Integration } from "@opencode-ai/schema/integration"
 import { Database } from "./database/database"
-import { makeGlobalNode } from "./effect/node"
+import { makeGlobalNode } from "./effect/app-node"
 import { CredentialTable } from "./credential/sql"
 
 export const ID = Credential.ID

+ 2 - 2
packages/core/src/cross-spawn-spawner.ts

@@ -24,8 +24,8 @@ import {
 import * as NodeChildProcess from "node:child_process"
 import { PassThrough } from "node:stream"
 import launch from "cross-spawn"
-import { makeGlobalNode } from "./effect/node"
-import { filesystem, path } from "./effect/layer-node-platform"
+import { makeGlobalNode } from "./effect/app-node"
+import { filesystem, path } from "./effect/app-node-platform"
 
 const toError = (err: unknown): Error => (err instanceof globalThis.Error ? err : new globalThis.Error(String(err)))
 

+ 1 - 1
packages/core/src/database/database.ts

@@ -8,7 +8,7 @@ import { Flag } from "../flag/flag"
 import { isAbsolute, join } from "path"
 import { DatabaseMigration } from "./migration"
 import { InstallationChannel } from "../installation/version"
-import { makeGlobalNode } from "../effect/node"
+import { makeGlobalNode } from "../effect/app-node"
 
 const makeDatabase = EffectDrizzleSqlite.makeWithDefaults()
 type DatabaseShape = Effect.Success<typeof makeDatabase>

+ 7 - 7
packages/core/src/effect/node-build.ts → packages/core/src/effect/app-node-builder.ts

@@ -1,24 +1,24 @@
 import { Layer } from "effect"
 import { buildLocationServiceMap } from "../location-services"
 import { LocationServiceMap } from "../location-service-map"
-import { LayerNode, LayerNodeTree } from "./layer-node"
-import { makeGlobalNode } from "./node"
+import { LayerNode } from "./layer-node"
+import { makeGlobalNode } from "./app-node"
 
 export function build<A, E>(root: LayerNode.Node<A, E, any>, replacements?: readonly LayerNode.Replacement[]) {
   const replacementMap = new Map(replacements?.map((item) => [item.source, item.replacement]))
 
-  if (!LayerNodeTree.hasUnbound(root, LocationServiceMap.node)) {
+  if (!LayerNode.hasUnbound(root, LocationServiceMap.node)) {
     // If the location service map is not needed, we shouldn't pull it
     // in. Compile the graph normally
-    return LayerNodeTree.compile(root, replacementMap)
+    return LayerNode.compile(root, replacementMap)
   }
 
   const locationMap = buildLocationServiceMap(replacementMap)
   const locationMapNode = makeGlobalNode({ service: LocationServiceMap.Service, layer: locationMap, deps: [] })
 
-  const app = LayerNodeTree.bind(root, LocationServiceMap.node, locationMapNode)
+  const app = LayerNode.bind(root, LocationServiceMap.node, locationMapNode)
 
-  return LayerNodeTree.compile(app, replacementMap)
+  return LayerNode.compile(app, replacementMap)
 }
 
-export * as NodeBuild from "./node-build"
+export * as NodeBuild from "./app-node-builder"

+ 2 - 2
packages/core/src/effect/layer-node-platform.ts → packages/core/src/effect/app-node-platform.ts

@@ -3,7 +3,7 @@ import { LLMClient, RequestExecutor } from "@opencode-ai/llm/route"
 import { FileSystem, Path } from "effect"
 import { FetchHttpClient } from "effect/unstable/http"
 import { HttpClient } from "effect/unstable/http"
-import { makeGlobalNode } from "./node"
+import { makeGlobalNode } from "./app-node"
 
 export const filesystem = makeGlobalNode({ service: FileSystem.FileSystem, layer: NodeFileSystem.layer, deps: [] })
 export const path = makeGlobalNode({ service: Path.Path, layer: NodePath.layer, deps: [] })
@@ -15,4 +15,4 @@ export const requestExecutor = makeGlobalNode({
 })
 export const llmClient = makeGlobalNode({ service: LLMClient.Service, layer: LLMClient.layer, deps: [requestExecutor] })
 
-export * as LayerNodePlatform from "./layer-node-platform"
+export * as LayerNodePlatform from "./app-node-platform"

+ 1 - 1
packages/core/src/effect/node.ts → packages/core/src/effect/app-node.ts

@@ -11,4 +11,4 @@ export type LocationNode<A, E = never> = LayerNode.Node<A, E, (typeof tags.value
 export const makeGlobalNode = tags.make("global")
 export const makeLocationNode = tags.make("location")
 
-export * as Node from "./node"
+export * as Node from "./app-node"

+ 177 - 31
packages/core/src/effect/layer-node/layer-node.ts → packages/core/src/effect/layer-node.ts

@@ -1,6 +1,7 @@
 import { Brand, Context, Layer } from "effect"
 
 type AnyNode = Node<unknown, unknown, any>
+type RuntimeLayer = Layer.Layer<never, unknown, unknown>
 type NodeList<Item extends AnyNode = AnyNode> = readonly [] | readonly [Item, ...Item[]]
 export type Output<Item> = [Item] extends [never] ? never : Item extends Node<infer A, unknown, any> ? A : never
 export type Error<Item> = [Item] extends [never] ? never : Item extends Node<unknown, infer E, any> ? E : never
@@ -34,6 +35,39 @@ type NodeIdentity =
   | { readonly name: string; readonly service?: never }
 type DistributiveOmit<A, K extends PropertyKey> = A extends unknown ? Omit<A, K> : never
 
+export type TagConfig = Readonly<Record<string, readonly string[]>>
+type TagNames<Config extends TagConfig> = keyof Config & string
+type NodeInTags<Names extends string> = Node<unknown, unknown, Tag<Names> | undefined>
+type CheckTags<Items extends NodeList, Names extends string> = [Exclude<Items[number], NodeInTags<Names>>] extends [
+  never,
+]
+  ? unknown
+  : { readonly "Invalid tag dependencies": Exclude<Items[number], NodeInTags<Names>> }
+
+export interface Tags<Config extends TagConfig> {
+  readonly values: { readonly [Name in TagNames<Config>]: Tag<Name> }
+  readonly make: <Name extends TagNames<Config>>(
+    name: Name,
+  ) => <const Implementation extends Layer.Any, const Items extends NodeList>(
+    input: DistributiveOmit<MakeInput<Implementation, Items, Tag<Name>>, "tag"> &
+      CheckTags<Items, Name | Extract<Config[Name][number], string>>,
+  ) => Node<Layer.Success<Implementation>, Layer.Error<Implementation> | Error<Items[number]>, Tag<Name>>
+}
+
+export function tags<const Config extends { readonly [Name in keyof Config]: readonly (keyof Config & string)[] }>(
+  config: Config,
+): Tags<Config> {
+  const names = Object.keys(config) as TagNames<Config>[]
+  const values = Object.fromEntries(names.map((name) => [name, makeTag(name)])) as Tags<Config>["values"]
+  return {
+    values,
+    make: ((name: TagNames<Config>) => (input: DistributiveOmit<MakeInput<Layer.Any, NodeList, Tag>, "tag">) =>
+      make({ ...input, tag: values[name] })) as Tags<Config>["make"],
+  }
+}
+
+// Nodes ---------------------------------------------------------------------
+
 type MakeInput<
   Implementation extends Layer.Any,
   Items extends NodeList,
@@ -77,37 +111,6 @@ export function group<const Items extends readonly AnyNode[]>(
   return { kind: "group", name: "group", dependencies }
 }
 
-export type TagConfig = Readonly<Record<string, readonly string[]>>
-type TagNames<Config extends TagConfig> = keyof Config & string
-type NodeInTags<Names extends string> = Node<unknown, unknown, Tag<Names> | undefined>
-type CheckTags<Items extends NodeList, Names extends string> = [Exclude<Items[number], NodeInTags<Names>>] extends [
-  never,
-]
-  ? unknown
-  : { readonly "Invalid tag dependencies": Exclude<Items[number], NodeInTags<Names>> }
-
-export interface Tags<Config extends TagConfig> {
-  readonly values: { readonly [Name in TagNames<Config>]: Tag<Name> }
-  readonly make: <Name extends TagNames<Config>>(
-    name: Name,
-  ) => <const Implementation extends Layer.Any, const Items extends NodeList>(
-    input: DistributiveOmit<MakeInput<Implementation, Items, Tag<Name>>, "tag"> &
-      CheckTags<Items, Name | Extract<Config[Name][number], string>>,
-  ) => Node<Layer.Success<Implementation>, Layer.Error<Implementation> | Error<Items[number]>, Tag<Name>>
-}
-
-export function tags<const Config extends { readonly [Name in keyof Config]: readonly (keyof Config & string)[] }>(
-  config: Config,
-): Tags<Config> {
-  const names = Object.keys(config) as TagNames<Config>[]
-  const values = Object.fromEntries(names.map((name) => [name, makeTag(name)])) as Tags<Config>["values"]
-  return {
-    values,
-    make: ((name: TagNames<Config>) => (input: DistributiveOmit<MakeInput<Layer.Any, NodeList, Tag>, "tag">) =>
-      make({ ...input, tag: values[name] })) as Tags<Config>["make"],
-  }
-}
-
 export type Replacement = {
   readonly source: Layer.Any
   readonly replacement: Layer.Any
@@ -124,4 +127,147 @@ export function replace<A, E, R, E2>(
   return { source, replacement }
 }
 
+// Tree -----------------------------------------------------------------------
+
+type Visit<Result> = (node: AnyNode, context: VisitContext<Result>) => Result
+
+type VisitContext<Result> = {
+  readonly cache: Map<AnyNode, Result>
+  readonly visit: (node: AnyNode) => Result
+}
+
+function walk<Result>(
+  root: AnyNode,
+  visit: Visit<Result>,
+  options: {
+    readonly cache?: Map<AnyNode, Result>
+    readonly resolve?: (node: AnyNode) => AnyNode
+    readonly detectCycles?: boolean
+  } = {},
+) {
+  const cache = options.cache ?? new Map<AnyNode, Result>()
+  const visiting = new Set<AnyNode>()
+  const stack: AnyNode[] = []
+
+  const recur = (node: AnyNode): Result => {
+    const target = options.resolve?.(node) ?? node
+    const cached = cache.get(target)
+    if (cached !== undefined || cache.has(target)) return cached!
+
+    if (options.detectCycles !== false && visiting.has(target)) {
+      const start = stack.indexOf(target)
+      throw new Error(
+        `Cycle detected in layer tree: ${[...stack.slice(start), target].map((item) => item.name).join(" -> ")}`,
+      )
+    }
+
+    visiting.add(target)
+    stack.push(target)
+    try {
+      const result = visit(target, { cache, visit: recur })
+      if (!cache.has(target)) cache.set(target, result)
+      return result
+    } finally {
+      stack.pop()
+      visiting.delete(target)
+    }
+  }
+
+  return recur(root)
+}
+
+export function hoist<A, E, T extends Tag>(
+  root: Node<A, E, any>,
+  tag: T,
+): {
+  readonly node: Node<A, E>
+  readonly hoisted: Node<unknown, E>
+} {
+  const hoisted = new Map<string, AnyNode>()
+
+  const node = walk<AnyNode>(root, (node, context) => {
+    if (node.kind === "group") {
+      return { ...node, dependencies: node.dependencies.map(context.visit) }
+    }
+    if (node.tag === tag) {
+      const existing = hoisted.get(node.name)
+      if (existing && existing !== node) {
+        throw new Error(`Tag ${tag} has conflicting implementations for ${node.name}`)
+      }
+      hoisted.set(node.name, node)
+      return group([])
+    }
+    if (node.kind === "unbound") {
+      return node
+    }
+    return { ...node, dependencies: node.dependencies.map(context.visit) }
+  })
+
+  return {
+    node: node as Node<A, E>,
+    hoisted: group(Array.from(hoisted.values())) as Node<unknown, E>,
+  }
+}
+
+export function compile<A, E>(
+  root: Node<A, E, any>,
+  replacements?: ReadonlyMap<Layer.Any, Layer.Any>,
+): Layer.Layer<A, E> {
+  const cache = new Map<AnyNode, RuntimeLayer>()
+  const compileNode = (node: AnyNode) =>
+    walk<RuntimeLayer>(
+      node,
+      (node, context) => {
+        if (node.kind === "unbound") throw new Error(`Unbound layer node: ${node.name}`)
+        const dependencies = node.dependencies.flatMap(flatten).map(context.visit)
+        const implementation = (replacements?.get(node.implementation!) ?? node.implementation!) as RuntimeLayer
+        return dependencies.length === 0
+          ? implementation
+          : implementation.pipe(Layer.provide(dependencies as [RuntimeLayer, ...RuntimeLayer[]]))
+      },
+      { cache },
+    )
+  const layers = flatten(root).map((node) => compileNode(node))
+  const layer = layers.reduce<RuntimeLayer>((result, layer) => layer.pipe(Layer.provideMerge(result)), Layer.empty)
+  return layer as Layer.Layer<A, E>
+}
+
+export function hasUnbound(root: Node<unknown, unknown, any>, source: AnyNode): boolean {
+  if (source.kind !== "unbound") throw new Error(`Cannot check non-unbound layer node: ${source.name}`)
+  return walk<boolean>(root, (node, context) => {
+    if (node === source) return true
+    return node.dependencies.some(context.visit)
+  })
+}
+
+export function bind<A, E, T extends Tag | undefined>(
+  root: Node<A, E, T>,
+  source: AnyNode,
+  replacement: AnyNode,
+): Node<A, E, T> {
+  if (source.kind !== "unbound") throw new Error(`Cannot bind non-unbound layer node: ${source.name}`)
+  if (source.name !== replacement.name) {
+    throw new Error(`Cannot bind ${source.name} to ${replacement.name}`)
+  }
+  if (source.tag !== replacement.tag) {
+    throw new Error(`Cannot bind ${source.name} across tags`)
+  }
+  return walk<AnyNode>(
+    root,
+    (target, context) => {
+      if (target.kind === "unbound") return target
+      const dependencies: AnyNode[] = []
+      const clone = { ...target, dependencies }
+      context.cache.set(target, clone)
+      dependencies.push(...target.dependencies.map(context.visit))
+      return clone
+    },
+    { detectCycles: false, resolve: (node) => (node === source ? replacement : node) },
+  ) as Node<A, E, T>
+}
+
+function flatten(node: AnyNode): readonly AnyNode[] {
+  return node.kind === "group" ? node.dependencies.flatMap(flatten) : [node]
+}
+
 export * as LayerNode from "./layer-node"

+ 0 - 2
packages/core/src/effect/layer-node/index.ts

@@ -1,2 +0,0 @@
-export * from "./layer-node"
-export * as LayerNodeTree from "./layer-node-tree"

+ 0 - 147
packages/core/src/effect/layer-node/layer-node-tree.ts

@@ -1,147 +0,0 @@
-import { Layer } from "effect"
-import { LayerNode } from "./layer-node"
-
-type AnyNode = LayerNode.Node<unknown, unknown, any>
-type RuntimeLayer = Layer.Layer<never, unknown, unknown>
-type Visit<Result> = (node: AnyNode, context: VisitContext<Result>) => Result
-
-type VisitContext<Result> = {
-  readonly cache: Map<AnyNode, Result>
-  readonly visit: (node: AnyNode) => Result
-}
-
-function walk<Result>(
-  root: AnyNode,
-  visit: Visit<Result>,
-  options: {
-    readonly cache?: Map<AnyNode, Result>
-    readonly resolve?: (node: AnyNode) => AnyNode
-    readonly detectCycles?: boolean
-  } = {},
-) {
-  const cache = options.cache ?? new Map<AnyNode, Result>()
-  const visiting = new Set<AnyNode>()
-  const stack: AnyNode[] = []
-
-  const recur = (node: AnyNode): Result => {
-    const target = options.resolve?.(node) ?? node
-    const cached = cache.get(target)
-    if (cached !== undefined || cache.has(target)) return cached!
-
-    if (options.detectCycles !== false && visiting.has(target)) {
-      const start = stack.indexOf(target)
-      throw new Error(
-        `Cycle detected in layer tree: ${[...stack.slice(start), target].map((item) => item.name).join(" -> ")}`,
-      )
-    }
-
-    visiting.add(target)
-    stack.push(target)
-    try {
-      const result = visit(target, { cache, visit: recur })
-      if (!cache.has(target)) cache.set(target, result)
-      return result
-    } finally {
-      stack.pop()
-      visiting.delete(target)
-    }
-  }
-
-  return recur(root)
-}
-
-export function hoist<A, E, T extends LayerNode.Tag>(
-  root: LayerNode.Node<A, E, any>,
-  tag: T,
-): {
-  readonly node: LayerNode.Node<A, E>
-  readonly hoisted: LayerNode.Node<unknown, E>
-} {
-  const hoisted = new Map<string, AnyNode>()
-
-  const node = walk<AnyNode>(root, (node, context) => {
-    if (node.kind === "group") {
-      return { ...node, dependencies: node.dependencies.map(context.visit) }
-    }
-    if (node.tag === tag) {
-      const existing = hoisted.get(node.name)
-      if (existing && existing !== node) {
-        throw new Error(`Tag ${tag} has conflicting implementations for ${node.name}`)
-      }
-      hoisted.set(node.name, node)
-      return LayerNode.group([])
-    }
-    if (node.kind === "unbound") {
-      return node
-    }
-    return { ...node, dependencies: node.dependencies.map(context.visit) }
-  })
-
-  return {
-    node: node as LayerNode.Node<A, E>,
-    hoisted: LayerNode.group(Array.from(hoisted.values())) as LayerNode.Node<unknown, E>,
-  }
-}
-
-export function compile<A, E>(
-  root: LayerNode.Node<A, E, any>,
-  replacements?: ReadonlyMap<Layer.Any, Layer.Any>,
-): Layer.Layer<A, E> {
-  const cache = new Map<AnyNode, RuntimeLayer>()
-  const compileNode = (node: AnyNode) =>
-    walk<RuntimeLayer>(
-      node,
-      (node, context) => {
-        if (node.kind === "unbound") throw new Error(`Unbound layer node: ${node.name}`)
-        const dependencies = node.dependencies.flatMap(flatten).map(context.visit)
-        const implementation = (replacements?.get(node.implementation!) ?? node.implementation!) as RuntimeLayer
-        return dependencies.length === 0
-          ? implementation
-          : implementation.pipe(Layer.provide(dependencies as [RuntimeLayer, ...RuntimeLayer[]]))
-      },
-      { cache },
-    )
-  const layers = flatten(root).map((node) => compileNode(node))
-  const layer = layers.reduce<RuntimeLayer>((result, layer) => layer.pipe(Layer.provideMerge(result)), Layer.empty)
-  return layer as Layer.Layer<A, E>
-}
-
-export function hasUnbound(root: LayerNode.Node<unknown, unknown, any>, source: AnyNode): boolean {
-  if (source.kind !== "unbound") throw new Error(`Cannot check non-unbound layer node: ${source.name}`)
-  return walk<boolean>(root, (node, context) => {
-    if (node === source) return true
-    return node.dependencies.some(context.visit)
-  })
-}
-
-export function bind<A, E, T extends LayerNode.Tag | undefined>(
-  root: LayerNode.Node<A, E, T>,
-  source: AnyNode,
-  replacement: AnyNode,
-): LayerNode.Node<A, E, T> {
-  if (source.kind !== "unbound") throw new Error(`Cannot bind non-unbound layer node: ${source.name}`)
-  if (source.name !== replacement.name) {
-    throw new Error(`Cannot bind ${source.name} to ${replacement.name}`)
-  }
-  if (source.tag !== replacement.tag) {
-    throw new Error(`Cannot bind ${source.name} across tags`)
-  }
-  return walk<AnyNode>(
-    root,
-    (target, context) => {
-      if (target.kind === "unbound") return target
-      const dependencies: AnyNode[] = []
-      const clone = { ...target, dependencies }
-      context.cache.set(target, clone)
-      dependencies.push(...target.dependencies.map(context.visit))
-      return clone
-    },
-    { detectCycles: false, resolve: (node) => (node === source ? replacement : node) },
-  ) as LayerNode.Node<A, E, T>
-}
-
-function flatten(node: AnyNode): readonly AnyNode[] {
-  return node.kind === "group" ? node.dependencies.flatMap(flatten) : [node]
-}
-
-export * as LayerNodeTree from "./layer-node-tree"

+ 1 - 1
packages/core/src/event.ts

@@ -7,7 +7,7 @@ import { and, asc, eq, gt, inArray } from "drizzle-orm"
 import { Database } from "./database/database"
 import { EventSequenceTable, EventTable } from "./event/sql"
 import { Location } from "./location"
-import { makeGlobalNode } from "./effect/node"
+import { makeGlobalNode } from "./effect/app-node"
 import { isDeepStrictEqual } from "node:util"
 import { Durable } from "@opencode-ai/schema/durable-event-manifest"
 

+ 1 - 1
packages/core/src/file-mutation.ts

@@ -1,6 +1,6 @@
 export * as FileMutation from "./file-mutation"
 
-import { makeLocationNode } from "./effect/node"
+import { makeLocationNode } from "./effect/app-node"
 import { Context, Effect, Layer, Schema } from "effect"
 import { dirname } from "path"
 import { KeyedMutex } from "./effect/keyed-mutex"

+ 1 - 1
packages/core/src/filesystem.ts

@@ -1,6 +1,6 @@
 export * as FileSystem from "./filesystem"
 
-import { makeLocationNode } from "./effect/node"
+import { makeLocationNode } from "./effect/app-node"
 import path from "path"
 import { Context, Effect, Layer, Schema } from "effect"
 import { FSUtil } from "./fs-util"

+ 1 - 1
packages/core/src/filesystem/search.ts

@@ -1,6 +1,6 @@
 export * as FileSystemSearch from "./search"
 
-import { makeLocationNode } from "../effect/node"
+import { makeLocationNode } from "../effect/app-node"
 import path from "path"
 import { Context, Effect, Layer, Scope } from "effect"
 import { Fff } from "#fff"

+ 1 - 1
packages/core/src/filesystem/watcher.ts

@@ -3,7 +3,7 @@ export * as Watcher from "./watcher"
 // @ts-ignore
 import { createWrapper } from "@parcel/watcher/wrapper"
 import type ParcelWatcher from "@parcel/watcher"
-import { makeLocationNode } from "../effect/node"
+import { makeLocationNode } from "../effect/app-node"
 import { Cause, Context, Effect, Layer } from "effect"
 import { FileSystemWatcher } from "@opencode-ai/schema/filesystem-watcher"
 import path from "path"

+ 2 - 2
packages/core/src/fs-util.ts

@@ -7,8 +7,8 @@ import { Context, Effect, FileSystem, Layer, Schema } from "effect"
 import type { PlatformError } from "effect/PlatformError"
 import { Glob } from "./util/glob"
 import { serviceUse } from "./effect/service-use"
-import { makeGlobalNode } from "./effect/node"
-import { filesystem } from "./effect/layer-node-platform"
+import { makeGlobalNode } from "./effect/app-node"
+import { filesystem } from "./effect/app-node-platform"
 
 export namespace FSUtil {
   export class FileSystemError extends Schema.TaggedErrorClass<FileSystemError>()("FileSystemError", {

+ 1 - 1
packages/core/src/git.ts

@@ -7,7 +7,7 @@ import { ChildProcess } from "effect/unstable/process"
 import { AbsolutePath, RelativePath } from "./schema"
 import { FSUtil } from "./fs-util"
 import { AppProcess } from "./process"
-import { makeGlobalNode } from "./effect/node"
+import { makeGlobalNode } from "./effect/app-node"
 import { File } from "./file"
 import { KeyedMutex } from "./effect/keyed-mutex"
 

+ 1 - 1
packages/core/src/global.ts

@@ -5,7 +5,7 @@ import os from "os"
 import { Context, Effect, Layer } from "effect"
 import { Flock } from "./util/flock"
 import { Flag } from "./flag/flag"
-import { makeGlobalNode } from "./effect/node"
+import { makeGlobalNode } from "./effect/app-node"
 
 const app = "opencode"
 const data = path.join(xdgData!, app)

+ 1 - 1
packages/core/src/image.ts

@@ -1,6 +1,6 @@
 export * as Image from "./image"
 
-import { makeLocationNode } from "./effect/node"
+import { makeLocationNode } from "./effect/app-node"
 import { Context, Effect, Layer, Schema } from "effect"
 import { Config } from "./config"
 import { FileSystem } from "./filesystem"

+ 1 - 1
packages/core/src/instruction-context.ts

@@ -9,7 +9,7 @@ import { Location } from "./location"
 import { AbsolutePath } from "./schema"
 import { SystemContext } from "./system-context/index"
 import { SystemContextRegistry } from "./system-context/registry"
-import { makeLocationNode } from "./effect/node"
+import { makeLocationNode } from "./effect/app-node"
 
 class File extends Schema.Class<File>("InstructionContext.File")({
   path: AbsolutePath,

+ 1 - 1
packages/core/src/integration.ts

@@ -1,6 +1,6 @@
 export * as Integration from "./integration"
 
-import { makeLocationNode } from "./effect/node"
+import { makeLocationNode } from "./effect/app-node"
 import {
   Cause,
   Clock,

+ 1 - 1
packages/core/src/location-mutation.ts

@@ -1,6 +1,6 @@
 export * as LocationMutation from "./location-mutation"
 
-import { makeLocationNode } from "./effect/node"
+import { makeLocationNode } from "./effect/app-node"
 import path from "path"
 import { Context, Effect, Layer, Schema } from "effect"
 import { FSUtil } from "./fs-util"

+ 1 - 1
packages/core/src/location-service-map.ts

@@ -1,6 +1,6 @@
 import { Context, Effect, Layer, LayerMap } from "effect"
 import { LayerNode } from "./effect/layer-node"
-import { Node } from "./effect/node"
+import { Node } from "./effect/app-node"
 import { Location } from "./location"
 import type { LocationError, LocationServices } from "./location-services"
 

+ 6 - 6
packages/core/src/location-services.ts

@@ -4,8 +4,8 @@ import { AISDK } from "./aisdk"
 import { Catalog } from "./catalog"
 import { CommandV2 } from "./command"
 import { Config } from "./config"
-import { LayerNode, LayerNodeTree } from "./effect/layer-node"
-import { Node } from "./effect/node"
+import { LayerNode } from "./effect/layer-node"
+import { Node } from "./effect/app-node"
 import { FileMutation } from "./file-mutation"
 import { FileSystem } from "./filesystem"
 import { FileSystemSearch } from "./filesystem/search"
@@ -88,11 +88,11 @@ export function buildLocationServiceMap(
     LocationServiceMap.Service,
     LayerMap.make(
       (ref: Location.Ref) => {
-        const location = LayerNodeTree.hoist(
-          LayerNodeTree.bind(locationServices, Location.node, Location.boundNode(ref)),
+        const location = LayerNode.hoist(
+          LayerNode.bind(locationServices, Location.node, Location.boundNode(ref)),
           Node.tags.values.global,
         )
-        return LayerNodeTree.compile(location.node, replacements).pipe(
+        return LayerNode.compile(location.node, replacements).pipe(
           Layer.fresh,
           Layer.tap(() =>
             Effect.logInfo("booting location services", {
@@ -100,7 +100,7 @@ export function buildLocationServiceMap(
               workspaceID: ref.workspaceID,
             }),
           ),
-          Layer.provide(LayerNodeTree.compile(location.hoisted, replacements)),
+          Layer.provide(LayerNode.compile(location.hoisted, replacements)),
         )
       },
       { idleTimeToLive: "60 minutes" },

+ 1 - 1
packages/core/src/location.ts

@@ -2,7 +2,7 @@ import { Context, Effect, Layer } from "effect"
 import { Info, Ref, response } from "@opencode-ai/schema/location"
 import { Project } from "./project"
 import { LayerNode } from "./effect/layer-node"
-import { makeLocationNode, tags } from "./effect/node"
+import { makeLocationNode, tags } from "./effect/app-node"
 
 export * as Location from "./location"
 

+ 2 - 2
packages/core/src/models-dev.ts

@@ -9,8 +9,8 @@ import { Hash } from "./util/hash"
 import { FSUtil } from "./fs-util"
 import { InstallationChannel, InstallationVersion } from "./installation/version"
 import { EventV2 } from "./event"
-import { makeGlobalNode } from "./effect/node"
-import { httpClient } from "./effect/layer-node-platform"
+import { makeGlobalNode } from "./effect/app-node"
+import { httpClient } from "./effect/app-node-platform"
 
 export const CatalogModelStatus = Schema.Literals(["alpha", "beta", "deprecated"])
 export type CatalogModelStatus = typeof CatalogModelStatus.Type

+ 2 - 2
packages/core/src/npm.ts

@@ -7,8 +7,8 @@ import { NodeFileSystem } from "@effect/platform-node"
 import { FSUtil } from "./fs-util"
 import { Global } from "./global"
 import { EffectFlock } from "./util/effect-flock"
-import { makeGlobalNode } from "./effect/node"
-import { filesystem } from "./effect/layer-node-platform"
+import { makeGlobalNode } from "./effect/app-node"
+import { filesystem } from "./effect/app-node-platform"
 import { makeRuntime } from "./effect/runtime"
 import { NpmConfig } from "./npm-config"
 

+ 1 - 1
packages/core/src/permission.ts

@@ -1,6 +1,6 @@
 export * as PermissionV2 from "./permission"
 
-import { makeLocationNode } from "./effect/node"
+import { makeLocationNode } from "./effect/app-node"
 import { Context, Deferred, Effect as EffectRuntime, Layer, Schema } from "effect"
 import { Permission } from "@opencode-ai/schema/permission"
 import { EventV2 } from "./event"

+ 1 - 1
packages/core/src/permission/saved.ts

@@ -3,7 +3,7 @@ export * as PermissionSaved from "./saved"
 import { eq } from "drizzle-orm"
 import { Context, Effect, Layer, Schema } from "effect"
 import { Database } from "../database/database"
-import { makeGlobalNode } from "../effect/node"
+import { makeGlobalNode } from "../effect/app-node"
 import { ProjectV2 } from "../project"
 import { PermissionTable } from "./sql"
 import { PermissionSaved } from "@opencode-ai/schema/permission-saved"

+ 1 - 1
packages/core/src/plugin.ts

@@ -1,6 +1,6 @@
 export * as PluginV2 from "./plugin"
 
-import { makeLocationNode } from "./effect/node"
+import { makeLocationNode } from "./effect/app-node"
 import { Context, Deferred, Effect, Exit, Layer, Scope } from "effect"
 import type { Plugin as PluginRuntime } from "@opencode-ai/plugin/v2/effect"
 import { Plugin } from "@opencode-ai/schema/plugin"

+ 2 - 2
packages/core/src/plugin/internal.ts

@@ -1,7 +1,7 @@
 export * as PluginInternal from "./internal"
 
-import { makeLocationNode } from "../effect/node"
-import { httpClient } from "../effect/layer-node-platform"
+import { makeLocationNode } from "../effect/app-node"
+import { httpClient } from "../effect/app-node-platform"
 import type { PluginContext } from "@opencode-ai/plugin/v2/effect"
 import { Effect, Layer, Scope } from "effect"
 import { AgentV2 } from "../agent"

+ 1 - 1
packages/core/src/policy.ts

@@ -1,6 +1,6 @@
 export * as Policy from "./policy"
 
-import { makeLocationNode } from "./effect/node"
+import { makeLocationNode } from "./effect/app-node"
 import { Context, Effect as EffectRuntime, Layer, Schema } from "effect"
 import { Wildcard } from "./util/wildcard"
 import { Location } from "./location"

+ 1 - 1
packages/core/src/process.ts

@@ -3,7 +3,7 @@ import type { PlatformError } from "effect/PlatformError"
 import { ChildProcess } from "effect/unstable/process"
 import { ChildProcessSpawner } from "effect/unstable/process/ChildProcessSpawner"
 import { CrossSpawnSpawner } from "./cross-spawn-spawner"
-import { makeGlobalNode } from "./effect/node"
+import { makeGlobalNode } from "./effect/app-node"
 
 export class AppProcessError extends Schema.TaggedErrorClass<AppProcessError>()("AppProcessError", {
   command: Schema.String,

+ 1 - 1
packages/core/src/project.ts

@@ -6,7 +6,7 @@ import path from "path"
 import { AbsolutePath } from "./schema"
 import { FSUtil } from "./fs-util"
 import { Git } from "./git"
-import { makeGlobalNode } from "./effect/node"
+import { makeGlobalNode } from "./effect/app-node"
 import { Hash } from "./util/hash"
 import { ProjectDirectories } from "./project/directories"
 import { ProjectSchema } from "./project/schema"

+ 1 - 1
packages/core/src/project/copy.ts

@@ -5,7 +5,7 @@ import path from "path"
 import { AbsolutePath } from "../schema"
 import { FSUtil } from "../fs-util"
 import { Git } from "../git"
-import { makeLocationNode } from "../effect/node"
+import { makeLocationNode } from "../effect/app-node"
 import { Project } from "../project"
 import { ProjectDirectories } from "./directories"
 import { makeGitWorktreeStrategy } from "./copy-strategies"

+ 1 - 1
packages/core/src/project/directories.ts

@@ -3,7 +3,7 @@ export * as ProjectDirectories from "./directories"
 import { and, asc, desc, eq, isNotNull, isNull, ne, or } from "drizzle-orm"
 import { Context, Effect, Layer, Schema } from "effect"
 import { Database } from "../database/database"
-import { makeGlobalNode } from "../effect/node"
+import { makeGlobalNode } from "../effect/app-node"
 import { AbsolutePath, optional } from "../schema"
 import { ProjectSchema } from "./schema"
 import { ProjectDirectoryTable } from "./sql"

+ 1 - 1
packages/core/src/pty.ts

@@ -1,6 +1,6 @@
 export * as Pty from "./pty"
 
-import { makeLocationNode } from "./effect/node"
+import { makeLocationNode } from "./effect/app-node"
 import type { Disp, Proc } from "#pty"
 import { Context, Effect, Layer, Schema, Types } from "effect"
 import { Pty } from "@opencode-ai/schema/pty"

+ 1 - 1
packages/core/src/pty/ticket.ts

@@ -4,7 +4,7 @@ import { WorkspaceV2 } from "../workspace"
 import { PtyTicket } from "@opencode-ai/schema/pty-ticket"
 import { PtyID } from "./schema"
 import { Cache, Context, Duration, Effect, Layer } from "effect"
-import { makeGlobalNode } from "../effect/node"
+import { makeGlobalNode } from "../effect/app-node"
 
 const DEFAULT_TTL = Duration.seconds(60)
 const CAPACITY = 10_000

+ 1 - 1
packages/core/src/question.ts

@@ -1,6 +1,6 @@
 export * as QuestionV2 from "./question"
 
-import { makeLocationNode } from "./effect/node"
+import { makeLocationNode } from "./effect/app-node"
 import { Context, Deferred, Effect, Layer, Schema } from "effect"
 import { Question } from "@opencode-ai/schema/question"
 import { EventV2 } from "./event"

+ 1 - 1
packages/core/src/reference.ts

@@ -1,6 +1,6 @@
 export * as Reference from "./reference"
 
-import { makeLocationNode } from "./effect/node"
+import { makeLocationNode } from "./effect/app-node"
 import { Context, Effect, Layer, Scope, Types } from "effect"
 import { Reference } from "@opencode-ai/schema/reference"
 import { Global } from "./global"

+ 1 - 1
packages/core/src/reference/guidance.ts

@@ -1,6 +1,6 @@
 export * as ReferenceGuidance from "./guidance"
 
-import { makeLocationNode } from "../effect/node"
+import { makeLocationNode } from "../effect/app-node"
 import { Context, Effect, Layer, Schema } from "effect"
 import { Reference } from "../reference"
 import { SystemContext } from "../system-context/index"

+ 1 - 1
packages/core/src/repository-cache.ts

@@ -5,7 +5,7 @@ import { Git } from "./git"
 import { Global } from "./global"
 import { Repository } from "./repository"
 import { AbsolutePath } from "./schema"
-import { makeGlobalNode } from "./effect/node"
+import { makeGlobalNode } from "./effect/app-node"
 import { EffectFlock } from "./util/effect-flock"
 
 export type Result = {

+ 1 - 1
packages/core/src/ripgrep.ts

@@ -3,7 +3,7 @@ export * as Ripgrep from "./ripgrep"
 import { Context, Effect, Fiber, Layer, Schema, Stream } from "effect"
 import { ChildProcess } from "effect/unstable/process"
 import { Entry, Match } from "@opencode-ai/schema/filesystem"
-import { makeGlobalNode } from "./effect/node"
+import { makeGlobalNode } from "./effect/app-node"
 import { AppProcess, collectStream, waitForAbort } from "./process"
 import { NonNegativeInt, PositiveInt, RelativePath } from "./schema"
 import { RipgrepBinary } from "./ripgrep/binary"

+ 2 - 2
packages/core/src/ripgrep/binary.ts

@@ -4,8 +4,8 @@ import { FetchHttpClient, HttpClient, HttpClientRequest } from "effect/unstable/
 import { ChildProcess } from "effect/unstable/process"
 import { ChildProcessSpawner } from "effect/unstable/process/ChildProcessSpawner"
 import { CrossSpawnSpawner } from "../cross-spawn-spawner"
-import { makeGlobalNode } from "../effect/node"
-import { httpClient } from "../effect/layer-node-platform"
+import { makeGlobalNode } from "../effect/app-node"
+import { httpClient } from "../effect/app-node-platform"
 import { FSUtil } from "../fs-util"
 import { Global } from "../global"
 import { which } from "../util/which"

+ 1 - 1
packages/core/src/session.ts

@@ -27,7 +27,7 @@ import { fromRow } from "./session/info"
 import { SessionRunner } from "./session/runner/index"
 import { SessionStore } from "./session/store"
 import { SessionExecution } from "./session/execution"
-import { makeGlobalNode } from "./effect/node"
+import { makeGlobalNode } from "./effect/app-node"
 import { LocationServiceMap } from "./location-service-map"
 import { MessageDecodeError } from "./session/error"
 import { SessionEvent } from "./session/event"

+ 1 - 1
packages/core/src/session/execution.ts

@@ -2,7 +2,7 @@ export * as SessionExecution from "./execution"
 
 import { Context, Effect, Layer } from "effect"
 import { LayerNode } from "../effect/layer-node"
-import { Node } from "../effect/node"
+import { Node } from "../effect/app-node"
 import { SessionRunner } from "./runner/index"
 import { SessionSchema } from "./schema"
 

+ 1 - 1
packages/core/src/session/execution/local.ts

@@ -1,6 +1,6 @@
 import { Cause, Effect, Layer } from "effect"
 import { LocationServiceMap } from "../../location-service-map"
-import { makeGlobalNode } from "../../effect/node"
+import { makeGlobalNode } from "../../effect/app-node"
 import { SessionRunCoordinator } from "../run-coordinator"
 import { SessionRunner } from "../runner"
 import { SessionSchema } from "../schema"

+ 1 - 1
packages/core/src/session/projector.ts

@@ -4,7 +4,7 @@ import { and, desc, eq, gt, or, sql } from "drizzle-orm"
 import { DateTime, Effect, Layer, Schema } from "effect"
 import { Database } from "../database/database"
 import { EventV2 } from "../event"
-import { makeGlobalNode } from "../effect/node"
+import { makeGlobalNode } from "../effect/app-node"
 import { SessionEvent } from "./event"
 import { SessionV1 } from "../v1/session"
 import { WorkspaceTable } from "../control-plane/workspace.sql"

+ 2 - 2
packages/core/src/session/runner/llm.ts

@@ -36,8 +36,8 @@ import { createLLMEventPublisher } from "./publish-llm-event"
 import { toLLMMessages } from "./to-llm-message"
 import { MAX_STEPS_PROMPT } from "./max-steps"
 import { Snapshot } from "../../snapshot"
-import { makeLocationNode } from "../../effect/node"
-import { llmClient } from "../../effect/layer-node-platform"
+import { makeLocationNode } from "../../effect/app-node"
+import { llmClient } from "../../effect/app-node-platform"
 
 /**
  * Runs one durable coding-agent Session until it settles.

+ 1 - 1
packages/core/src/session/runner/model.ts

@@ -1,6 +1,6 @@
 export * as SessionRunnerModel from "./model"
 
-import { makeLocationNode } from "../../effect/node"
+import { makeLocationNode } from "../../effect/app-node"
 import { type Model } from "@opencode-ai/llm"
 import * as AnthropicMessages from "@opencode-ai/llm/protocols/anthropic-messages"
 import * as OpenAICompatibleChat from "@opencode-ai/llm/protocols/openai-compatible-chat"

+ 1 - 1
packages/core/src/session/store.ts

@@ -3,7 +3,7 @@ export * as SessionStore from "./store"
 import { eq } from "drizzle-orm"
 import { Context, Effect, Layer, Schema } from "effect"
 import { Database } from "../database/database"
-import { makeGlobalNode } from "../effect/node"
+import { makeGlobalNode } from "../effect/app-node"
 import { SessionHistory } from "./history"
 import { MessageDecodeError } from "./error"
 import { SessionMessage } from "./message"

+ 1 - 1
packages/core/src/session/todo.ts

@@ -4,7 +4,7 @@ import { asc, eq } from "drizzle-orm"
 import { Context, Effect, Layer } from "effect"
 import { SessionTodo } from "@opencode-ai/schema/session-todo"
 import { Database } from "../database/database"
-import { makeLocationNode } from "../effect/node"
+import { makeLocationNode } from "../effect/app-node"
 import { EventV2 } from "../event"
 import { SessionSchema } from "./schema"
 import { TodoTable } from "./sql"

+ 1 - 1
packages/core/src/skill.ts

@@ -1,6 +1,6 @@
 export * as SkillV2 from "./skill"
 
-import { makeLocationNode } from "./effect/node"
+import { makeLocationNode } from "./effect/app-node"
 import path from "path"
 import { Context, Effect, Layer, Schema, Types } from "effect"
 import { Skill } from "@opencode-ai/schema/skill"

+ 2 - 2
packages/core/src/skill/discovery.ts

@@ -5,8 +5,8 @@ import { Context, Effect, Layer, Schedule, Schema } from "effect"
 import { FetchHttpClient, HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http"
 import { FSUtil } from "../fs-util"
 import { Global } from "../global"
-import { makeGlobalNode } from "../effect/node"
-import { httpClient } from "../effect/layer-node-platform"
+import { makeGlobalNode } from "../effect/app-node"
+import { httpClient } from "../effect/app-node-platform"
 import { AbsolutePath } from "../schema"
 
 const skillConcurrency = 4

+ 1 - 1
packages/core/src/skill/guidance.ts

@@ -1,6 +1,6 @@
 export * as SkillGuidance from "./guidance"
 
-import { makeLocationNode } from "../effect/node"
+import { makeLocationNode } from "../effect/app-node"
 import { Context, Effect, Layer, Schema } from "effect"
 import { AgentV2 } from "../agent"
 import { PermissionV2 } from "../permission"

+ 1 - 1
packages/core/src/snapshot.ts

@@ -1,6 +1,6 @@
 export * as Snapshot from "./snapshot"
 
-import { makeLocationNode } from "./effect/node"
+import { makeLocationNode } from "./effect/app-node"
 import path from "path"
 import { Context, Effect, Layer, Schema } from "effect"
 import { Config } from "./config"

+ 1 - 1
packages/core/src/system-context/builtins.ts

@@ -1,6 +1,6 @@
 export * as SystemContextBuiltIns from "./builtins"
 
-import { makeLocationNode } from "../effect/node"
+import { makeLocationNode } from "../effect/app-node"
 import { DateTime, Effect, Layer, Schema } from "effect"
 import { Location } from "../location"
 import { SystemContext } from "./index"

+ 1 - 1
packages/core/src/system-context/registry.ts

@@ -2,7 +2,7 @@ export * as SystemContextRegistry from "./registry"
 
 import { Context, Effect, Layer, Ref, Scope } from "effect"
 import { SystemContext } from "./index"
-import { makeLocationNode } from "../effect/node"
+import { makeLocationNode } from "../effect/app-node"
 
 export interface Entry {
   readonly key: SystemContext.Key

+ 1 - 1
packages/core/src/tool-output-store.ts

@@ -5,7 +5,7 @@ import { Context, Duration, Effect, Layer, Option, Schedule, Schema } from "effe
 import { Config } from "./config"
 import { FSUtil } from "./fs-util"
 import { Global } from "./global"
-import { makeGlobalNode, makeLocationNode } from "./effect/node"
+import { makeGlobalNode, makeLocationNode } from "./effect/app-node"
 import { SessionSchema } from "./session/schema"
 import { Identifier } from "./util/identifier"
 import type { ToolOutput } from "@opencode-ai/llm"

+ 1 - 1
packages/core/src/tool/application-tools.ts

@@ -3,7 +3,7 @@ export * as ApplicationTools from "./application-tools"
 import { Context, Effect, Layer, Scope } from "effect"
 import { State } from "../state"
 import { Tool } from "./tool"
-import { makeGlobalNode } from "../effect/node"
+import { makeGlobalNode } from "../effect/app-node"
 
 type Data = {
   readonly entries: Map<string, Entry>

+ 2 - 2
packages/core/src/tool/builtins.ts

@@ -1,6 +1,6 @@
 export * as BuiltInTools from "./builtins"
 
-import { makeLocationNode } from "../effect/node"
+import { makeLocationNode } from "../effect/app-node"
 import { Layer } from "effect"
 import { BashTool } from "./bash"
 import { ApplyPatchTool } from "./apply-patch"
@@ -28,7 +28,7 @@ import { QuestionV2 } from "../question"
 import { SkillV2 } from "../skill"
 import { SessionTodo } from "../session/todo"
 import { ToolRegistry } from "./registry"
-import { httpClient } from "../effect/layer-node-platform"
+import { httpClient } from "../effect/app-node-platform"
 
 /**
  * Composes only the shipped Location-scoped built-in tool transforms.

+ 1 - 1
packages/core/src/tool/read-filesystem.ts

@@ -5,7 +5,7 @@ import { pathToFileURL } from "url"
 import { Context, Effect, Layer, Option, Schema } from "effect"
 import { FileSystem } from "../filesystem"
 import { FSUtil } from "../fs-util"
-import { makeLocationNode } from "../effect/node"
+import { makeLocationNode } from "../effect/app-node"
 import { AbsolutePath, PositiveInt, RelativePath } from "../schema"
 
 export const MAX_READ_LINES = 2_000

+ 1 - 1
packages/core/src/tool/registry.ts

@@ -11,7 +11,7 @@ import { Wildcard } from "../util/wildcard"
 import { ApplicationTools } from "./application-tools"
 import { definition, permission, settle, validateName, type AnyTool, type RegistrationError } from "./tool"
 import { Tools } from "./tools"
-import { makeLocationNode } from "../effect/node"
+import { makeLocationNode } from "../effect/app-node"
 
 export type ExecuteInput = {
   readonly sessionID: SessionSchema.ID

+ 1 - 1
packages/core/src/util/effect-flock.ts

@@ -6,7 +6,7 @@ import type { FileSystem, Scope } from "effect"
 import type { PlatformError } from "effect/PlatformError"
 import { FSUtil } from "../fs-util"
 import { Global } from "../global"
-import { makeGlobalNode } from "../effect/node"
+import { makeGlobalNode } from "../effect/app-node"
 import { Hash } from "./hash"
 
 export namespace EffectFlock {

+ 2 - 3
packages/core/test/effect/layer-node/layer-node-types.test.ts

@@ -1,8 +1,7 @@
 import { test } from "bun:test"
 import { Context, Effect, Layer } from "effect"
 import { LayerNode } from "@opencode-ai/core/effect/layer-node"
-import { LayerNodeTree } from "@opencode-ai/core/effect/layer-node"
-import { makeGlobalNode, makeLocationNode } from "@opencode-ai/core/effect/node"
+import { makeGlobalNode, makeLocationNode } from "@opencode-ai/core/effect/app-node"
 
 class A extends Context.Service<A, {}>()("test/LayerNodeA") {}
 class B extends Context.Service<B, {}>()("test/LayerNodeB") {}
@@ -16,7 +15,7 @@ class OtherError {
 
 const tags = LayerNode.tags({ app: [] })
 const make = tags.make("app")
-const build = <A, E>(root: LayerNode.Node<A, E, any>) => LayerNodeTree.compile(root) as Layer.Layer<A, E>
+const build = <A, E>(root: LayerNode.Node<A, E, any>) => LayerNode.compile(root) as Layer.Layer<A, E>
 const aLayer = Layer.succeed(A, A.of({}))
 const bLayer = Layer.effect(B, Effect.as(A, B.of({})))
 const cLayer = Layer.effect(

+ 10 - 11
packages/core/test/effect/layer-node/layer-node.test.ts

@@ -1,7 +1,6 @@
 import { describe, expect, test } from "bun:test"
 import { Context, Effect, Layer } from "effect"
 import { LayerNode } from "@opencode-ai/core/effect/layer-node"
-import { LayerNodeTree } from "@opencode-ai/core/effect/layer-node"
 
 class Value extends Context.Service<Value, { readonly value: string }>()("test/LayerNodeValue") {}
 class Greeting extends Context.Service<Greeting, { readonly value: string }>()("test/LayerNodeGreeting") {}
@@ -14,7 +13,7 @@ class App extends Context.Service<App, { readonly run: Effect.Effect<string[]> }
 const tags = LayerNode.tags({ app: [] })
 const make = tags.make("app")
 const build = <A, E>(root: LayerNode.Node<A, E, any>, replacements?: readonly LayerNode.Replacement[]) =>
-  LayerNodeTree.compile(root, new Map(replacements?.map((item) => [item.source, item.replacement]))) as Layer.Layer<
+  LayerNode.compile(root, new Map(replacements?.map((item) => [item.source, item.replacement]))) as Layer.Layer<
     A,
     E
   >
@@ -31,7 +30,7 @@ describe("layer node", () => {
     const value = LayerNode.make({ service: Value, layer: valueLayer, deps: [] })
     const greeting = LayerNode.make({ service: Greeting, layer: greetingLayer, deps: [value] })
     const program = Effect.map(Greeting, (item) => item.value).pipe(
-      Effect.provide(LayerNodeTree.compile(LayerNode.group([greeting]))),
+      Effect.provide(LayerNode.compile(LayerNode.group([greeting]))),
     )
     expect(await Effect.runPromise(program)).toBe("hello production")
   })
@@ -71,9 +70,9 @@ describe("layer node", () => {
     const unbound = LayerNode.unbound(Value, tags.values.app)
     const greeting = make({ service: Greeting, layer: greetingLayer, deps: [unbound] })
     const tree = LayerNode.group([greeting])
-    expect(() => LayerNodeTree.compile(tree)).toThrow("Unbound layer node: test/LayerNodeValue")
-    const bound = LayerNodeTree.bind(tree, unbound, value)
-    const layer = LayerNodeTree.compile(bound) as Layer.Layer<Greeting>
+    expect(() => LayerNode.compile(tree)).toThrow("Unbound layer node: test/LayerNodeValue")
+    const bound = LayerNode.bind(tree, unbound, value)
+    const layer = LayerNode.compile(bound) as Layer.Layer<Greeting>
     const program = Effect.map(Greeting, (item) => item.value).pipe(Effect.provide(layer))
     expect(await Effect.runPromise(program)).toBe("hello production")
   })
@@ -155,15 +154,15 @@ describe("layer node", () => {
       deps: [users],
     })
 
-    const result = LayerNodeTree.hoist(LayerNode.group([app]), tags.values.global)
+    const result = LayerNode.hoist(LayerNode.group([app]), tags.values.global)
     expect(result.node.dependencies[0]?.dependencies[0]?.dependencies[0]).toMatchObject({
       kind: "group",
       dependencies: [],
     })
     expect(result.hoisted.dependencies).toEqual([database])
 
-    const layer = LayerNodeTree.compile(result.node).pipe(
-      Layer.provide(LayerNodeTree.compile(result.hoisted)),
+    const layer = LayerNode.compile(result.node).pipe(
+      Layer.provide(LayerNode.compile(result.hoisted)),
     ) as unknown as Layer.Layer<App>
     const program = Effect.gen(function* () {
       return yield* (yield* App).run
@@ -197,7 +196,7 @@ describe("layer node", () => {
       deps: [second],
     })
 
-    expect(() => LayerNodeTree.hoist(LayerNode.group([left, right]), tags.values.global)).toThrow(
+    expect(() => LayerNode.hoist(LayerNode.group([left, right]), tags.values.global)).toThrow(
       "Tag global has conflicting implementations for test/GraphDatabase",
     )
   })
@@ -216,7 +215,7 @@ describe("layer node", () => {
       layer: Layer.effect(Users, Effect.as(Database, Users.of({ list: Effect.succeed([]) }))),
       deps: [LayerNode.group([database])],
     })
-    const result = LayerNodeTree.hoist(LayerNode.group([users]), tags.values.global)
+    const result = LayerNode.hoist(LayerNode.group([users]), tags.values.global)
 
     expect(result.node.dependencies[0]?.dependencies[0]?.dependencies[0]).toMatchObject({
       kind: "group",

+ 4 - 4
packages/core/test/effect/layer-node/node-build.test.ts

@@ -1,8 +1,8 @@
 import { describe, expect, test } from "bun:test"
 import { Context, Effect, Layer, LayerMap, Option } from "effect"
-import { LayerNode, LayerNodeTree } from "@opencode-ai/core/effect/layer-node"
-import { Node } from "@opencode-ai/core/effect/node"
-import { NodeBuild } from "@opencode-ai/core/effect/node-build"
+import { LayerNode } from "@opencode-ai/core/effect/layer-node"
+import { Node } from "@opencode-ai/core/effect/app-node"
+import { NodeBuild } from "@opencode-ai/core/effect/app-node-builder"
 import { Location } from "@opencode-ai/core/location"
 import { LocationServiceMap } from "@opencode-ai/core/location-service-map"
 import type { LocationError, LocationServices } from "@opencode-ai/core/location-services"
@@ -62,7 +62,7 @@ describe("node build", () => {
     }) as unknown as Effect.Effect<LayerMap.LayerMap<Location.Ref, LocationServices, LocationError>, never, CycleB>
     const mapLayer = Layer.effect(LocationServiceMap.Service, mapEffect)
     const map = Node.makeGlobalNode({ service: LocationServiceMap.Service, layer: mapLayer, deps: [b] })
-    const graph = LayerNodeTree.bind(LayerNode.group([a]), LocationServiceMap.node, map)
+    const graph = LayerNode.bind(LayerNode.group([a]), LocationServiceMap.node, map)
 
     expect(() => NodeBuild.build(graph)).toThrow("Cycle detected in layer tree")
   })

+ 2 - 1
packages/core/test/process/process.test.ts

@@ -5,10 +5,11 @@ import { tmpdir } from "node:os"
 import path from "node:path"
 import { Effect, Exit, Fiber, Stream } from "effect"
 import { ChildProcess } from "effect/unstable/process"
+import { LayerNode } from "@opencode-ai/core/effect/layer-node"
 import { AppProcess } from "@opencode-ai/core/process"
 import { testEffect } from "../lib/effect"
 
-const it = testEffect(AppProcess.defaultLayer)
+const it = testEffect(LayerNode.compile(AppProcess.node))
 
 const NODE = process.execPath
 const cmd = (...args: string[]) => ChildProcess.make(NODE, args)

+ 1 - 1
packages/opencode/src/account/account.ts

@@ -1,5 +1,5 @@
 import { LayerNode } from "@opencode-ai/core/effect/layer-node"
-import { httpClient } from "@opencode-ai/core/effect/layer-node-platform"
+import { httpClient } from "@opencode-ai/core/effect/app-node-platform"
 import { Cache, Clock, Duration, Effect, Layer, Option, Schema, SchemaGetter, Context } from "effect"
 import { serviceUse } from "@opencode-ai/core/effect/service-use"
 import {

+ 1 - 1
packages/opencode/src/config/config.ts

@@ -1,5 +1,5 @@
 import { LayerNode } from "@opencode-ai/core/effect/layer-node"
-import { httpClient } from "@opencode-ai/core/effect/layer-node-platform"
+import { httpClient } from "@opencode-ai/core/effect/app-node-platform"
 import { serviceUse } from "@opencode-ai/core/effect/service-use"
 import path from "path"
 import { pathToFileURL } from "url"

+ 1 - 1
packages/opencode/src/control-plane/workspace.ts

@@ -1,5 +1,5 @@
 import { LayerNode } from "@opencode-ai/core/effect/layer-node"
-import { httpClient } from "@opencode-ai/core/effect/layer-node-platform"
+import { httpClient } from "@opencode-ai/core/effect/app-node-platform"
 import { Context, Effect, FiberMap, Iterable, Layer, Schema, Stream } from "effect"
 import { serviceUse } from "@opencode-ai/core/effect/service-use"
 import { FetchHttpClient, HttpBody, HttpClient, HttpClientError, HttpClientRequest } from "effect/unstable/http"

+ 1 - 1
packages/opencode/src/installation/index.ts

@@ -1,5 +1,5 @@
 import { LayerNode } from "@opencode-ai/core/effect/layer-node"
-import { httpClient } from "@opencode-ai/core/effect/layer-node-platform"
+import { httpClient } from "@opencode-ai/core/effect/app-node-platform"
 import { Effect, Layer, Schema, Context, Stream } from "effect"
 import { serviceUse } from "@opencode-ai/core/effect/service-use"
 import { FetchHttpClient, HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http"

+ 2 - 3
packages/opencode/src/server/routes/instance/httpapi/server.ts

@@ -52,8 +52,7 @@ import { RuntimeFlags } from "@/effect/runtime-flags"
 import { MoveSession } from "@opencode-ai/core/control-plane/move-session"
 import { Database } from "@opencode-ai/core/database/database"
 import { LayerNode } from "@opencode-ai/core/effect/layer-node"
-import { LayerNodeTree } from "@opencode-ai/core/effect/layer-node"
-import { httpClient } from "@opencode-ai/core/effect/layer-node-platform"
+import { httpClient } from "@opencode-ai/core/effect/app-node-platform"
 import { EventV2 } from "@opencode-ai/core/event"
 import { ModelsDev } from "@opencode-ai/core/models-dev"
 import { Npm } from "@opencode-ai/core/npm"
@@ -302,7 +301,7 @@ export function createRoutes(
     ),
     Layer.provide(locationServiceMapLayer),
 
-    Layer.provide(LayerNodeTree.compile(app)),
+    Layer.provide(LayerNode.compile(app)),
   )
 }
 

+ 1 - 1
packages/opencode/src/session/instruction.ts

@@ -1,5 +1,5 @@
 import { LayerNode } from "@opencode-ai/core/effect/layer-node"
-import { httpClient } from "@opencode-ai/core/effect/layer-node-platform"
+import { httpClient } from "@opencode-ai/core/effect/app-node-platform"
 import path from "path"
 import { SessionV1 } from "@opencode-ai/core/v1/session"
 import { Effect, Layer, Context } from "effect"

+ 1 - 1
packages/opencode/src/session/llm.ts

@@ -1,5 +1,5 @@
 import { LayerNode } from "@opencode-ai/core/effect/layer-node"
-import { llmClient } from "@opencode-ai/core/effect/layer-node-platform"
+import { llmClient } from "@opencode-ai/core/effect/app-node-platform"
 import { PermissionV1 } from "@opencode-ai/core/v1/permission"
 import { Provider } from "@/provider/provider"
 import { SessionV1 } from "@opencode-ai/core/v1/session"

+ 1 - 1
packages/opencode/src/share/share-next.ts

@@ -1,5 +1,5 @@
 import { LayerNode } from "@opencode-ai/core/effect/layer-node"
-import { httpClient } from "@opencode-ai/core/effect/layer-node-platform"
+import { httpClient } from "@opencode-ai/core/effect/app-node-platform"
 import type * as SDK from "@opencode-ai/sdk/v2"
 import { serviceUse } from "@opencode-ai/core/effect/service-use"
 import { Effect, Exit, Layer, Option, Schema, Scope, Context, Stream } from "effect"

+ 1 - 1
packages/opencode/src/skill/discovery.ts

@@ -1,5 +1,5 @@
 import { LayerNode } from "@opencode-ai/core/effect/layer-node"
-import { httpClient, path } from "@opencode-ai/core/effect/layer-node-platform"
+import { httpClient, path } from "@opencode-ai/core/effect/app-node-platform"
 import { NodePath } from "@effect/platform-node"
 import { Effect, Layer, Path, Schema, Context } from "effect"
 import { FetchHttpClient, HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http"

+ 1 - 1
packages/opencode/src/tool/registry.ts

@@ -1,5 +1,5 @@
 import { LayerNode } from "@opencode-ai/core/effect/layer-node"
-import { httpClient } from "@opencode-ai/core/effect/layer-node-platform"
+import { httpClient } from "@opencode-ai/core/effect/app-node-platform"
 import { Ripgrep } from "@opencode-ai/core/ripgrep"
 import { PlanExitTool } from "./plan"
 import { Session } from "@/session/session"

+ 1 - 1
packages/opencode/src/worktree/index.ts

@@ -1,5 +1,5 @@
 import { LayerNode } from "@opencode-ai/core/effect/layer-node"
-import { path } from "@opencode-ai/core/effect/layer-node-platform"
+import { path } from "@opencode-ai/core/effect/app-node-platform"
 import { Global } from "@opencode-ai/core/global"
 import { InstanceLayer } from "@/project/instance-layer"
 import { InstanceStore } from "@/project/instance-store"

+ 2 - 3
packages/opencode/test/git/git.test.ts

@@ -1,16 +1,15 @@
 import { $ } from "bun"
-import { LayerNodeTree } from "@opencode-ai/core/effect/layer-node"
+import { LayerNode } from "@opencode-ai/core/effect/layer-node"
 import { describe, expect } from "bun:test"
 import fs from "fs/promises"
 import path from "path"
 import { Effect } from "effect"
-import { LayerNode } from "@opencode-ai/core/effect/layer-node"
 import { Git } from "../../src/git"
 import { tmpdir } from "../fixture/fixture"
 import { testEffect } from "../lib/effect"
 
 const weird = process.platform === "win32" ? "space file.txt" : "tab\tfile.txt"
-const it = testEffect(LayerNodeTree.compile(LayerNode.group([Git.node])))
+const it = testEffect(LayerNode.compile(LayerNode.group([Git.node])))
 
 const scopedTmpdir = (options?: Parameters<typeof tmpdir>[0]) =>
   Effect.acquireRelease(

+ 2 - 3
packages/opencode/test/session/messages-pagination.test.ts

@@ -1,7 +1,6 @@
 import { describe, expect, test } from "bun:test"
-import { LayerNodeTree } from "@opencode-ai/core/effect/layer-node"
-import { SessionV1 } from "@opencode-ai/core/v1/session"
 import { LayerNode } from "@opencode-ai/core/effect/layer-node"
+import { SessionV1 } from "@opencode-ai/core/v1/session"
 import { SessionProjector } from "@opencode-ai/core/session/projector"
 import { Effect, Option } from "effect"
 import { Session as SessionNs } from "@/session/session"
@@ -13,7 +12,7 @@ import { testEffect } from "../lib/effect"
 import { ProviderV2 } from "@opencode-ai/core/provider"
 import { ModelV2 } from "@opencode-ai/core/model"
 
-const it = testEffect(LayerNodeTree.compile(LayerNode.group([SessionNs.node, MessageV2.node, SessionProjector.node])))
+const it = testEffect(LayerNode.compile(LayerNode.group([SessionNs.node, MessageV2.node, SessionProjector.node])))
 
 const withSession = <A, E, R>(
   fn: (input: { session: SessionNs.Interface; sessionID: SessionID }) => Effect.Effect<A, E, R>,

+ 3 - 4
packages/opencode/test/session/processor-effect.test.ts

@@ -1,7 +1,6 @@
 import { SessionV1 } from "@opencode-ai/core/v1/session"
 import { Database } from "@opencode-ai/core/database/database"
 import { LayerNode } from "@opencode-ai/core/effect/layer-node"
-import { LayerNodeTree } from "@opencode-ai/core/effect/layer-node"
 import { EventV2Bridge } from "@/event-v2-bridge"
 import { expect } from "bun:test"
 import { tool } from "ai"
@@ -180,7 +179,7 @@ const replacements = [
   LayerNode.replace(SessionSummary.layer, summary),
   LayerNode.replace(RuntimeFlags.defaultLayer, RuntimeFlags.layer({ experimentalEventSystem: true })),
 ]
-const env = LayerNodeTree.compile(
+const env = LayerNode.compile(
   LayerNode.group([root, LayerNode.make({ service: TestLLMServer, layer: TestLLMServer.layer, deps: [] })]),
   new Map(replacements.map((item) => [item.source, item.replacement])),
 )
@@ -207,7 +206,7 @@ const providerErrorLLM = Layer.succeed(
       ),
   }),
 )
-const providerErrorEnv = LayerNodeTree.compile(
+const providerErrorEnv = LayerNode.compile(
   root,
   new Map(
     [...replacements, LayerNode.replace(LLM.layer, providerErrorLLM)].map((item) => [item.source, item.replacement]),
@@ -229,7 +228,7 @@ const fragmentFailureLLM = Layer.succeed(
       ),
   }),
 )
-const fragmentFailureEnv = LayerNodeTree.compile(
+const fragmentFailureEnv = LayerNode.compile(
   root,
   new Map(
     [...replacements, LayerNode.replace(LLM.layer, fragmentFailureLLM)].map((item) => [item.source, item.replacement]),

+ 2 - 3
packages/opencode/test/session/retry.test.ts

@@ -1,11 +1,10 @@
 import { describe, expect, test } from "bun:test"
-import { LayerNodeTree } from "@opencode-ai/core/effect/layer-node"
+import { LayerNode } from "@opencode-ai/core/effect/layer-node"
 import { SessionV1 } from "@opencode-ai/core/v1/session"
 import type { NamedError } from "@opencode-ai/core/util/error"
 import { APICallError } from "ai"
 import { setTimeout as sleep } from "node:timers/promises"
 import { Effect, Schedule, Schema } from "effect"
-import { LayerNode } from "@opencode-ai/core/effect/layer-node"
 import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
 import { SessionRetry } from "../../src/session/retry"
 import { MessageV2 } from "../../src/session/message-v2"
@@ -17,7 +16,7 @@ import { ProviderV2 } from "@opencode-ai/core/provider"
 
 const providerID = ProviderV2.ID.make("test")
 const retryProvider = "test"
-const it = testEffect(LayerNodeTree.compile(LayerNode.group([SessionStatus.node, CrossSpawnSpawner.node])))
+const it = testEffect(LayerNode.compile(LayerNode.group([SessionStatus.node, CrossSpawnSpawner.node])))
 
 function apiError(headers?: Record<string, string>): SessionV1.APIError {
   return Schema.decodeUnknownSync(SessionV1.APIError.Schema)(

+ 1 - 2
packages/opencode/test/session/snapshot-tool-race.test.ts

@@ -14,7 +14,6 @@
 import { expect } from "bun:test"
 import { Effect, Layer } from "effect"
 import { LayerNode } from "@opencode-ai/core/effect/layer-node"
-import { LayerNodeTree } from "@opencode-ai/core/effect/layer-node"
 import fs from "fs/promises"
 import path from "path"
 import { Session } from "@/session/session"
@@ -88,7 +87,7 @@ const root = LayerNode.group([
   LayerNode.make({ service: TestLLMServer, layer: TestLLMServer.layer, deps: [] }),
 ])
 const it = testEffect(
-  LayerNodeTree.compile(
+  LayerNode.compile(
     root,
     new Map(
       [

+ 4 - 5
packages/opencode/test/share/share-next.test.ts

@@ -2,8 +2,7 @@ import { beforeEach, describe, expect } from "bun:test"
 import { Effect, Exit, Layer, Option } from "effect"
 import { FetchHttpClient, HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http"
 import { LayerNode } from "@opencode-ai/core/effect/layer-node"
-import { LayerNodeTree } from "@opencode-ai/core/effect/layer-node"
-import { httpClient } from "@opencode-ai/core/effect/layer-node-platform"
+import { httpClient } from "@opencode-ai/core/effect/app-node-platform"
 import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
 import { SessionProjector } from "@opencode-ai/core/session/projector"
 
@@ -20,7 +19,7 @@ import { provideTmpdirInstance } from "../fixture/fixture"
 import { resetDatabase } from "../fixture/db"
 import { pollWithTimeout, testEffect } from "../lib/effect"
 
-const env = LayerNodeTree.compile(LayerNode.group([CrossSpawnSpawner.node]))
+const env = LayerNode.compile(LayerNode.group([CrossSpawnSpawner.node]))
 const it = testEffect(env)
 
 const json = (req: Parameters<typeof HttpClientResponse.fromWeb>[0], body: unknown, status = 200) =>
@@ -36,7 +35,7 @@ const none = HttpClient.make(() => Effect.die("unexpected http call"))
 
 function requestLayer(client: HttpClient.HttpClient) {
   const replacement = LayerNode.replace(FetchHttpClient.layer, Layer.succeed(HttpClient.HttpClient, client))
-  return LayerNodeTree.compile(
+  return LayerNode.compile(
     LayerNode.group([ShareNext.node, AccountRepo.node]),
     new Map([[replacement.source, replacement.replacement]]),
   )
@@ -44,7 +43,7 @@ function requestLayer(client: HttpClient.HttpClient) {
 
 function integrationLayer(client: HttpClient.HttpClient) {
   const replacement = LayerNode.replace(FetchHttpClient.layer, Layer.succeed(HttpClient.HttpClient, client))
-  return LayerNodeTree.compile(
+  return LayerNode.compile(
     LayerNode.group([
       ShareNext.node,
       EventV2Bridge.node,

+ 2 - 3
packages/opencode/test/tool/registry.test.ts

@@ -4,7 +4,6 @@ import fs from "fs/promises"
 import { fileURLToPath, pathToFileURL } from "url"
 import { Effect, Layer, Result, Schema } from "effect"
 import { LayerNode } from "@opencode-ai/core/effect/layer-node"
-import { LayerNodeTree } from "@opencode-ai/core/effect/layer-node"
 import { ToolRegistry } from "@/tool/registry"
 import { Tool } from "@/tool/tool"
 import { disposeAllInstances, TestInstance } from "../fixture/fixture"
@@ -55,9 +54,9 @@ const replacements = [
   LayerNode.replace(RuntimeFlags.defaultLayer, RuntimeFlags.layer()),
 ]
 
-const it = testEffect(LayerNodeTree.compile(root, new Map(replacements.map((item) => [item.source, item.replacement]))))
+const it = testEffect(LayerNode.compile(root, new Map(replacements.map((item) => [item.source, item.replacement]))))
 const withBrokenPlugin = testEffect(
-  LayerNodeTree.compile(
+  LayerNode.compile(
     root,
     new Map(
       [...replacements, LayerNode.replace(Plugin.layer, brokenPluginLayer)].map((item) => [

+ 1 - 1
packages/server/src/pty-environment.ts

@@ -1,7 +1,7 @@
 export * as PtyEnvironment from "./pty-environment"
 
 import { Context, Effect, Layer } from "effect"
-import { makeGlobalNode } from "@opencode-ai/core/effect/node"
+import { makeGlobalNode } from "@opencode-ai/core/effect/app-node"
 
 export interface Interface {
   readonly get: (input: { directory: string; cwd: string }) => Effect.Effect<Record<string, string>>

+ 4 - 4
packages/server/src/routes.ts

@@ -1,7 +1,7 @@
 import { Database } from "@opencode-ai/core/database/database"
-import { LayerNode, LayerNodeTree } from "@opencode-ai/core/effect/layer-node"
-import { httpClient } from "@opencode-ai/core/effect/layer-node-platform"
-import { NodeBuild } from "@opencode-ai/core/effect/node-build"
+import { LayerNode } from "@opencode-ai/core/effect/layer-node"
+import { httpClient } from "@opencode-ai/core/effect/app-node-platform"
+import { NodeBuild } from "@opencode-ai/core/effect/app-node-builder"
 import { EventV2 } from "@opencode-ai/core/event"
 import { Credential } from "@opencode-ai/core/credential"
 import { PermissionSaved } from "@opencode-ai/core/permission/saved"
@@ -50,7 +50,7 @@ export function createEmbeddedRoutes() {
 
 function makeRoutes<AuthError, AuthServices>(auth: Layer.Layer<ServerAuth.Config, AuthError, AuthServices>) {
   const serviceLayer = NodeBuild.build(
-    LayerNodeTree.bind(applicationServices, SessionExecution.node, SessionExecutionLocal.node),
+    LayerNode.bind(applicationServices, SessionExecution.node, SessionExecutionLocal.node),
   )
 
   return HttpApiBuilder.layer(Api, { openapiPath: "/openapi.json" }).pipe(