fix(core): normalize Location.Ref keys in LocationServiceMap (#35757)
This commit is contained in:
@@ -150,8 +150,14 @@ export type LocationError = LayerNode.Error<typeof locationServices>
|
|||||||
export function buildLocationServiceMap(
|
export function buildLocationServiceMap(
|
||||||
replacements: LayerNode.Replacements = [],
|
replacements: LayerNode.Replacements = [],
|
||||||
): Layer.Layer<LocationServiceMap.Service> {
|
): Layer.Layer<LocationServiceMap.Service> {
|
||||||
|
// Structural Equal is own-key-set sensitive, so `{ directory }` (schema-decoded
|
||||||
|
// payloads omit optional keys) and `{ directory, workspaceID: undefined }` are
|
||||||
|
// different RcMap keys. The RcMap caches by the raw key before the build
|
||||||
|
// callback runs, so canonicalize at the map boundary to the key-present shape.
|
||||||
|
const canonical = (ref: Location.Ref) => Location.Ref.make({ directory: ref.directory, workspaceID: ref.workspaceID })
|
||||||
return Layer.effect(
|
return Layer.effect(
|
||||||
LocationServiceMap.Service,
|
LocationServiceMap.Service,
|
||||||
|
Effect.map(
|
||||||
LayerMap.make(
|
LayerMap.make(
|
||||||
(ref: Location.Ref) => {
|
(ref: Location.Ref) => {
|
||||||
const startedAt = performance.now()
|
const startedAt = performance.now()
|
||||||
@@ -176,6 +182,13 @@ export function buildLocationServiceMap(
|
|||||||
},
|
},
|
||||||
{ idleTimeToLive: "60 minutes" },
|
{ idleTimeToLive: "60 minutes" },
|
||||||
),
|
),
|
||||||
|
(inner) => ({
|
||||||
|
...inner,
|
||||||
|
get: (ref: Location.Ref) => inner.get(canonical(ref)),
|
||||||
|
contextEffect: (ref: Location.Ref) => inner.contextEffect(canonical(ref)),
|
||||||
|
invalidate: (ref: Location.Ref) => inner.invalidate(canonical(ref)),
|
||||||
|
}),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import path from "path"
|
|||||||
import { describe, expect } from "bun:test"
|
import { describe, expect } from "bun:test"
|
||||||
import { Config } from "@opencode-ai/schema/config"
|
import { Config } from "@opencode-ai/schema/config"
|
||||||
import { Plugin } from "@opencode-ai/schema/plugin"
|
import { Plugin } from "@opencode-ai/schema/plugin"
|
||||||
import { Context, DateTime, Effect, Equal, Hash, Schema, Stream } from "effect"
|
import { Context, DateTime, Effect, Equal, Hash, RcMap, Schema, Stream } from "effect"
|
||||||
import { define } from "@opencode-ai/plugin/v2/effect"
|
import { define } from "@opencode-ai/plugin/v2/effect"
|
||||||
import { AgentV2 } from "@opencode-ai/core/agent"
|
import { AgentV2 } from "@opencode-ai/core/agent"
|
||||||
import { Catalog } from "@opencode-ai/core/catalog"
|
import { Catalog } from "@opencode-ai/core/catalog"
|
||||||
@@ -208,6 +208,36 @@ describe("LocationServiceMap", () => {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
it.live("normalizes ref key shapes to one cached location graph", () =>
|
||||||
|
Effect.acquireRelease(
|
||||||
|
Effect.promise(() => tmpdir()),
|
||||||
|
(dir) => Effect.promise(() => dir[Symbol.asyncDispose]()),
|
||||||
|
).pipe(
|
||||||
|
Effect.flatMap((dir) =>
|
||||||
|
Effect.scoped(
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const locations = yield* LocationServiceMap.Service
|
||||||
|
const directory = AbsolutePath.make(dir.path)
|
||||||
|
const absent = Location.Ref.make({ directory })
|
||||||
|
const present = Location.Ref.make({ directory, workspaceID: undefined })
|
||||||
|
// The two shapes are not structurally Equal: own-key sets differ.
|
||||||
|
expect(Object.keys(absent)).toEqual(["directory"])
|
||||||
|
expect(Object.keys(present)).toEqual(["directory", "workspaceID"])
|
||||||
|
expect(Equal.equals(absent, present)).toBe(false)
|
||||||
|
|
||||||
|
const first = yield* locations.contextEffect(absent)
|
||||||
|
expect(yield* locations.contextEffect(present)).toBe(first)
|
||||||
|
expect(Array.from(yield* RcMap.keys(locations.rcMap))).toHaveLength(1)
|
||||||
|
|
||||||
|
// Invalidating with the shape opposite to the one that booted must evict.
|
||||||
|
yield* locations.invalidate(present)
|
||||||
|
expect(Array.from(yield* RcMap.keys(locations.rcMap))).toHaveLength(0)
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
it.live("isolates catalog state by location", () =>
|
it.live("isolates catalog state by location", () =>
|
||||||
Effect.acquireRelease(
|
Effect.acquireRelease(
|
||||||
Effect.promise(() => Promise.all([tmpdir(), tmpdir()])),
|
Effect.promise(() => Promise.all([tmpdir(), tmpdir()])),
|
||||||
|
|||||||
Reference in New Issue
Block a user