fix(cli): keep location map scoped to listener

This commit is contained in:
marius-kilocode
2026-08-25 11:35:57 +02:00
parent efacf3f95d
commit c92cac8a87
3 changed files with 12 additions and 18 deletions
@@ -67,12 +67,10 @@ import { ProjectCopy } from "@opencode-ai/core/project/copy" // kilocode_change
import { MoveSession } from "@opencode-ai/core/control-plane/move-session" // kilocode_change
import { PtyTicket } from "@opencode-ai/core/pty/ticket" // kilocode_change
import { Pty } from "@opencode-ai/core/pty" // kilocode_change
import { buildLocationServiceMap, LocationServiceMap } from "@opencode-ai/core/location-services" // kilocode_change
// kilocode_change start - retain Kilo runtime services in the upstream node graph
const memory = LayerNode.make({ service: MemoryService.Service, layer: MemoryService.layer, deps: [] })
const kilo = LayerNode.group([Credential.node, ModelCache.node, AgentManager.node, Notebook.node, memory])
const locationServiceMap = buildLocationServiceMap() // kilocode_change - bind fallback consumers to one process-wide map
// kilocode_change end
export const AppLayer = AppNodeBuilderV1.build(
@@ -134,10 +132,8 @@ export const AppLayer = AppNodeBuilderV1.build(
MoveSession.node,
PtyTicket.node,
Pty.shutdownNode, // kilocode_change
LocationServiceMap.node, // kilocode_change - expose the process-wide location cache to listeners
// kilocode_change end
]),
[[LocationServiceMap.node, locationServiceMap]], // kilocode_change
).pipe(Layer.provideMerge(AppNodeBuilderV1.build(Ripgrep.node)), Layer.provideMerge(Observability.layer))
const rt = ManagedRuntime.make(AppLayer, { memoMap })
@@ -337,7 +337,7 @@ export function createRoutes(
),
Layer.provide(locationServiceMapV2),
Layer.provide(AppNodeBuilderV1.build(app, [[LocationServiceMap.node, locationServiceMapV2]])), // kilocode_change
Layer.provide(AppNodeBuilderV1.build(app)),
// Must stay last: layers provided later in this pipe build beneath earlier ones,
// so Observability must come after every service graph. Otherwise eagerly forked
// fibers (e.g. the ModelsDev background refresh) capture Effect's default stdout
@@ -348,9 +348,7 @@ export function createRoutes(
// kilocode_change start - keep listener routes local while application services come from AppRuntime
export function createListenerRoutes(corsOptions?: CorsOptions) {
// Re-export AppRuntime's process-wide map through listener-local graphs instead of
// materializing another full catalog and native file index for every worktree.
const locationServiceMapV2 = Layer.effect(LocationServiceMap.Service, LocationServiceMap.Service)
const locationServiceMapV2 = buildLocationServiceMap()
return Layer.mergeAll(
rootApiRoutes,
@@ -366,8 +364,11 @@ export function createListenerRoutes(corsOptions?: CorsOptions) {
// satisfied when the layer is built, not at request time, so the listener needs the same chain
// createRoutes uses.
//
// SessionV2 remains listener-local because it uses SessionExecutionLocal. Its location map is
// inherited from AppRuntime so every server graph shares one cache.
// These builds sit inside KiloListener's Layer.fresh boundary, so each one self-provides its own
// dependency subtree rather than resolving AppRuntime's. That is deliberate: SessionV2 is bound
// to this listener's LocationServiceMap and to SessionExecutionLocal, so it cannot be the
// process-wide instance. Everything the graph does not rebind (the nodes listed in AppLayer)
// still comes from AppRuntime, and the scope teardown releases the rest.
Layer.provide(sessionLocationLayer),
Layer.provide(locationLayer),
Layer.provide(PtyEnvironment.layer),
@@ -20,16 +20,13 @@ describe("shared location service map", () => {
}
})
test("server app graph receives the listener location map", () => {
test("listener owns its location map scope", () => {
expect(source("server/routes/instance/httpapi/server.ts")).toContain(
"const locationServiceMapV2 = buildLocationServiceMap()",
)
expect(source("server/routes/instance/httpapi/server.ts")).not.toContain(
"AppNodeBuilderV1.build(app, [[LocationServiceMap.node, locationServiceMapV2]])",
)
})
test("listener inherits the process-wide location map", () => {
expect(source("server/routes/instance/httpapi/server.ts")).toContain(
"Layer.effect(LocationServiceMap.Service, LocationServiceMap.Service)",
)
expect(source("effect/app-runtime.ts")).toContain("LocationServiceMap.node")
expect(source("effect/app-runtime.ts")).not.toContain("LocationServiceMap.node")
})
})