mirror of
https://github.com/anomalyco/opencode.git
synced 2026-05-18 10:07:58 +00:00
docs(effect): refresh migration status specs (#23206)
This commit is contained in:
@@ -1,12 +1,13 @@
|
||||
# Facade removal checklist
|
||||
|
||||
Concrete inventory of the remaining `makeRuntime(...)`-backed service facades in `packages/opencode`.
|
||||
Concrete inventory of the remaining `makeRuntime(...)`-backed facades in `packages/opencode`.
|
||||
|
||||
As of 2026-04-13, latest `origin/dev`:
|
||||
Current status on this branch:
|
||||
|
||||
- `src/` still has 15 `makeRuntime(...)` call sites.
|
||||
- 13 of those are still in scope for facade removal.
|
||||
- 2 are excluded from this checklist: `bus/index.ts` and `effect/cross-spawn-spawner.ts`.
|
||||
- `src/` has 5 `makeRuntime(...)` call sites total.
|
||||
- 2 are intentionally excluded from this checklist: `src/bus/index.ts` and `src/effect/cross-spawn-spawner.ts`.
|
||||
- 1 is tracked primarily by the instance-context migration rather than facade removal: `src/project/instance.ts`.
|
||||
- That leaves 2 live runtime-backed service facades still worth tracking here: `src/npm/index.ts` and `src/cli/cmd/tui/config/tui.ts`.
|
||||
|
||||
Recent progress:
|
||||
|
||||
@@ -15,8 +16,9 @@ Recent progress:
|
||||
|
||||
## Priority hotspots
|
||||
|
||||
- `server/instance/session.ts` still depends on `Session`, `SessionPrompt`, `SessionRevert`, `SessionCompaction`, `SessionSummary`, `ShareSession`, `Agent`, and `Permission` facades.
|
||||
- `src/effect/app-runtime.ts` still references many facade namespaces directly, so it should stay in view during each deletion.
|
||||
- `src/cli/cmd/tui/config/tui.ts` still exports `makeRuntime(...)` plus async facade helpers for `get()` and `waitForDependencies()`.
|
||||
- `src/npm/index.ts` still exports `makeRuntime(...)` plus async facade helpers for `install()`, `add()`, `outdated()`, and `which()`.
|
||||
- `src/project/instance.ts` still uses a dedicated runtime for project boot, but that file is really part of the broader legacy instance-context transition tracked in `instance-context.md`.
|
||||
|
||||
## Completed Batches
|
||||
|
||||
@@ -184,53 +186,34 @@ These were the recurring mistakes and useful corrections from the first two batc
|
||||
5. For CLI readability, extract file-local preload helpers when the handler starts doing config load + service load + batched effect fanout inline.
|
||||
6. When rebasing a facade branch after nearby merges, prefer the already-cleaned service/test version over older inline facade-era code.
|
||||
|
||||
## Next batch
|
||||
## Remaining work
|
||||
|
||||
Recommended next five, in order:
|
||||
Most of the original facade-removal backlog is already done. The practical remaining work is narrower now:
|
||||
|
||||
1. `src/permission/index.ts`
|
||||
2. `src/agent/agent.ts`
|
||||
3. `src/session/summary.ts`
|
||||
4. `src/session/revert.ts`
|
||||
5. `src/mcp/auth.ts`
|
||||
|
||||
Why this batch:
|
||||
|
||||
- It keeps pushing the session-adjacent cleanup without jumping straight into `session/index.ts` or `session/prompt.ts`.
|
||||
- `Permission`, `Agent`, `SessionSummary`, and `SessionRevert` all reduce fanout in `server/instance/session.ts`.
|
||||
- `McpAuth` is small and closely related to the just-landed `MCP` cleanup.
|
||||
|
||||
After that batch, the expected follow-up is the main session cluster:
|
||||
|
||||
1. `src/session/index.ts`
|
||||
2. `src/session/prompt.ts`
|
||||
3. `src/session/compaction.ts`
|
||||
1. remove the `Npm` runtime-backed facade from `src/npm/index.ts`
|
||||
2. remove the `TuiConfig` runtime-backed facade from `src/cli/cmd/tui/config/tui.ts`
|
||||
3. keep `src/project/instance.ts` in the separate instance-context migration, not this checklist
|
||||
|
||||
## Checklist
|
||||
|
||||
- [ ] `src/session/index.ts` (`Session`) - facades: `create`, `fork`, `get`, `setTitle`, `setArchived`, `setPermission`, `setRevert`, `messages`, `children`, `remove`, `updateMessage`, `removeMessage`, `removePart`, `updatePart`; main callers: `server/instance/session.ts`, `cli/cmd/session.ts`, `cli/cmd/export.ts`, `cli/cmd/github.ts`; tests: `test/server/session-actions.test.ts`, `test/server/session-list.test.ts`, `test/server/global-session-list.test.ts`
|
||||
- [ ] `src/session/prompt.ts` (`SessionPrompt`) - facades: `prompt`, `resolvePromptParts`, `cancel`, `loop`, `shell`, `command`; main callers: `server/instance/session.ts`, `cli/cmd/github.ts`; tests: `test/session/prompt.test.ts`, `test/session/prompt-effect.test.ts`, `test/session/structured-output-integration.test.ts`
|
||||
- [ ] `src/session/revert.ts` (`SessionRevert`) - facades: `revert`, `unrevert`, `cleanup`; main callers: `server/instance/session.ts`; tests: `test/session/revert-compact.test.ts`
|
||||
- [ ] `src/session/compaction.ts` (`SessionCompaction`) - facades: `isOverflow`, `prune`, `create`; main callers: `server/instance/session.ts`; tests: `test/session/compaction.test.ts`
|
||||
- [ ] `src/session/summary.ts` (`SessionSummary`) - facades: `summarize`, `diff`; main callers: `session/prompt.ts`, `session/processor.ts`, `server/instance/session.ts`; tests: `test/session/snapshot-tool-race.test.ts`
|
||||
- [ ] `src/share/session.ts` (`ShareSession`) - facades: `create`, `share`, `unshare`; main callers: `server/instance/session.ts`, `cli/cmd/github.ts`
|
||||
- [ ] `src/agent/agent.ts` (`Agent`) - facades: `get`, `list`, `defaultAgent`, `generate`; main callers: `cli/cmd/agent.ts`, `server/instance/session.ts`, `server/instance/experimental.ts`; tests: `test/agent/agent.test.ts`
|
||||
- [ ] `src/permission/index.ts` (`Permission`) - facades: `ask`, `reply`, `list`; main callers: `server/instance/permission.ts`, `server/instance/session.ts`, `session/llm.ts`; tests: `test/permission/next.test.ts`
|
||||
- [x] `src/file/index.ts` (`File`) - facades removed and merged.
|
||||
- [x] `src/lsp/index.ts` (`LSP`) - facades removed and merged.
|
||||
- [x] `src/mcp/index.ts` (`MCP`) - facades removed and merged.
|
||||
- [x] `src/config/config.ts` (`Config`) - facades removed and merged.
|
||||
- [x] `src/provider/provider.ts` (`Provider`) - facades removed and merged.
|
||||
- [x] `src/pty/index.ts` (`Pty`) - facades removed and merged.
|
||||
- [x] `src/skill/index.ts` (`Skill`) - facades removed and merged.
|
||||
- [x] `src/project/vcs.ts` (`Vcs`) - facades removed and merged.
|
||||
- [x] `src/tool/registry.ts` (`ToolRegistry`) - facades removed and merged.
|
||||
- [ ] `src/worktree/index.ts` (`Worktree`) - facades: `makeWorktreeInfo`, `createFromInfo`, `create`, `remove`, `reset`; main callers: `control-plane/adaptors/worktree.ts`, `server/instance/experimental.ts`; tests: `test/project/worktree.test.ts`, `test/project/worktree-remove.test.ts`
|
||||
- [x] `src/auth/index.ts` (`Auth`) - facades removed and merged.
|
||||
- [ ] `src/mcp/auth.ts` (`McpAuth`) - facades: `get`, `getForUrl`, `all`, `set`, `remove`, `updateTokens`, `updateClientInfo`, `updateCodeVerifier`, `updateOAuthState`; main callers: `mcp/oauth-provider.ts`, `cli/cmd/mcp.ts`; tests: `test/mcp/oauth-auto-connect.test.ts`
|
||||
- [ ] `src/plugin/index.ts` (`Plugin`) - facades: `trigger`, `list`, `init`; main callers: `agent/agent.ts`, `session/llm.ts`, `project/bootstrap.ts`; tests: `test/plugin/trigger.test.ts`, `test/provider/provider.test.ts`
|
||||
- [ ] `src/project/project.ts` (`Project`) - facades: `fromDirectory`, `discover`, `initGit`, `update`, `sandboxes`, `addSandbox`, `removeSandbox`; main callers: `project/instance.ts`, `server/instance/project.ts`, `server/instance/experimental.ts`; tests: `test/project/project.test.ts`, `test/project/migrate-global.test.ts`
|
||||
- [ ] `src/snapshot/index.ts` (`Snapshot`) - facades: `init`, `track`, `patch`, `restore`, `revert`, `diff`, `diffFull`; main callers: `project/bootstrap.ts`, `cli/cmd/debug/snapshot.ts`; tests: `test/snapshot/snapshot.test.ts`, `test/session/revert-compact.test.ts`
|
||||
- [ ] `src/npm/index.ts` (`Npm`) - still exports runtime-backed async facade helpers on top of `Npm.Service`
|
||||
- [ ] `src/cli/cmd/tui/config/tui.ts` (`TuiConfig`) - still exports runtime-backed async facade helpers on top of `TuiConfig.Service`
|
||||
- [x] `src/session/session.ts` / `src/session/prompt.ts` / `src/session/revert.ts` / `src/session/summary.ts` - service-local facades removed
|
||||
- [x] `src/agent/agent.ts` (`Agent`) - service-local facades removed
|
||||
- [x] `src/permission/index.ts` (`Permission`) - service-local facades removed
|
||||
- [x] `src/worktree/index.ts` (`Worktree`) - service-local facades removed
|
||||
- [x] `src/plugin/index.ts` (`Plugin`) - service-local facades removed
|
||||
- [x] `src/snapshot/index.ts` (`Snapshot`) - service-local facades removed
|
||||
- [x] `src/file/index.ts` (`File`) - facades removed and merged
|
||||
- [x] `src/lsp/index.ts` (`LSP`) - facades removed and merged
|
||||
- [x] `src/mcp/index.ts` (`MCP`) - facades removed and merged
|
||||
- [x] `src/config/config.ts` (`Config`) - facades removed and merged
|
||||
- [x] `src/provider/provider.ts` (`Provider`) - facades removed and merged
|
||||
- [x] `src/pty/index.ts` (`Pty`) - facades removed and merged
|
||||
- [x] `src/skill/index.ts` (`Skill`) - facades removed and merged
|
||||
- [x] `src/project/vcs.ts` (`Vcs`) - facades removed and merged
|
||||
- [x] `src/tool/registry.ts` (`ToolRegistry`) - facades removed and merged
|
||||
- [x] `src/auth/index.ts` (`Auth`) - facades removed and merged
|
||||
|
||||
## Excluded `makeRuntime(...)` sites
|
||||
|
||||
|
||||
@@ -76,7 +76,7 @@ Many route boundaries still use Zod-first validators. That does not block all ex
|
||||
|
||||
### Mixed handler styles
|
||||
|
||||
Many current `server/instance/*.ts` handlers still call async facades directly. Migrating those to composed `Effect.gen(...)` handlers is the low-risk step to do first.
|
||||
Many current `server/routes/instance/*.ts` handlers still mix composed Effect code with smaller Promise- or ALS-backed seams. Migrating those to consistent `Effect.gen(...)` handlers is the low-risk step to do first.
|
||||
|
||||
### Non-JSON routes
|
||||
|
||||
@@ -90,7 +90,7 @@ The current server composition, middleware, and docs flow are Hono-centered toda
|
||||
|
||||
### 1. Finish the prerequisites first
|
||||
|
||||
- continue route-handler effectification in `server/instance/*.ts`
|
||||
- continue route-handler effectification in `server/routes/instance/*.ts`
|
||||
- continue schema migration toward Effect Schema-first DTOs and errors
|
||||
- keep removing service facades
|
||||
|
||||
@@ -98,9 +98,9 @@ The current server composition, middleware, and docs flow are Hono-centered toda
|
||||
|
||||
Introduce one small `HttpApi` group for plain JSON endpoints only. Good initial candidates are the least stateful endpoints in:
|
||||
|
||||
- `server/instance/question.ts`
|
||||
- `server/instance/provider.ts`
|
||||
- `server/instance/permission.ts`
|
||||
- `server/routes/instance/question.ts`
|
||||
- `server/routes/instance/provider.ts`
|
||||
- `server/routes/instance/permission.ts`
|
||||
|
||||
Avoid `session.ts`, SSE, websocket, and TUI-facing routes first.
|
||||
|
||||
@@ -155,9 +155,9 @@ This gives:
|
||||
|
||||
As each route group is ported to `HttpApi`:
|
||||
|
||||
1. change its `root` path from `/experimental/httpapi/<group>` to `/<group>`
|
||||
2. add `.all("/<group>", handler)` / `.all("/<group>/*", handler)` to the flag block in `instance/index.ts`
|
||||
3. for partial ports (e.g. only `GET /provider/auth`), bridge only the specific path
|
||||
1. add `.get(...)` / `.post(...)` bridge entries to the flag block in `server/routes/instance/index.ts`
|
||||
2. for partial ports (e.g. only `GET /provider/auth`), bridge only the specific path
|
||||
3. keep the legacy Hono route registered behind it for OpenAPI / SDK generation until the spec pipeline changes
|
||||
4. verify SDK output is unchanged
|
||||
|
||||
Leave streaming-style endpoints on Hono until there is a clear reason to move them.
|
||||
@@ -267,7 +267,7 @@ Use the same sequence for each route group.
|
||||
3. Apply the schema migration ordering above so those types are Effect Schema-first.
|
||||
4. Define the `HttpApi` contract separately from the handlers.
|
||||
5. Implement handlers by yielding the existing service from context.
|
||||
6. Mount the new surface in parallel under an experimental prefix.
|
||||
6. Mount the new surface in parallel behind the `OPENCODE_EXPERIMENTAL_HTTPAPI` bridge.
|
||||
7. Regenerate the SDK and verify zero diff against `dev` (see SDK shape rule above).
|
||||
8. Add one end-to-end test and one OpenAPI-focused test.
|
||||
9. Compare ergonomics before migrating the next endpoint.
|
||||
@@ -286,20 +286,20 @@ Placement rule:
|
||||
- keep `HttpApi` code under `src/server`, not `src/effect`
|
||||
- `src/effect` should stay focused on runtimes, layers, instance state, and shared Effect plumbing
|
||||
- place each `HttpApi` slice next to the HTTP boundary it serves
|
||||
- for instance-scoped routes, prefer `src/server/instance/httpapi/*`
|
||||
- if control-plane routes ever migrate, prefer `src/server/control/httpapi/*`
|
||||
- for instance-scoped routes, prefer `src/server/routes/instance/httpapi/*`
|
||||
- if control-plane routes ever migrate, prefer `src/server/routes/control/httpapi/*`
|
||||
|
||||
Suggested file layout for a repeatable spike:
|
||||
|
||||
- `src/server/instance/httpapi/question.ts` — contract and handler layer for one route group
|
||||
- `src/server/instance/httpapi/server.ts` — standalone Effect HTTP server that composes all groups
|
||||
- `test/server/question-httpapi.test.ts` — end-to-end test against the real service
|
||||
- `src/server/routes/instance/httpapi/question.ts` — contract and handler layer for one route group
|
||||
- `src/server/routes/instance/httpapi/server.ts` — bridged Effect HTTP layer that composes all groups
|
||||
- route or OpenAPI verification should live alongside the existing server tests; there is no dedicated `question-httpapi` test file on this branch
|
||||
|
||||
Suggested responsibilities:
|
||||
|
||||
- `question.ts` defines the `HttpApi` contract and `HttpApiBuilder.group(...)` handlers
|
||||
- `server.ts` composes all route groups into one `HttpRouter.serve` layer with shared middleware (auth, instance lookup)
|
||||
- tests use `ExperimentalHttpApiServer.layerTest` to run against a real in-process HTTP server
|
||||
- `server.ts` composes all route groups into one `HttpRouter.toWebHandler(...)` bridge with shared middleware (auth, instance lookup)
|
||||
- tests should verify the bridged routes through the normal server surface
|
||||
|
||||
## Example migration shape
|
||||
|
||||
@@ -319,33 +319,33 @@ Each route-group spike should follow the same shape.
|
||||
- keep handler bodies thin
|
||||
- keep transport mapping at the HTTP boundary only
|
||||
|
||||
### 3. Standalone server
|
||||
### 3. Bridged server
|
||||
|
||||
- the Effect HTTP server is self-contained in `httpapi/server.ts`
|
||||
- it is **not** mounted into the Hono app — no bridge, no `toWebHandler`
|
||||
- route paths use the `/experimental/httpapi` prefix so they match the eventual cutover
|
||||
- each route group exposes its own OpenAPI doc endpoint
|
||||
- the Effect HTTP layer is composed in `httpapi/server.ts`
|
||||
- it is mounted into the Hono app via `HttpRouter.toWebHandler(...)`
|
||||
- routes keep their normal instance paths and are gated by the `OPENCODE_EXPERIMENTAL_HTTPAPI` flag
|
||||
- the legacy Hono handlers stay registered after the bridge so current OpenAPI / SDK generation still works
|
||||
|
||||
### 4. Verification
|
||||
|
||||
- seed real state through the existing service
|
||||
- call the experimental endpoints
|
||||
- call the bridged endpoints with the flag enabled
|
||||
- assert that the service behavior is unchanged
|
||||
- assert that the generated OpenAPI contains the migrated paths and schemas
|
||||
|
||||
## Boundary composition
|
||||
|
||||
The standalone Effect server owns its own middleware stack. It does not share middleware with the Hono server.
|
||||
The Effect `HttpApi` layer owns its own auth and instance middleware, but it is currently mounted inside the existing Hono server.
|
||||
|
||||
### Auth
|
||||
|
||||
- the standalone server implements auth as an `HttpApiMiddleware.Service` using `HttpApiSecurity.basic`
|
||||
- the bridged `HttpApi` layer implements auth as an `HttpApiMiddleware.Service` using `HttpApiSecurity.basic`
|
||||
- each route group's `HttpApi` is wrapped with `.middleware(Authorization)` before being served
|
||||
- this is independent of the Hono `AuthMiddleware` — when the Effect server eventually replaces Hono, this becomes the only auth layer
|
||||
- this is independent of the Hono auth layer; the current bridge keeps the responsibility local to the `HttpApi` slice
|
||||
|
||||
### Instance and workspace lookup
|
||||
|
||||
- the standalone server resolves instance context via an `HttpRouter.middleware` that reads `x-opencode-directory` headers and `directory` query params
|
||||
- the bridged `HttpApi` layer resolves instance context via an `HttpRouter.middleware` that reads `x-opencode-directory` headers and `directory` query params
|
||||
- this is the Effect equivalent of the Hono `WorkspaceRouterMiddleware`
|
||||
- `HttpApi` handlers yield services from context and assume the correct instance has already been provided
|
||||
|
||||
@@ -360,7 +360,7 @@ The standalone Effect server owns its own middleware stack. It does not share mi
|
||||
|
||||
The first slice is successful if:
|
||||
|
||||
- the standalone Effect server starts and serves the endpoints independently of the Hono server
|
||||
- the bridged endpoints serve correctly through the existing Hono host when the flag is enabled
|
||||
- the handlers reuse the existing Effect service
|
||||
- request decoding and response shapes are schema-defined from canonical Effect schemas
|
||||
- any remaining Zod boundary usage is derived from `.zod` or clearly temporary
|
||||
|
||||
@@ -157,7 +157,7 @@ Direct legacy usage means any source file that still calls one of:
|
||||
- `Instance.reload(...)`
|
||||
- `Instance.dispose()` / `Instance.disposeAll()`
|
||||
|
||||
Current total: `54` files in `packages/opencode/src`.
|
||||
Current total: `56` files in `packages/opencode/src`.
|
||||
|
||||
### Core bridge and plumbing
|
||||
|
||||
@@ -177,13 +177,13 @@ Migration rule:
|
||||
|
||||
These are the current request-entry seams that still create or consume instance context through the legacy helper.
|
||||
|
||||
- `src/server/instance/middleware.ts`
|
||||
- `src/server/instance/index.ts`
|
||||
- `src/server/instance/project.ts`
|
||||
- `src/server/instance/workspace.ts`
|
||||
- `src/server/instance/file.ts`
|
||||
- `src/server/instance/experimental.ts`
|
||||
- `src/server/instance/global.ts`
|
||||
- `src/server/routes/instance/middleware.ts`
|
||||
- `src/server/routes/instance/index.ts`
|
||||
- `src/server/routes/instance/project.ts`
|
||||
- `src/server/routes/control/workspace.ts`
|
||||
- `src/server/routes/instance/file.ts`
|
||||
- `src/server/routes/instance/experimental.ts`
|
||||
- `src/server/routes/global.ts`
|
||||
|
||||
Migration rule:
|
||||
|
||||
@@ -239,7 +239,7 @@ Migration rule:
|
||||
These modules are already the best near-term migration targets because they are in Effect code but still read sync getters from the legacy helper.
|
||||
|
||||
- `src/agent/agent.ts`
|
||||
- `src/config/tui-migrate.ts`
|
||||
- `src/cli/cmd/tui/config/tui-migrate.ts`
|
||||
- `src/file/index.ts`
|
||||
- `src/file/watcher.ts`
|
||||
- `src/format/formatter.ts`
|
||||
@@ -250,7 +250,7 @@ These modules are already the best near-term migration targets because they are
|
||||
- `src/project/vcs.ts`
|
||||
- `src/provider/provider.ts`
|
||||
- `src/pty/index.ts`
|
||||
- `src/session/index.ts`
|
||||
- `src/session/session.ts`
|
||||
- `src/session/instruction.ts`
|
||||
- `src/session/llm.ts`
|
||||
- `src/session/system.ts`
|
||||
|
||||
@@ -4,11 +4,11 @@ Small follow-ups that do not fit neatly into the main facade, route, tool, or sc
|
||||
|
||||
## Config / TUI
|
||||
|
||||
- [ ] `config/tui.ts` - finish the internal Effect migration after the `Instance.state(...)` removal.
|
||||
- [ ] `cli/cmd/tui/config/tui.ts` - finish the internal Effect migration.
|
||||
Keep the current precedence and migration semantics intact while converting the remaining internal async helpers (`loadState`, `mergeFile`, `loadFile`, `load`) to `Effect.gen(...)` / `Effect.fn(...)`.
|
||||
- [ ] `config/tui.ts` callers - once the internal service is stable, migrate plain async callers to use `TuiConfig.Service` directly where that actually simplifies the code.
|
||||
- [ ] `cli/cmd/tui/config/tui.ts` callers - once the internal service is stable, migrate plain async callers to use `TuiConfig.Service` directly where that actually simplifies the code.
|
||||
Likely first callers: `cli/cmd/tui/attach.ts`, `cli/cmd/tui/thread.ts`, `cli/cmd/tui/plugin/runtime.ts`.
|
||||
- [ ] `env/index.ts` - move the last production `Instance.state(...)` usage onto `InstanceState` (or its replacement) so `Instance.state` can be deleted.
|
||||
- [x] `env/index.ts` - already uses `InstanceState.make(...)`.
|
||||
|
||||
## ConfigPaths
|
||||
|
||||
@@ -21,14 +21,12 @@ Small follow-ups that do not fit neatly into the main facade, route, tool, or sc
|
||||
- `readFile(...)`
|
||||
- `parseText(...)`
|
||||
- [ ] `config/config.ts` - switch internal config loading from `Effect.promise(() => ConfigPaths.*(...))` to `yield* paths.*(...)` once the service exists.
|
||||
- [ ] `config/tui.ts` - switch TUI config loading from async `ConfigPaths.*` wrappers to the `ConfigPaths.Service` once that service exists.
|
||||
- [ ] `config/tui-migrate.ts` - decide whether to leave this as a plain async module using wrapper functions or effectify it fully after `ConfigPaths.Service` lands.
|
||||
- [ ] `cli/cmd/tui/config/tui.ts` - switch TUI config loading from async `ConfigPaths.*` wrappers to the `ConfigPaths.Service` once that service exists.
|
||||
- [ ] `cli/cmd/tui/config/tui-migrate.ts` - decide whether to leave this as a plain async module using wrapper functions or effectify it fully after `ConfigPaths.Service` lands.
|
||||
|
||||
## Instance cleanup
|
||||
|
||||
- [ ] `project/instance.ts` - remove `Instance.state(...)` once `env/index.ts` is migrated.
|
||||
- [ ] `project/state.ts` - delete the bespoke per-instance state helper after the last production caller is gone.
|
||||
- [ ] `test/project/state.test.ts` - replace or delete the old `Instance.state(...)` tests after the removal.
|
||||
- [ ] `project/instance.ts` - keep shrinking the legacy ALS / Promise cache after the remaining `Instance.*` callers move over.
|
||||
|
||||
## Notes
|
||||
|
||||
|
||||
@@ -19,53 +19,43 @@ See `instance-context.md` for the phased plan to remove the legacy ALS / promise
|
||||
|
||||
## Service shape
|
||||
|
||||
Every service follows the same pattern — a single namespace with the service definition, layer, `runPromise`, and async facade functions:
|
||||
Every service follows the same pattern: one module, flat top-level exports, traced Effect methods, and a self-reexport at the bottom when the file is the public module.
|
||||
|
||||
```ts
|
||||
export namespace Foo {
|
||||
export interface Interface {
|
||||
readonly get: (id: FooID) => Effect.Effect<FooInfo, FooError>
|
||||
}
|
||||
|
||||
export class Service extends Context.Service<Service, Interface>()("@opencode/Foo") {}
|
||||
|
||||
export const layer = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
// For instance-scoped services:
|
||||
const state = yield* InstanceState.make<State>(
|
||||
Effect.fn("Foo.state")(() => Effect.succeed({ ... })),
|
||||
)
|
||||
|
||||
const get = Effect.fn("Foo.get")(function* (id: FooID) {
|
||||
const s = yield* InstanceState.get(state)
|
||||
// ...
|
||||
})
|
||||
|
||||
return Service.of({ get })
|
||||
}),
|
||||
)
|
||||
|
||||
// Optional: wire dependencies
|
||||
export const defaultLayer = layer.pipe(Layer.provide(FooDep.layer))
|
||||
|
||||
// Per-service runtime (inside the namespace)
|
||||
const { runPromise } = makeRuntime(Service, defaultLayer)
|
||||
|
||||
// Async facade functions
|
||||
export async function get(id: FooID) {
|
||||
return runPromise((svc) => svc.get(id))
|
||||
}
|
||||
export interface Interface {
|
||||
readonly get: (id: FooID) => Effect.Effect<FooInfo, FooError>
|
||||
}
|
||||
|
||||
export class Service extends Context.Service<Service, Interface>()("@opencode/Foo") {}
|
||||
|
||||
export const layer = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const state = yield* InstanceState.make<State>(
|
||||
Effect.fn("Foo.state")(() => Effect.succeed({ ... })),
|
||||
)
|
||||
|
||||
const get = Effect.fn("Foo.get")(function* (id: FooID) {
|
||||
const s = yield* InstanceState.get(state)
|
||||
// ...
|
||||
})
|
||||
|
||||
return Service.of({ get })
|
||||
}),
|
||||
)
|
||||
|
||||
export const defaultLayer = layer.pipe(Layer.provide(FooDep.layer))
|
||||
|
||||
export * as Foo from "."
|
||||
```
|
||||
|
||||
Rules:
|
||||
|
||||
- Keep everything in one namespace, one file — no separate `service.ts` / `index.ts` split
|
||||
- `runPromise` goes inside the namespace (not exported unless tests need it)
|
||||
- Facade functions are plain `async function` — no `fn()` wrappers
|
||||
- Use `Effect.fn("Namespace.method")` for all Effect functions (for tracing)
|
||||
- No `Layer.fresh` — InstanceState handles per-directory isolation
|
||||
- Keep the service surface in one module; prefer flat top-level exports over `export namespace Foo { ... }`
|
||||
- Use `Effect.fn("Foo.method")` for Effect methods
|
||||
- Use a self-reexport (`export * as Foo from "."` or `"./foo"`) for the public namespace projection
|
||||
- Avoid service-local `makeRuntime(...)` facades unless a file is still intentionally in the older migration phase
|
||||
- No `Layer.fresh` for normal per-directory isolation; use `InstanceState`
|
||||
|
||||
## Schema → Zod interop
|
||||
|
||||
@@ -266,7 +256,7 @@ Tool-specific filesystem cleanup notes live in `tools.md`.
|
||||
|
||||
## Destroying the facades
|
||||
|
||||
This phase is still broadly open. As of 2026-04-13 there are still 15 `makeRuntime(...)` call sites under `src/`, with 13 still in scope for facade removal. The live checklist now lives in `facades.md`.
|
||||
This phase is no longer broadly open. There are 5 `makeRuntime(...)` call sites under `src/`, and only a small subset are still ordinary facade-removal targets. The live checklist now lives in `facades.md`.
|
||||
|
||||
These facades exist because cyclic imports used to force each service to build its own independent runtime. Now that the layer DAG is acyclic and `AppRuntime` (`src/effect/app-runtime.ts`) composes everything into one `ManagedRuntime`, we're removing them.
|
||||
|
||||
@@ -297,11 +287,11 @@ For each service, the migration is roughly:
|
||||
- `ShareNext` — migrated 2026-04-11. Swapped remaining async callers to `AppRuntime.runPromise(ShareNext.Service.use(...))`, removed the `makeRuntime(...)` facade, and kept instance bootstrap on the shared app runtime.
|
||||
- `SessionTodo` — migrated 2026-04-10. Already matched the target service shape in `session/todo.ts`: single namespace, traced Effect methods, and no `makeRuntime(...)` facade remained; checklist updated to reflect the completed migration.
|
||||
- `Storage` — migrated 2026-04-10. One production caller (`Session.diff`) and all storage.test.ts tests converted to effectful style. Facades and `makeRuntime` removed.
|
||||
- `SessionRunState` — migrated 2026-04-11. Single caller in `server/instance/session.ts` converted; facade removed.
|
||||
- `Account` — migrated 2026-04-11. Callers in `server/instance/experimental.ts` and `cli/cmd/account.ts` converted; facade removed.
|
||||
- `SessionRunState` — migrated 2026-04-11. Single caller in `server/routes/instance/session.ts` converted; facade removed.
|
||||
- `Account` — migrated 2026-04-11. Callers in `server/routes/instance/experimental.ts` and `cli/cmd/account.ts` converted; facade removed.
|
||||
- `Instruction` — migrated 2026-04-11. Test-only callers converted; facade removed.
|
||||
- `FileWatcher` — migrated 2026-04-11. Callers in `project/bootstrap.ts` and test converted; facade removed.
|
||||
- `Question` — migrated 2026-04-11. Callers in `server/instance/question.ts` and test converted; facade removed.
|
||||
- `Question` — migrated 2026-04-11. Callers in `server/routes/instance/question.ts` and test converted; facade removed.
|
||||
- `Truncate` — migrated 2026-04-11. Caller in `tool/tool.ts` and test converted; facade removed.
|
||||
|
||||
## Route handler effectification
|
||||
|
||||
@@ -39,28 +39,26 @@ This eliminates multiple `runPromise` round-trips and lets handlers compose natu
|
||||
|
||||
## Current route files
|
||||
|
||||
Current instance route files live under `src/server/instance`, not `server/routes`.
|
||||
Current instance route files live under `src/server/routes/instance`.
|
||||
|
||||
The main migration targets are:
|
||||
Files that are already mostly on the intended service-yielding shape:
|
||||
|
||||
- [ ] `server/instance/session.ts` — heaviest; still has many direct facade calls for Session, SessionPrompt, SessionRevert, SessionCompaction, SessionShare, SessionSummary, Agent, Bus
|
||||
- [ ] `server/instance/global.ts` — still has direct facade calls for Config and instance lifecycle actions
|
||||
- [ ] `server/instance/provider.ts` — still has direct facade calls for Config and Provider
|
||||
- [ ] `server/instance/question.ts` — partially converted; still worth tracking here until it consistently uses the composed style
|
||||
- [ ] `server/instance/pty.ts` — still calls Pty facades directly
|
||||
- [ ] `server/instance/experimental.ts` — mixed state; some handlers are already composed, others still use facades
|
||||
- [x] `server/routes/instance/question.ts` — handlers yield `Question.Service`
|
||||
- [x] `server/routes/instance/provider.ts` — handlers yield `Provider.Service`, `ProviderAuth.Service`, and `Config.Service`
|
||||
- [x] `server/routes/instance/permission.ts` — handlers yield `Permission.Service`
|
||||
- [x] `server/routes/instance/mcp.ts` — handlers mostly yield `MCP.Service`
|
||||
- [x] `server/routes/instance/pty.ts` — handlers yield `Pty.Service`
|
||||
|
||||
Additional route files that still participate in the migration:
|
||||
Files still worth tracking here:
|
||||
|
||||
- [ ] `server/instance/index.ts` — Vcs, Agent, Skill, LSP, Format
|
||||
- [ ] `server/instance/file.ts` — Ripgrep, File, LSP
|
||||
- [ ] `server/instance/mcp.ts` — MCP facade-heavy
|
||||
- [ ] `server/instance/permission.ts` — Permission
|
||||
- [ ] `server/instance/workspace.ts` — Workspace
|
||||
- [ ] `server/instance/tui.ts` — Bus and Session
|
||||
- [ ] `server/instance/middleware.ts` — Session and Workspace lookups
|
||||
- [ ] `server/routes/instance/session.ts` — still the heaviest mixed file; many handlers are composed, but the file still mixes patterns and has direct `Bus.publish(...)` / `Session.list(...)` usage
|
||||
- [ ] `server/routes/instance/index.ts` — mostly converted, but still has direct `Instance.dispose()` / `Instance.*` reads for `/instance/dispose` and `/path`
|
||||
- [ ] `server/routes/instance/file.ts` — most handlers yield services, but `/find` still passes `Instance.directory` directly into ripgrep and `/find/symbol` is still stubbed
|
||||
- [ ] `server/routes/instance/experimental.ts` — mixed state; many handlers are composed, but some still rely on `runRequest(...)` or direct `Instance.project` reads
|
||||
- [ ] `server/routes/instance/middleware.ts` — still enters the instance via `Instance.provide(...)`
|
||||
- [ ] `server/routes/global.ts` — still uses `Instance.disposeAll()` and remains partly outside the fully-composed style
|
||||
|
||||
## Notes
|
||||
|
||||
- Some handlers already use `AppRuntime.runPromise(Effect.gen(...))` in isolated places. Keep pushing those files toward one consistent style.
|
||||
- Route conversion is closely tied to facade removal. As services lose `makeRuntime`-backed async exports, route handlers should switch to yielding the service directly.
|
||||
- Route conversion is now less about facade removal and more about removing the remaining direct `Instance.*` reads, `Instance.provide(...)` boundaries, and small Promise-style bridges inside route files.
|
||||
- `jsonRequest(...)` / `runRequest(...)` already provide a good intermediate shape for many handlers. The remaining cleanup is mostly consistency work in the heavier files.
|
||||
|
||||
@@ -40,13 +40,13 @@ Everything still lives in `packages/opencode`.
|
||||
Important current facts:
|
||||
|
||||
- there is no `packages/core` or `packages/cli` workspace yet
|
||||
- `packages/server` now exists as a minimal scaffold package, but it does not own any real route contracts, handlers, or runtime composition yet
|
||||
- there is no `packages/server` workspace yet on this branch
|
||||
- the main host server is still Hono-based in `src/server/server.ts`
|
||||
- current OpenAPI generation is Hono-based through `Server.openapi()` and `cli/cmd/generate.ts`
|
||||
- the Effect runtime and app layer are centralized in `src/effect/app-runtime.ts` and `src/effect/run-service.ts`
|
||||
- there is already one experimental Effect `HttpApi` slice at `src/server/instance/httpapi/question.ts`
|
||||
- that experimental slice is mounted under `/experimental/httpapi/question`
|
||||
- that experimental slice already has an end-to-end test at `test/server/question-httpapi.test.ts`
|
||||
- there are already bridged Effect `HttpApi` slices under `src/server/routes/instance/httpapi/*`
|
||||
- those slices are mounted into the Hono server behind `OPENCODE_EXPERIMENTAL_HTTPAPI`
|
||||
- the bridge currently covers `question`, `permission`, `provider`, partial `config`, and partial `project` routes
|
||||
|
||||
This means the package split should start from an extraction path, not from greenfield package ownership.
|
||||
|
||||
@@ -209,17 +209,19 @@ Current host and route composition:
|
||||
|
||||
- `src/server/server.ts`
|
||||
- `src/server/control/index.ts`
|
||||
- `src/server/instance/index.ts`
|
||||
- `src/server/routes/instance/index.ts`
|
||||
- `src/server/middleware.ts`
|
||||
- `src/server/adapter.bun.ts`
|
||||
- `src/server/adapter.node.ts`
|
||||
|
||||
Current experimental `HttpApi` slice:
|
||||
Current bridged `HttpApi` slices:
|
||||
|
||||
- `src/server/instance/httpapi/question.ts`
|
||||
- `src/server/instance/httpapi/index.ts`
|
||||
- `src/server/instance/experimental.ts`
|
||||
- `test/server/question-httpapi.test.ts`
|
||||
- `src/server/routes/instance/httpapi/question.ts`
|
||||
- `src/server/routes/instance/httpapi/permission.ts`
|
||||
- `src/server/routes/instance/httpapi/provider.ts`
|
||||
- `src/server/routes/instance/httpapi/config.ts`
|
||||
- `src/server/routes/instance/httpapi/project.ts`
|
||||
- `src/server/routes/instance/httpapi/server.ts`
|
||||
|
||||
Current OpenAPI flow:
|
||||
|
||||
@@ -245,7 +247,7 @@ Keep in `packages/opencode` for now:
|
||||
|
||||
- `src/server/server.ts`
|
||||
- `src/server/control/index.ts`
|
||||
- `src/server/instance/*.ts`
|
||||
- `src/server/routes/**/*.ts`
|
||||
- `src/server/middleware.ts`
|
||||
- `src/server/adapter.*.ts`
|
||||
- `src/effect/app-runtime.ts`
|
||||
@@ -305,14 +307,13 @@ Bad early migration targets:
|
||||
|
||||
## First vertical slice
|
||||
|
||||
The first slice for the package split is the existing experimental `question` group.
|
||||
The first slice for the package split is still the existing `question` `HttpApi` group.
|
||||
|
||||
Why `question` first:
|
||||
|
||||
- it already exists as an experimental `HttpApi` slice
|
||||
- it already follows the desired contract and implementation split in one file
|
||||
- it is already mounted through the current Hono host
|
||||
- it already has an end-to-end test
|
||||
- it is JSON-only
|
||||
- it has low blast radius
|
||||
|
||||
@@ -357,7 +358,7 @@ Done means:
|
||||
|
||||
Scope:
|
||||
|
||||
- extract the pure `HttpApi` contract from `src/server/instance/httpapi/question.ts`
|
||||
- extract the pure `HttpApi` contract from `src/server/routes/instance/httpapi/question.ts`
|
||||
- place it in `packages/server/src/definition/question.ts`
|
||||
- aggregate it in `packages/server/src/definition/api.ts`
|
||||
- generate OpenAPI in `packages/server/src/openapi.ts`
|
||||
@@ -399,8 +400,9 @@ Scope:
|
||||
|
||||
- replace local experimental question route wiring in `packages/opencode`
|
||||
- keep the same mount path:
|
||||
- `/experimental/httpapi/question`
|
||||
- `/experimental/httpapi/question/doc`
|
||||
- `/question`
|
||||
- `/question/:requestID/reply`
|
||||
- `/question/:requestID/reject`
|
||||
|
||||
Rules:
|
||||
|
||||
@@ -569,7 +571,7 @@ For package-split PRs, validate the smallest useful thing.
|
||||
Typical validation for the first waves:
|
||||
|
||||
- `bun typecheck` in the touched package directory or directories
|
||||
- the relevant route test, especially `test/server/question-httpapi.test.ts`
|
||||
- the relevant server / route coverage for the migrated slice
|
||||
- merged OpenAPI coverage if the PR touches spec generation
|
||||
|
||||
Do not run tests from repo root.
|
||||
|
||||
@@ -36,7 +36,7 @@ This keeps tool tests aligned with the production service graph and makes follow
|
||||
|
||||
## Exported tools
|
||||
|
||||
These exported tool definitions already exist in `src/tool` and are on the current Effect-native `Tool.define(...)` path:
|
||||
These exported tool definitions currently use `Tool.define(...)` in `src/tool`:
|
||||
|
||||
- [x] `apply_patch.ts`
|
||||
- [x] `bash.ts`
|
||||
@@ -45,7 +45,6 @@ These exported tool definitions already exist in `src/tool` and are on the curre
|
||||
- [x] `glob.ts`
|
||||
- [x] `grep.ts`
|
||||
- [x] `invalid.ts`
|
||||
- [x] `ls.ts`
|
||||
- [x] `lsp.ts`
|
||||
- [x] `multiedit.ts`
|
||||
- [x] `plan.ts`
|
||||
@@ -60,7 +59,7 @@ These exported tool definitions already exist in `src/tool` and are on the curre
|
||||
|
||||
Notes:
|
||||
|
||||
- `batch.ts` is no longer a current tool file and should not be tracked here.
|
||||
- There is no current `ls.ts` tool file on this branch.
|
||||
- `truncate.ts` is an Effect service used by tools, not a tool definition itself.
|
||||
- `mcp-exa.ts`, `external-directory.ts`, and `schema.ts` are support modules, not standalone tool definitions.
|
||||
|
||||
@@ -73,7 +72,7 @@ Current spot cleanups worth tracking:
|
||||
- [ ] `read.ts` — still bridges to Node stream / `readline` helpers and Promise-based binary detection
|
||||
- [ ] `bash.ts` — already uses Effect child-process primitives; only keep tracking shell-specific platform bridges and parser/loading details as they come up
|
||||
- [ ] `webfetch.ts` — already uses `HttpClient`; remaining work is limited to smaller boundary helpers like HTML text extraction
|
||||
- [ ] `file/ripgrep.ts` — adjacent to tool migration; still has raw fs/process usage that affects `grep.ts` and `ls.ts`
|
||||
- [ ] `file/ripgrep.ts` — adjacent to tool migration; still has raw fs/process usage that affects `grep.ts` and file-search routes
|
||||
- [ ] `patch/index.ts` — adjacent to tool migration; still has raw fs usage behind patch application
|
||||
|
||||
Notable items that are already effectively on the target path and do not need separate migration bullets right now:
|
||||
@@ -83,7 +82,6 @@ Notable items that are already effectively on the target path and do not need se
|
||||
- `write.ts`
|
||||
- `codesearch.ts`
|
||||
- `websearch.ts`
|
||||
- `ls.ts`
|
||||
- `multiedit.ts`
|
||||
- `edit.ts`
|
||||
|
||||
|
||||
Reference in New Issue
Block a user