From 1c453bd4e1751014926c1edad6456ae3674bddf0 Mon Sep 17 00:00:00 2001 From: James Long Date: Sun, 17 May 2026 15:53:11 -0400 Subject: [PATCH] fix --- dependency-graph.md | 234 ++++++++++++------ packages/opencode/src/cli/cmd/tui/simulate.ts | 4 + .../server/routes/instance/httpapi/server.ts | 57 +++-- 3 files changed, 207 insertions(+), 88 deletions(-) diff --git a/dependency-graph.md b/dependency-graph.md index 48c5117712a..4a5ce02bcb7 100644 --- a/dependency-graph.md +++ b/dependency-graph.md @@ -4,43 +4,42 @@ Generated for `createSimulatedRoutes` in `packages/opencode/src/server/routes/in ## Notation -- `→ X` means "yields `X.Service` from its `Effect.gen` body" +- `→ X` means "yields `X.Service` from its `Effect.gen` body at layer init (a true `RIn` of `.layer`)" - `(lazy)` means "uses `InstanceState.context` or similar at call time, not at layer construction" - `(opt)` means "uses `Effect.serviceOption(X)` — not strictly required" +- `(internal)` means "satisfied internally by the `.layer` itself via `Layer.provide(...)`, NOT a residual requirement" -## Service → Dependencies +## Service → Dependencies (current, post-rebase) ``` ─── External / Platform ───────────────────────────────────────── NodePath (no app deps) provides Path.Path -HttpClient (FetchHttp) (no app deps) provides HttpClient.HttpClient +FetchHttpClient (no app deps) provides HttpClient.HttpClient HttpServer.layerServices (no app deps) ChildProcessSpawner (from SimulationSpawner) (no app deps) ─── Leaf services (no app deps) ───────────────────────────────── Global (no app deps) -Env (no app deps) -Bus (lazy via InstanceState) -SyncEvent (lazy via InstanceState) -AccountRepo (uses Database directly) -PtyTicket (Cache only) -Truncate → AppFileSystem (+ Config opt) +Env (no app deps — uses InstanceState.make, no Service yields) +Bus (no app deps — uses InstanceState.make, no Service yields) +SyncEvent → RuntimeFlags, Bus (NEW — previously listed as leaf/lazy) +AccountRepo (no app deps — pure DB closures) +PtyTicket (no app deps — Cache only) +Truncate → AppFileSystem ─── Middleware/route layers (no app service deps) ─────────────── errorLayer compressionLayer → HttpServerRequest (builtin) corsVaryFix fenceLayer → HttpServerRequest (builtin) -runtime simulationShareNextLayer provides ShareNext (Layer.succeed override) ─── Simulation overrides ──────────────────────────────────────── -simulationBoundary: - SimulationFileSystem provides AppFileSystem + FileSystem.FileSystem(noop) - SimulationSpawner provides ChildProcessSpawner - SimulationNetwork provides SimulationNetwork.Service + HttpClient - (httpClientLayer → SimulationNetwork) - +SimulationFileSystem provides AppFileSystem (does NOT provide FileSystem.FileSystem; + tier0 also merges FileSystem.layerNoop({}) for that tag) +SimulationSpawner provides ChildProcessSpawner (Layer.succeed; no deps) +SimulationNetwork provides SimulationNetwork.Service + HttpClient.HttpClient + (httpClientLayer composed inside `layer(options)`) SimulationGit → AppFileSystem (overrides Git tag) SimulationProvider → Simulation (overrides Provider tag) Simulation → AppFileSystem, SimulationNetwork @@ -48,31 +47,34 @@ Simulation → AppFileSystem, SimulationNetwork ─── Core services ─────────────────────────────────────────────── EffectFlock → Global, AppFileSystem Auth → AppFileSystem +McpAuth → AppFileSystem Account → AccountRepo, HttpClient Npm → AppFileSystem, Global, FileSystem.FileSystem, EffectFlock Config → AppFileSystem, Auth, Account, Env, Npm Permission → Bus -Plugin → Bus, Config +Plugin → Bus, Config, RuntimeFlags (NEW: RuntimeFlags) Discovery → AppFileSystem, Path, HttpClient -Skill → Discovery, Config, Bus, AppFileSystem, Global +Skill → Discovery, Config, Bus, AppFileSystem, Global, + RuntimeFlags (NEW: RuntimeFlags) SystemPrompt → Skill ─── File / git ────────────────────────────────────────────────── Ripgrep → AppFileSystem, HttpClient, ChildProcessSpawner File → AppFileSystem, Ripgrep, Git, Scope FileWatcher → Config, Git -Format → Config, ChildProcessSpawner -Snapshot → AppFileSystem, ChildProcessSpawner, Config +Format → Config, AppProcess, RuntimeFlags (CHANGED: was ChildProcessSpawner; now AppProcess + RuntimeFlags) +Snapshot → AppFileSystem, AppProcess, Config (CHANGED: was ChildProcessSpawner) Storage → AppFileSystem, Git Vcs → Git, Bus, Scope -Worktree → Scope, AppFileSystem, Path, ChildProcessSpawner, - Git, Project, InstanceStore -Project → AppFileSystem, Path, ChildProcessSpawner, Bus +Worktree → Scope, AppFileSystem, Path, AppProcess, + Git, Project, InstanceStore (CHANGED: AppProcess instead of ChildProcessSpawner) +Project → AppFileSystem, Path, ChildProcessSpawner, + Bus, RuntimeFlags (NEW: RuntimeFlags) ─── Provider / LSP / MCP ──────────────────────────────────────── ModelsDev → AppFileSystem, HttpClient ProviderAuth → Auth, Plugin -LSP → Config +LSP → Config, RuntimeFlags (NEW: RuntimeFlags) McpAuth → AppFileSystem MCP → ChildProcessSpawner, McpAuth, Bus, Config @@ -80,98 +82,146 @@ MCP → ChildProcessSpawner, McpAuth, Bus, Config Todo → Bus Question → Bus SessionStatus → Bus -SessionRunState → SessionStatus -Instruction → Config, AppFileSystem, Global, HttpClient +SessionRunState → BackgroundJob, SessionStatus (NEW: BackgroundJob) +Instruction → Config, AppFileSystem, Global, HttpClient, + RuntimeFlags (NEW: RuntimeFlags) -Session → Bus, Storage, SyncEvent +Session → BackgroundJob, Bus, Storage, SyncEvent, + RuntimeFlags (NEW: BackgroundJob, RuntimeFlags) SessionSummary → Session, Snapshot, Storage, Bus SessionRevert → Session, Snapshot, Storage, Bus, SessionSummary, SessionRunState, SyncEvent -LLM → Auth, Config, Provider, Plugin, Permission +LLM → Auth, Config, Provider, Plugin, RuntimeFlags (CHANGED: Permission satisfied internally; + (Permission satisfied internally via .layer) RuntimeFlags is new) -Agent → Config, Auth, Plugin, Skill, Provider +Agent → Config, Auth, Plugin, Skill, Provider, + RuntimeFlags (NEW: RuntimeFlags) Command → Config, MCP, Skill SessionProcessor → Session, Config, Bus, Snapshot, Agent, LLM, - Permission, Plugin, SessionSummary, Scope, - SessionStatus + Permission, Plugin, SessionSummary, + SessionStatus, Image, EventV2Bridge, + RuntimeFlags, Scope (NEW: Image, EventV2Bridge, RuntimeFlags) SessionCompaction → Bus, Config, Session, Agent, Plugin, - SessionProcessor, Provider + SessionProcessor, Provider, EventV2Bridge, + RuntimeFlags (NEW: EventV2Bridge, RuntimeFlags) SessionPrompt → Bus, SessionStatus, Session, Agent, Provider, SessionProcessor, SessionCompaction, Plugin, Command, Config, Permission, AppFileSystem, MCP, LSP, ToolRegistry, Truncate, ChildProcessSpawner, Scope, Instruction, SessionRunState, SessionRevert, - SessionSummary, SystemPrompt, LLM + SessionSummary, SystemPrompt, LLM, + Image, Reference, EventV2Bridge, + RuntimeFlags (NEW: Image, Reference, EventV2Bridge, RuntimeFlags) -ToolRegistry → Config, Plugin, Agent, Skill, Truncate (+ many - tool deps: Question, Todo, Session, Provider, - Git, LSP, Instruction, AppFileSystem, Bus, - HttpClient, ChildProcessSpawner, Ripgrep, - Format, Truncate) +ToolRegistry → Config, Plugin, Question, Todo, Agent, Skill, + Session, SessionStatus, BackgroundJob, + Provider, Git, Reference, LSP, Instruction, + AppFileSystem, Bus, HttpClient, + ChildProcessSpawner, Ripgrep, Format, + Truncate, RuntimeFlags (NEW: BackgroundJob, Reference, RuntimeFlags) ─── Share / Workspace ─────────────────────────────────────────── ShareNext (provided by simulationShareNextLayer in sim) -SessionShare → Config, Session, ShareNext, Scope, SyncEvent +SessionShare → Config, Session, ShareNext, Scope, SyncEvent, + RuntimeFlags (NEW: RuntimeFlags) Workspace → Auth, Session, SessionPrompt, HttpClient, - SyncEvent, Vcs - (also uses InstanceStore/Bootstrap via - Effect.provide inside runInWorkspace) + SyncEvent, Vcs, AppFileSystem, RuntimeFlags (NEW: AppFileSystem (explicit), RuntimeFlags) ─── Misc ──────────────────────────────────────────────────────── -Installation → HttpClient, ChildProcessSpawner +Installation → HttpClient, AppProcess (CHANGED: AppProcess instead of ChildProcessSpawner) Pty → Config, Bus, Plugin ─── Instance lifecycle ────────────────────────────────────────── InstanceBootstrap → Config, File, FileWatcher, Format, LSP, Plugin, - Project, ShareNext, Snapshot, Vcs + Project, Reference, ShareNext, Snapshot, Vcs (NEW: Reference) InstanceStore → Project, InstanceBootstrap, Scope Observability (no app deps; provides Logger + tracer) ``` +## NEW dependencies introduced since previous graph + +The rebase pulled in several new cross-cutting services that need to be +satisfied somewhere in tier0/tier1 of the simulated chain. They are NOT yet +listed in the `Tier*Services` unions or merged into any tier in `server.ts`: + +``` +RuntimeFlags.Service — yielded by ~17 services (Plugin, Skill, Project, + Session, SyncEvent, Format, LSP, Instruction, + Agent, LLM, SessionShare, SessionProcessor, + SessionCompaction, ToolRegistry, SessionPrompt, + Workspace, SessionRunState). + Source: `@/effect/runtime-flags`. + +AppProcess.Service — yielded by Installation, Format, Snapshot, + Worktree (and prod Git, but sim uses SimulationGit). + Source: presumed `@opencode-ai/core/app-process` + or similar — needs `AppProcess.defaultLayer`. + +BackgroundJob.Service — yielded by Session, SessionRunState, ToolRegistry. + Needs `BackgroundJob.defaultLayer`. + +Image.Service — yielded by SessionProcessor, SessionPrompt. + +EventV2Bridge.Service — yielded by SessionProcessor, SessionCompaction, + SessionPrompt. Source: `@/event-v2-bridge` (already + imported in server.ts but never added to a tier). + +Reference.Service — yielded by ToolRegistry, SessionPrompt, + InstanceBootstrap. +``` + ## Dependency Tiers (topological order) Roughly, build order from leaves to roots: ``` Tier 0 (no deps): - Global, Env, NodePath, AccountRepo, PtyTicket, Bus (lazy), - SyncEvent (lazy), AppFileSystem (sim), ChildProcessSpawner (sim), - HttpClient (sim), SimulationNetwork, errorLayer, compressionLayer, - corsVaryFix, fenceLayer, runtime, simulationShareNextLayer + Global, Env, NodePath, AccountRepo, PtyTicket, Bus, SyncEvent, + AppFileSystem (sim), ChildProcessSpawner (sim), HttpClient (sim), + SimulationNetwork, FileSystem.layerNoop, simulationShareNextLayer + + (NEW REQUIRED) RuntimeFlags, AppProcess, BackgroundJob, Image, + EventV2Bridge, Reference + (SyncEvent now depends on RuntimeFlags + Bus, so it's actually tier 1.) Tier 1: Auth, Truncate, EffectFlock, Permission, Todo, Question, - SessionStatus, McpAuth, Discovery, SimulationGit, Simulation, - Ripgrep, Storage, Vcs + SessionStatus, McpAuth, Discovery, SimulationGit, Ripgrep, Account, + SyncEvent (needs RuntimeFlags + Bus) Tier 2: - Account, Npm, LSP, Skill, SessionRunState, ModelsDev, File, - Project, Worktree (needs InstanceStore — cycle hint), MCP, - Installation, Pty, SystemPrompt + Npm, ModelsDev, Project, Installation, Storage, Vcs, SessionRunState Tier 3: - Config, FileWatcher, Format, Snapshot, ProviderAuth, Session, - SessionShare, SessionSummary + Config, File, Simulation, Session Tier 4: - Plugin, Agent, Command, LLM, SessionRevert, + Plugin, FileWatcher, Format, Snapshot, LSP, MCP, Skill, Instruction, SimulationProvider (= Provider) Tier 5: - SessionProcessor, SessionCompaction, Workspace + Pty, ProviderAuth, SessionSummary, Agent, Command, LLM, SystemPrompt Tier 6: - SessionPrompt, ToolRegistry + SessionRevert, SessionProcessor, SessionShare Tier 7: - InstanceBootstrap (needs many of the above) + SessionCompaction, ToolRegistry Tier 8: - InstanceStore (needs InstanceBootstrap + Project) + SessionPrompt + +Tier 9: + Workspace, InstanceBootstrap + +Tier 10: + InstanceStore + +Tier 11: + Worktree ``` ## Potential cycles / hazards @@ -180,8 +230,7 @@ Tier 8: Worktree → InstanceStore → InstanceBootstrap → Project → (back to Worktree?) - InstanceBootstrap requires Project (yes) - Project does NOT require Worktree directly - - Worktree requires InstanceStore — only used inside its methods, - but yielded at layer init, so it's a true requirement + - Worktree requires InstanceStore at layer init → Worktree must be built AFTER InstanceStore. SimulationProvider provides Provider tag, depends on Simulation. @@ -189,26 +238,63 @@ SimulationProvider provides Provider tag, depends on Simulation. SimulationProvider in this layer chain. SimulationGit provides Git tag, used by File, FileWatcher, - Storage, Vcs, Worktree, ToolRegistry tools, Project (no — Project - uses ChildProcessSpawner instead). + Storage, Vcs, Worktree, ToolRegistry tools. LLM.layer pipes Layer.provide(Permission.defaultLayer) internally. - That means LLM brings its own Permission — beware double-providing - Permission (sim chain also provides Permission.layer separately). + So LLM's residual requirements no longer include Permission, BUT the + sim chain still provides Permission.layer separately (correct — used by + SessionProcessor, SessionPrompt directly). ``` -## Why the simulated chain fails with stepwise `provideMerge` +## Why the simulated chain fails today -`Layer.provideMerge(self, that)` builds `that` independently and uses it to satisfy `self`'s reqs. `that` cannot see services already provided inside `self`. So when you write: - -```ts -withCoreAppServices // contains Config.layer, Plugin.layer, ... - .pipe(Layer.provideMerge(Npm.layer)) // Npm needs EffectFlock +The current `server.ts` `Tier0Services` union lists: +``` +AppFileSystem, FileSystem.FileSystem, ChildProcessSpawner, HttpClient, +SimulationNetwork, Path, Global, Env, Bus, AccountRepo, ShareNext, +SyncEvent, PtyTicket ``` -`Npm.layer` is built in isolation and its `EffectFlock` requirement must be filled from OUTSIDE the entire pipe — not from inside `withCoreAppServices`. Same for every other "consumer added later" combination. +But the actual `Layer.mergeAll(...)` body at tier0 has residual requirements +beyond that union. The TS error says +`Layer<..., never, Service | Service>` — those two unresolved `Service`s +are members of the NEW dependencies table above. -Production avoids this because every service is added via `Layer.provide([... defaultLayer, defaultLayer, ...])` — flat mergeAll of self-contained layers, all visible to each other. +The most likely culprits, in order of leakage: + +1. **`SyncEvent.layer`** now yields `RuntimeFlags` and `Bus`. `Bus` is in tier0 + already, but `RuntimeFlags` is not provided anywhere in `createSimulatedRoutes`. + So `SyncEvent` leaks `RuntimeFlags` into tier0's `RIn`. + +2. Downstream tiers also leak `RuntimeFlags`, `AppProcess`, `BackgroundJob`, + `Image`, `EventV2Bridge`, `Reference` — these cascade through every higher + tier as `Service | Service | ...` in the error type. + +## Fix strategy + +The minimal change to make tier0 type-check: + +1. Add `RuntimeFlags.defaultLayer` to tier0 and `RuntimeFlags.Service` to + `Tier0Services`. This satisfies `SyncEvent`'s new dep and unblocks every + service that yields `RuntimeFlags`. +2. Add `AppProcess.defaultLayer` (or a simulated equivalent) to tier0 and + `AppProcess.Service` to `Tier0Services`. Needed by `Installation`, `Format`, + `Snapshot`, `Worktree`. +3. Add `BackgroundJob.defaultLayer` to tier0 and `BackgroundJob.Service` to + `Tier0Services`. Needed by `Session`, `SessionRunState`, `ToolRegistry`. +4. Add `Image.defaultLayer` to tier0 (or wherever it fits) and `Image.Service` + to `Tier0Services`. Needed by `SessionProcessor`, `SessionPrompt`. +5. Add `EventV2Bridge.defaultLayer` to tier0 and `EventV2Bridge.Service` to + `Tier0Services`. (Note: `EventV2Bridge` is already imported in `server.ts` + for the production routes but is not in any simulated tier.) +6. Add `Reference.defaultLayer` to a tier that satisfies its deps (it's a leaf + wrt the listed graph above) and `Reference.Service` to the corresponding + tier union. Needed by `ToolRegistry`, `SessionPrompt`, `InstanceBootstrap`. + +Production routes (`createProductionRoutes`) already include +`RuntimeFlags.defaultLayer` and `EventV2Bridge.defaultLayer` in the flat +`Layer.provide([...])` list — they were simply never carried over to the +simulated chain after the rebase. ## See also diff --git a/packages/opencode/src/cli/cmd/tui/simulate.ts b/packages/opencode/src/cli/cmd/tui/simulate.ts index 20523470107..fc2f3412e9d 100644 --- a/packages/opencode/src/cli/cmd/tui/simulate.ts +++ b/packages/opencode/src/cli/cmd/tui/simulate.ts @@ -165,6 +165,10 @@ export const SimulateCommand = cmd({ [OPENCODE_RUN_ID]: ensureRunID(), PWD: simulatedDirectory, [simulatedCwdEnv]: simulatedDirectory, + // Simulated filesystem lives in InMemoryFs — SQLite needs to be in-memory + // too, since real fs writes to `/opencode/.local/share/opencode/...` will + // fail (the directory only exists in the sim FS). + OPENCODE_DB: ":memory:", }), }) worker.onerror = (e) => { diff --git a/packages/opencode/src/server/routes/instance/httpapi/server.ts b/packages/opencode/src/server/routes/instance/httpapi/server.ts index 576987e2942..25000b55315 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/server.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/server.ts @@ -1,6 +1,11 @@ import { Config as EffectConfig, Context, Effect, Layer, FileSystem, Path } from "effect" import { ChildProcessSpawner } from "effect/unstable/process" import { NodePath } from "@effect/platform-node" +import { AppProcess } from "@opencode-ai/core/process" +import { EventV2 } from "@opencode-ai/core/event" +import { BackgroundJob } from "@/background/job" +import { Image } from "@/image/image" +import { Reference } from "@/reference/reference" import { Git } from "@/git" import { HttpApiBuilder, OpenApi } from "effect/unstable/httpapi" import { @@ -318,8 +323,10 @@ export function createSimulatedRoutes(corsOptions?: CorsOptions): ReturnType = Layer.mergeAll( SimulationFileSystem.layer({ @@ -341,8 +348,10 @@ export function createSimulatedRoutes(corsOptions?: CorsOptions): ReturnType = Layer.mergeAll( @@ -387,6 +400,8 @@ export function createSimulatedRoutes(corsOptions?: CorsOptions): ReturnType = Layer.mergeAll( @@ -431,6 +450,7 @@ export function createSimulatedRoutes(corsOptions?: CorsOptions): ReturnType = Layer.mergeAll( @@ -489,6 +514,8 @@ export function createSimulatedRoutes(corsOptions?: CorsOptions): ReturnType