mirror of
https://github.com/anomalyco/opencode.git
synced 2026-09-03 17:04:32 +00:00
refactor(core): rename plugin flush to awaitActivation
This commit is contained in:
parent
f330f3e02b
commit
d4b4dd17cc
32 changed files with 104 additions and 87 deletions
|
|
@ -93,7 +93,7 @@ const layer = Layer.effect(
|
|||
)
|
||||
yield* policy.observe(reconcile)
|
||||
yield* Effect.gen(function* () {
|
||||
yield* plugins.flush
|
||||
yield* plugins.awaitActivation
|
||||
yield* reconcile(policy.current())
|
||||
}).pipe(
|
||||
Effect.catchCauseIf(
|
||||
|
|
|
|||
|
|
@ -8,10 +8,12 @@ import { Context, Effect } from "effect"
|
|||
*/
|
||||
export interface Interface {
|
||||
/**
|
||||
* Wait for the plugin generation to settle. Use this rarely: blocking reads,
|
||||
* UI startup, or other unrelated work on plugin boot should be avoided.
|
||||
* Wait for configured plugin activation to settle, including missing-package installs.
|
||||
* Completion does not imply every plugin succeeded.
|
||||
* Interrupting this wait does not cancel activation. Use rarely: avoid blocking reads,
|
||||
* UI startup, or unrelated work on plugin boot.
|
||||
*/
|
||||
readonly flush: Effect.Effect<void>
|
||||
readonly awaitActivation: Effect.Effect<void>
|
||||
}
|
||||
|
||||
export class Service extends Context.Service<Service, Interface>()("@opencode/PluginSupervisor") {}
|
||||
|
|
|
|||
|
|
@ -169,7 +169,7 @@ export const layer = Layer.effect(
|
|||
Stream.map(() => undefined),
|
||||
),
|
||||
).pipe(
|
||||
// Make accepted work visible to flush before coalescing the burst.
|
||||
// Make accepted work visible to awaitActivation before coalescing the burst.
|
||||
Stream.mapEffect(() =>
|
||||
Effect.gen(function* () {
|
||||
observed++
|
||||
|
|
@ -191,7 +191,7 @@ export const layer = Layer.effect(
|
|||
Effect.forkScoped({ startImmediately: true }),
|
||||
)
|
||||
yield* Effect.sleep("24 hours").pipe(Effect.andThen(activate()), Effect.forever, Effect.forkScoped)
|
||||
return Service.of({ flush: ready.await })
|
||||
return Service.of({ awaitActivation: ready.await })
|
||||
}),
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -407,7 +407,7 @@ const layer = Layer.effect(
|
|||
const session = yield* result.get(input.sessionID)
|
||||
const commands = yield* Effect.gen(function* () {
|
||||
const plugins = yield* PluginSupervisor.Service
|
||||
yield* plugins.flush
|
||||
yield* plugins.awaitActivation
|
||||
return yield* Command.Service
|
||||
}).pipe(instances.provide(session))
|
||||
const delivery = input.delivery ?? "steer"
|
||||
|
|
|
|||
|
|
@ -125,7 +125,7 @@ const layer = Layer.effect(
|
|||
if (session.location.directory !== location.directory || session.location.workspaceID !== location.workspaceID)
|
||||
return yield* Effect.interrupt
|
||||
|
||||
yield* plugins.flush
|
||||
yield* plugins.awaitActivation
|
||||
yield* mcpTools.flush
|
||||
const agent = yield* agents.select(session.agent)
|
||||
if (!agent.info) return yield* new AgentNotFoundError({ sessionID: session.id, agent: session.agent ?? agent.id })
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ export const make = Effect.fn("SessionPrompt.make")(function* () {
|
|||
messageID: SessionMessage.ID
|
||||
input: Input
|
||||
}) {
|
||||
yield* plugins.flush
|
||||
yield* plugins.awaitActivation
|
||||
const event = yield* hooks.trigger("session", "prompt", {
|
||||
sessionID: request.sessionID,
|
||||
messageID: request.messageID,
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ export const make = Effect.fn("SessionRevert.make")(function* () {
|
|||
const snapshot = yield* Snapshot.Service
|
||||
|
||||
const stage: Interface["stage"] = Effect.fn("SessionRevert.stage")(function* (input) {
|
||||
yield* plugins.flush
|
||||
yield* plugins.awaitActivation
|
||||
const original = input.session.revert?.snapshot
|
||||
? Snapshot.ID.make(input.session.revert.snapshot)
|
||||
: yield* snapshot.capture()
|
||||
|
|
@ -66,7 +66,7 @@ export const make = Effect.fn("SessionRevert.make")(function* () {
|
|||
})
|
||||
|
||||
const clear: Interface["clear"] = Effect.fn("SessionRevert.clear")(function* (session) {
|
||||
yield* plugins.flush
|
||||
yield* plugins.awaitActivation
|
||||
if (!session.revert) return
|
||||
const original = session.revert.snapshot ? Snapshot.ID.make(session.revert.snapshot) : undefined
|
||||
if (original)
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ const layer = Layer.effect(
|
|||
const control = pending.type === "compaction" || pending.type === "move"
|
||||
if (promotable === "steer" && pending.delivery === "queue" && !control) return DrainResult.Complete()
|
||||
}
|
||||
yield* plugins.flush
|
||||
yield* plugins.awaitActivation
|
||||
yield* settleStaleToolCalls(sessionID)
|
||||
|
||||
const advanceToStep = Effect.fn("SessionRunner.advanceToStep")(() =>
|
||||
|
|
|
|||
|
|
@ -196,7 +196,7 @@ export const make = Effect.fn("Session.make")(function* () {
|
|||
// Resolve shell services here without pinning Session events to this Location after a move.
|
||||
const shell = yield* Effect.gen(function* () {
|
||||
const plugins = yield* PluginSupervisor.Service
|
||||
yield* plugins.flush
|
||||
yield* plugins.awaitActivation
|
||||
return yield* Shell.Service
|
||||
}).pipe(instances.provide(session))
|
||||
const started = yield* shell
|
||||
|
|
|
|||
|
|
@ -490,7 +490,7 @@ describe("PluginSupervisor config", () => {
|
|||
),
|
||||
)
|
||||
|
||||
it.live("unblocks flush when plugin activation fails", () =>
|
||||
it.live("unblocks awaitActivation when plugin activation fails", () =>
|
||||
Effect.gen(function* () {
|
||||
const sdk = yield* SdkPlugins.Service
|
||||
yield* sdk.register(define({ id: "duplicate-id", effect: () => Effect.void }))
|
||||
|
|
@ -530,9 +530,9 @@ describe("PluginSupervisor config", () => {
|
|||
const plugins = yield* Plugin.Service
|
||||
expect((yield* plugins.list()).map((plugin) => String(plugin.id))).toContain("opencode.provider.openai")
|
||||
const supervisor = yield* PluginSupervisor.Service
|
||||
expect(Option.isNone(yield* supervisor.flush.pipe(Effect.timeoutOption("20 millis")))).toBeTrue()
|
||||
expect(Option.isNone(yield* supervisor.awaitActivation.pipe(Effect.timeoutOption("20 millis")))).toBeTrue()
|
||||
yield* Effect.promise(() => Bun.write(path.join(global.tmp, "cold-plugin", "release"), ""))
|
||||
yield* supervisor.flush.pipe(Effect.timeout("2 seconds"))
|
||||
yield* supervisor.awaitActivation.pipe(Effect.timeout("2 seconds"))
|
||||
expect((yield* plugins.list()).map((plugin) => String(plugin.id))).toContain("cold-plugin")
|
||||
}),
|
||||
),
|
||||
|
|
@ -542,7 +542,7 @@ describe("PluginSupervisor config", () => {
|
|||
|
||||
const ready = Effect.fnUntraced(function* () {
|
||||
const supervisor = yield* PluginSupervisor.Service
|
||||
yield* supervisor.flush
|
||||
yield* supervisor.awaitActivation
|
||||
})
|
||||
|
||||
const waitForFile = (file: string) =>
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ const it = testEffect(AppNodeBuilder.build(LayerNode.group([FSUtil.node, Bus.nod
|
|||
const configLayer = Config.testLayer()
|
||||
const pluginNode = makeLocationNode({
|
||||
service: PluginSupervisor.Service,
|
||||
layer: Layer.succeed(PluginSupervisor.Service, PluginSupervisor.Service.of({ flush: Effect.void })),
|
||||
layer: Layer.succeed(PluginSupervisor.Service, PluginSupervisor.Service.of({ awaitActivation: Effect.void })),
|
||||
deps: [],
|
||||
})
|
||||
|
||||
|
|
@ -313,7 +313,7 @@ describe("LocationWatcher subscriptions", () => {
|
|||
Effect.gen(function* () {
|
||||
const policy = yield* LocationWatcherPolicy.Service
|
||||
yield* policy.transform((draft) => draft.add([".git"]))
|
||||
return PluginSupervisor.Service.of({ flush: Effect.void })
|
||||
return PluginSupervisor.Service.of({ awaitActivation: Effect.void })
|
||||
}),
|
||||
),
|
||||
deps: [LocationWatcherPolicy.node],
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ export const promptLocationNode = makeGlobalNode({
|
|||
}),
|
||||
Layer.succeed(FSUtil.Service, fs),
|
||||
Layer.succeed(PluginSupervisor.Service, {
|
||||
flush: Effect.void,
|
||||
awaitActivation: Effect.void,
|
||||
}),
|
||||
Layer.mock(Reference.Service, { refresh: () => Effect.void }),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ function withFormatter<A, E, R>(
|
|||
Effect.andThen(
|
||||
Effect.gen(function* () {
|
||||
const plugins = yield* PluginSupervisor.Service
|
||||
yield* plugins.flush
|
||||
yield* plugins.awaitActivation
|
||||
return yield* body(yield* Formatter.Service, directory)
|
||||
}).pipe(
|
||||
Effect.scoped,
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ describe("InstancePlugins", () => {
|
|||
const agents = (ref: Location.Ref) =>
|
||||
Effect.gen(function* () {
|
||||
const supervisor = yield* PluginSupervisor.Service
|
||||
yield* supervisor.flush
|
||||
yield* supervisor.awaitActivation
|
||||
const service = yield* Agent.Service
|
||||
return {
|
||||
bound: yield* service.get(Agent.ID.make("thread-a-agent")),
|
||||
|
|
|
|||
|
|
@ -77,7 +77,7 @@ describe("Instance vanilla", () => {
|
|||
const read = (ref: Location.Ref) =>
|
||||
Effect.gen(function* () {
|
||||
const supervisor = yield* PluginSupervisor.Service
|
||||
yield* supervisor.flush
|
||||
yield* supervisor.awaitActivation
|
||||
const config = yield* Config.Service
|
||||
const discovery = yield* InstructionDiscovery.Service
|
||||
const tools = yield* Tool.Service
|
||||
|
|
@ -145,7 +145,7 @@ describe("Instance vanilla", () => {
|
|||
const ref = Location.Ref.make({ directory: AbsolutePath.make(directory) })
|
||||
yield* Effect.gen(function* () {
|
||||
const supervisor = yield* PluginSupervisor.Service
|
||||
yield* supervisor.flush
|
||||
yield* supervisor.awaitActivation
|
||||
const config = yield* Config.Service
|
||||
// Only the pathless host-injected document; nothing file-backed.
|
||||
const entries = yield* config.entries()
|
||||
|
|
|
|||
|
|
@ -199,7 +199,7 @@ describe("LocationServiceMap", () => {
|
|||
const ref = Location.Ref.make({ directory: AbsolutePath.make(dir.path) })
|
||||
const read = Effect.gen(function* () {
|
||||
const supervisor = yield* PluginSupervisor.Service
|
||||
yield* supervisor.flush
|
||||
yield* supervisor.awaitActivation
|
||||
const agents = yield* Agent.Service
|
||||
return yield* agents.get(id)
|
||||
})
|
||||
|
|
@ -233,14 +233,14 @@ describe("LocationServiceMap", () => {
|
|||
const context = yield* locations.contextEffect(Location.Ref.make({ directory: AbsolutePath.make(dir.path) }))
|
||||
yield* Deferred.await(started)
|
||||
|
||||
const flushFiber = yield* PluginSupervisor.Service.use((supervisor) => supervisor.flush).pipe(
|
||||
const activationFiber = yield* PluginSupervisor.Service.use((supervisor) => supervisor.awaitActivation).pipe(
|
||||
Effect.provide(context),
|
||||
Effect.forkChild,
|
||||
)
|
||||
expect(flushFiber.pollUnsafe()).toBeUndefined()
|
||||
expect(activationFiber.pollUnsafe()).toBeUndefined()
|
||||
yield* Deferred.succeed(release, undefined)
|
||||
yield* Fiber.join(flushFiber)
|
||||
yield* PluginSupervisor.Service.use((supervisor) => supervisor.flush).pipe(
|
||||
yield* Fiber.join(activationFiber)
|
||||
yield* PluginSupervisor.Service.use((supervisor) => supervisor.awaitActivation).pipe(
|
||||
Effect.provide(context),
|
||||
Effect.timeout("1 second"),
|
||||
)
|
||||
|
|
@ -281,7 +281,7 @@ describe("LocationServiceMap", () => {
|
|||
const context = yield* locations.contextEffect(Location.Ref.make({ directory: AbsolutePath.make(dir.path) }))
|
||||
yield* Deferred.await(firstStarted)
|
||||
|
||||
const flushFiber = yield* PluginSupervisor.Service.use((supervisor) => supervisor.flush).pipe(
|
||||
const activationFiber = yield* PluginSupervisor.Service.use((supervisor) => supervisor.awaitActivation).pipe(
|
||||
Effect.provide(context),
|
||||
Effect.forkChild({ startImmediately: true }),
|
||||
)
|
||||
|
|
@ -296,10 +296,10 @@ describe("LocationServiceMap", () => {
|
|||
|
||||
yield* Deferred.succeed(releaseFirst, undefined)
|
||||
yield* Deferred.await(secondStarted)
|
||||
expect(flushFiber.pollUnsafe()).toBeUndefined()
|
||||
expect(activationFiber.pollUnsafe()).toBeUndefined()
|
||||
|
||||
yield* Deferred.succeed(releaseSecond, undefined)
|
||||
yield* Fiber.join(flushFiber)
|
||||
yield* Fiber.join(activationFiber)
|
||||
}),
|
||||
),
|
||||
),
|
||||
|
|
@ -352,22 +352,22 @@ describe("LocationServiceMap", () => {
|
|||
)
|
||||
yield* Fiber.join(updated)
|
||||
|
||||
const flushFiber = yield* PluginSupervisor.Service.use((supervisor) => supervisor.flush).pipe(
|
||||
const activationFiber = yield* PluginSupervisor.Service.use((supervisor) => supervisor.awaitActivation).pipe(
|
||||
Effect.provide(context),
|
||||
Effect.forkChild,
|
||||
)
|
||||
yield* Deferred.succeed(releaseFirst, undefined)
|
||||
yield* Deferred.await(secondStarted)
|
||||
expect(flushFiber.pollUnsafe()).toBeUndefined()
|
||||
expect(activationFiber.pollUnsafe()).toBeUndefined()
|
||||
yield* Deferred.succeed(releaseSecond, undefined)
|
||||
yield* Fiber.join(flushFiber)
|
||||
yield* Fiber.join(activationFiber)
|
||||
expect(activations.count).toBe(2)
|
||||
}),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
itWithSdk.live("keeps flush pending while startup updates continue", () =>
|
||||
itWithSdk.live("keeps awaitActivation pending while startup updates continue", () =>
|
||||
Effect.acquireRelease(
|
||||
Effect.promise(() => tmpdir()),
|
||||
(dir) => Effect.promise(() => dir[Symbol.asyncDispose]()),
|
||||
|
|
@ -376,7 +376,7 @@ describe("LocationServiceMap", () => {
|
|||
Effect.gen(function* () {
|
||||
const locations = yield* LocationServiceMap.Service
|
||||
const context = yield* locations.contextEffect(Location.Ref.make({ directory: AbsolutePath.make(dir.path) }))
|
||||
const flushFiber = yield* PluginSupervisor.Service.use((supervisor) => supervisor.flush).pipe(
|
||||
const activationFiber = yield* PluginSupervisor.Service.use((supervisor) => supervisor.awaitActivation).pipe(
|
||||
Effect.provide(context),
|
||||
Effect.forkChild({ startImmediately: true }),
|
||||
)
|
||||
|
|
@ -387,8 +387,8 @@ describe("LocationServiceMap", () => {
|
|||
() => bus.publish(SdkPlugins.Updated, {}).pipe(Effect.andThen(Effect.sleep("50 millis"))),
|
||||
{ discard: true },
|
||||
)
|
||||
expect(flushFiber.pollUnsafe()).toBeUndefined()
|
||||
yield* Fiber.join(flushFiber)
|
||||
expect(activationFiber.pollUnsafe()).toBeUndefined()
|
||||
yield* Fiber.join(activationFiber)
|
||||
}),
|
||||
),
|
||||
),
|
||||
|
|
@ -412,7 +412,7 @@ describe("LocationServiceMap", () => {
|
|||
|
||||
const locations = yield* LocationServiceMap.Service
|
||||
const context = yield* locations.contextEffect(Location.Ref.make({ directory: AbsolutePath.make(dir.path) }))
|
||||
yield* PluginSupervisor.Service.use((supervisor) => supervisor.flush).pipe(Effect.provide(context))
|
||||
yield* PluginSupervisor.Service.use((supervisor) => supervisor.awaitActivation).pipe(Effect.provide(context))
|
||||
expect(activations.count).toBe(1)
|
||||
|
||||
yield* Bus.Service.use((bus) => bus.publish(Config.Event.Updated, {})).pipe(Effect.provide(context))
|
||||
|
|
@ -424,7 +424,7 @@ describe("LocationServiceMap", () => {
|
|||
),
|
||||
)
|
||||
|
||||
itWithSdk.live("keeps flush open while later hot reload runs", () =>
|
||||
itWithSdk.live("keeps awaitActivation pending while later hot reload runs", () =>
|
||||
Effect.acquireRelease(
|
||||
Effect.promise(() => tmpdir()),
|
||||
(dir) => Effect.promise(() => dir[Symbol.asyncDispose]()),
|
||||
|
|
@ -433,7 +433,7 @@ describe("LocationServiceMap", () => {
|
|||
Effect.gen(function* () {
|
||||
const locations = yield* LocationServiceMap.Service
|
||||
const context = yield* locations.contextEffect(Location.Ref.make({ directory: AbsolutePath.make(dir.path) }))
|
||||
yield* PluginSupervisor.Service.use((supervisor) => supervisor.flush).pipe(Effect.provide(context))
|
||||
yield* PluginSupervisor.Service.use((supervisor) => supervisor.awaitActivation).pipe(Effect.provide(context))
|
||||
|
||||
const started = yield* Deferred.make<void>()
|
||||
const release = yield* Deferred.make<void>()
|
||||
|
|
@ -451,20 +451,20 @@ describe("LocationServiceMap", () => {
|
|||
)
|
||||
yield* Deferred.await(started)
|
||||
|
||||
const flushFiber = yield* PluginSupervisor.Service.use((supervisor) => supervisor.flush).pipe(
|
||||
const activationFiber = yield* PluginSupervisor.Service.use((supervisor) => supervisor.awaitActivation).pipe(
|
||||
Effect.provide(context),
|
||||
Effect.forkChild({ startImmediately: true }),
|
||||
)
|
||||
expect(flushFiber.pollUnsafe()).toBeUndefined()
|
||||
expect(activationFiber.pollUnsafe()).toBeUndefined()
|
||||
yield* Deferred.succeed(release, undefined)
|
||||
yield* Fiber.join(flushFiber)
|
||||
yield* Fiber.join(activationFiber)
|
||||
yield* Deferred.await(completed)
|
||||
}),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
itWithSdk.live("does not cancel activation when a flush waiter is interrupted", () =>
|
||||
itWithSdk.live("does not cancel activation when an awaitActivation waiter is interrupted", () =>
|
||||
Effect.acquireRelease(
|
||||
Effect.promise(() => tmpdir()),
|
||||
(dir) => Effect.promise(() => dir[Symbol.asyncDispose]()),
|
||||
|
|
@ -489,15 +489,15 @@ describe("LocationServiceMap", () => {
|
|||
const locations = yield* LocationServiceMap.Service
|
||||
const context = yield* locations.contextEffect(Location.Ref.make({ directory: AbsolutePath.make(dir.path) }))
|
||||
yield* Deferred.await(started)
|
||||
const flushFiber = yield* PluginSupervisor.Service.use((supervisor) => supervisor.flush).pipe(
|
||||
const activationFiber = yield* PluginSupervisor.Service.use((supervisor) => supervisor.awaitActivation).pipe(
|
||||
Effect.provide(context),
|
||||
Effect.forkChild({ startImmediately: true }),
|
||||
)
|
||||
yield* Fiber.interrupt(flushFiber)
|
||||
yield* Fiber.interrupt(activationFiber)
|
||||
|
||||
yield* Deferred.succeed(release, undefined)
|
||||
yield* Deferred.await(completed)
|
||||
yield* PluginSupervisor.Service.use((supervisor) => supervisor.flush).pipe(
|
||||
yield* PluginSupervisor.Service.use((supervisor) => supervisor.awaitActivation).pipe(
|
||||
Effect.provide(context),
|
||||
Effect.timeout("500 millis"),
|
||||
)
|
||||
|
|
@ -519,7 +519,7 @@ describe("LocationServiceMap", () => {
|
|||
const plugins = yield* Effect.gen(function* () {
|
||||
const plugins = yield* Plugin.Service
|
||||
const supervisor = yield* PluginSupervisor.Service
|
||||
yield* supervisor.flush
|
||||
yield* supervisor.awaitActivation
|
||||
return yield* plugins.list()
|
||||
}).pipe(
|
||||
Effect.scoped,
|
||||
|
|
@ -546,7 +546,7 @@ describe("LocationServiceMap", () => {
|
|||
yield* Effect.gen(function* () {
|
||||
const registry = yield* Plugin.Service
|
||||
const supervisor = yield* PluginSupervisor.Service
|
||||
yield* supervisor.flush
|
||||
yield* supervisor.awaitActivation
|
||||
expect((yield* registry.list()).map((plugin) => String(plugin.id))).toEqual(["opencode.agent"])
|
||||
|
||||
yield* Effect.promise(() => fs.writeFile(file, JSON.stringify({ plugins: ["-*", "opencode.command"] })))
|
||||
|
|
@ -710,7 +710,7 @@ describe("LocationServiceMap", () => {
|
|||
const catalog = yield* Catalog.Service
|
||||
yield* catalog.transform((editor) => editor.provider.update(providerID, () => {}))
|
||||
const supervisor = yield* PluginSupervisor.Service
|
||||
yield* supervisor.flush
|
||||
yield* supervisor.awaitActivation
|
||||
const registry = yield* Tool.Service
|
||||
return {
|
||||
providers: yield* catalog.provider.all(),
|
||||
|
|
@ -992,7 +992,7 @@ describe("LocationServiceMap", () => {
|
|||
yield* Effect.gen(function* () {
|
||||
const supervisor = yield* PluginSupervisor.Service
|
||||
const mcp = yield* Mcp.Service
|
||||
yield* supervisor.flush
|
||||
yield* supervisor.awaitActivation
|
||||
expect(observed.example).toBe(false)
|
||||
yield* mcp.add("dynamic", {
|
||||
type: "remote",
|
||||
|
|
|
|||
|
|
@ -111,7 +111,7 @@ const discovery = Layer.mock(InstructionDiscovery.Service, {
|
|||
const skills = Layer.mock(SkillInstructions.Service, { load: () => Effect.succeed(Instructions.empty) })
|
||||
const references = Layer.mock(ReferenceInstructions.Service, { load: () => Effect.succeed(Instructions.empty) })
|
||||
const mcp = Layer.mock(McpInstructions.Service, { load: () => Effect.succeed(Instructions.empty) })
|
||||
const plugins = Layer.mock(PluginSupervisor.Service, { flush: Effect.void })
|
||||
const plugins = Layer.mock(PluginSupervisor.Service, { awaitActivation: Effect.void })
|
||||
const tools = Layer.mock(Tool.Service, {
|
||||
snapshot: () =>
|
||||
Effect.succeed({
|
||||
|
|
|
|||
|
|
@ -95,7 +95,7 @@ const setup = Effect.fnUntraced(function* (options?: {
|
|||
)
|
||||
const hooks = yield* PluginHooks.Service.pipe(Effect.provide(LayerNode.compile(PluginHooks.node)))
|
||||
const locations: Location.Ref[] = []
|
||||
const flushes: Location.Ref[] = []
|
||||
const activationWaits: Location.Ref[] = []
|
||||
const resumes: SessionSchema.ID[] = []
|
||||
const wakes: Array<{ sessionID: SessionSchema.ID; pending: SessionMessage.ID[]; enqueued: number }> = []
|
||||
const execution = SessionExecution.Service.of({
|
||||
|
|
@ -143,8 +143,8 @@ const setup = Effect.fnUntraced(function* (options?: {
|
|||
}),
|
||||
options?.snapshot?.(ref) ?? Layer.mock(Snapshot.Service, {}),
|
||||
Layer.succeed(PluginSupervisor.Service, {
|
||||
flush: Effect.sync(() => {
|
||||
flushes.push(ref)
|
||||
awaitActivation: Effect.sync(() => {
|
||||
activationWaits.push(ref)
|
||||
}),
|
||||
}),
|
||||
),
|
||||
|
|
@ -175,7 +175,7 @@ const setup = Effect.fnUntraced(function* (options?: {
|
|||
}),
|
||||
Effect.provideService(SessionExecution.Service, options?.execution ?? execution),
|
||||
)
|
||||
return { sessions, hooks, locations, flushes, resumes, wakes, db: database.db, bus, store }
|
||||
return { sessions, hooks, locations, activationWaits, resumes, wakes, db: database.db, bus, store }
|
||||
})
|
||||
|
||||
describe("Session-owned handles", () => {
|
||||
|
|
@ -252,7 +252,7 @@ describe("Session-owned handles", () => {
|
|||
const calls: string[] = []
|
||||
yield* fixture.hooks.register("session", "prompt", (event) =>
|
||||
Effect.sync(() => {
|
||||
expect(fixture.flushes).toEqual([source])
|
||||
expect(fixture.activationWaits).toEqual([source])
|
||||
calls.push(event.prompt.text)
|
||||
event.prompt.text += " prepared"
|
||||
}),
|
||||
|
|
@ -277,7 +277,7 @@ describe("Session-owned handles", () => {
|
|||
)
|
||||
expect(calls).toEqual(["Original"])
|
||||
expect(fixture.locations).toEqual([source])
|
||||
expect(fixture.flushes).toEqual([source])
|
||||
expect(fixture.activationWaits).toEqual([source])
|
||||
expect(fixture.wakes).toEqual([
|
||||
{ sessionID, pending: [synthetic.id, first.id], enqueued: 2 },
|
||||
{ sessionID, pending: [synthetic.id, first.id], enqueued: 2 },
|
||||
|
|
@ -363,7 +363,7 @@ describe("Session-owned handles", () => {
|
|||
expect(fixture.locations).toEqual([source])
|
||||
yield* prompt
|
||||
expect(fixture.locations).toEqual([source, destination])
|
||||
expect(fixture.flushes).toEqual([source, destination])
|
||||
expect(fixture.activationWaits).toEqual([source, destination])
|
||||
expect((yield* fixture.sessions.forSession(otherID).get()).location).toEqual(source)
|
||||
}),
|
||||
)
|
||||
|
|
@ -408,7 +408,7 @@ describe("Session-owned handles", () => {
|
|||
expect(
|
||||
events.filter((event) => event.type === SessionEvent.Skill.Activated.type).map((event) => event.location),
|
||||
).toEqual([undefined, undefined, source])
|
||||
expect(fixture.flushes).toEqual([])
|
||||
expect(fixture.activationWaits).toEqual([])
|
||||
expect(fixture.resumes).toEqual([])
|
||||
expect(fixture.wakes).toEqual([])
|
||||
}),
|
||||
|
|
@ -442,7 +442,7 @@ describe("Session-owned handles", () => {
|
|||
expect(yield* handle.get()).toEqual(before)
|
||||
expect(yield* handle.inbox()).toEqual([])
|
||||
expect(yield* fixture.store.context(sessionID)).toEqual([])
|
||||
expect(fixture.flushes).toEqual([])
|
||||
expect(fixture.activationWaits).toEqual([])
|
||||
expect(fixture.resumes).toEqual([])
|
||||
expect(fixture.wakes).toEqual([])
|
||||
}),
|
||||
|
|
@ -776,7 +776,7 @@ describe("Session-owned handles", () => {
|
|||
|
||||
expect(captures).toEqual([source, destination])
|
||||
expect(fixture.locations).toEqual([source, destination, destination])
|
||||
expect(fixture.flushes).toEqual([source, destination, destination])
|
||||
expect(fixture.activationWaits).toEqual([source, destination, destination])
|
||||
expect((yield* handle.get()).revert).toBeUndefined()
|
||||
}),
|
||||
)
|
||||
|
|
@ -800,7 +800,7 @@ describe("SessionPrompt construction", () => {
|
|||
Layer.mergeAll(
|
||||
Layer.succeed(PluginHooks.Service, fixture.hooks),
|
||||
Layer.succeed(PluginSupervisor.Service, {
|
||||
flush: Effect.sync(() => {
|
||||
awaitActivation: Effect.sync(() => {
|
||||
calls.push("ready")
|
||||
}),
|
||||
}),
|
||||
|
|
@ -842,8 +842,8 @@ describe("SessionRevert construction", () => {
|
|||
Effect.provide(
|
||||
Layer.merge(
|
||||
Layer.succeed(PluginSupervisor.Service, {
|
||||
flush: Effect.sync(() => {
|
||||
calls.push("flush")
|
||||
awaitActivation: Effect.sync(() => {
|
||||
calls.push("awaitActivation")
|
||||
}),
|
||||
}),
|
||||
Layer.mock(Snapshot.Service, {
|
||||
|
|
@ -871,7 +871,7 @@ describe("SessionRevert construction", () => {
|
|||
yield* revert
|
||||
.stage({ session, messageID: boundary.id, files: false })
|
||||
.pipe(Effect.satisfiesServicesType<never>(), Effect.provide(unrelated))
|
||||
expect(calls).toEqual(["flush", "capture", "capture", "diff"])
|
||||
expect(calls).toEqual(["awaitActivation", "capture", "capture", "diff"])
|
||||
|
||||
const staged = yield* handle.get()
|
||||
expect(staged.revert?.snapshot).toBe(Snapshot.ID.make("captured-tree"))
|
||||
|
|
@ -879,7 +879,15 @@ describe("SessionRevert construction", () => {
|
|||
const cleared = yield* handle.get()
|
||||
expect(cleared.revert).toBeUndefined()
|
||||
yield* revert.clear(cleared).pipe(Effect.satisfiesServicesType<never>(), Effect.provide(unrelated))
|
||||
expect(calls).toEqual(["flush", "capture", "capture", "diff", "flush", "restore", "flush"])
|
||||
expect(calls).toEqual([
|
||||
"awaitActivation",
|
||||
"capture",
|
||||
"capture",
|
||||
"diff",
|
||||
"awaitActivation",
|
||||
"restore",
|
||||
"awaitActivation",
|
||||
])
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ const setup = Effect.gen(function* () {
|
|||
const services = locations.get(session.location)
|
||||
const hooks = yield* Effect.gen(function* () {
|
||||
const plugins = yield* PluginSupervisor.Service
|
||||
yield* plugins.flush
|
||||
yield* plugins.awaitActivation
|
||||
return yield* PluginHooks.Service
|
||||
}).pipe(Effect.provide(services))
|
||||
return { sessions, session, hooks, services }
|
||||
|
|
|
|||
|
|
@ -115,7 +115,7 @@ const locations = (references: Layer.Layer<Reference.Service>) =>
|
|||
Layer.succeed(
|
||||
PluginSupervisor.Service,
|
||||
PluginSupervisor.Service.of({
|
||||
flush: Effect.sync(() => (ready = true)),
|
||||
awaitActivation: Effect.sync(() => (ready = true)),
|
||||
}),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ describe("Session.revert files", () => {
|
|||
|
||||
yield* Effect.gen(function* () {
|
||||
const plugins = yield* PluginSupervisor.Service
|
||||
yield* plugins.flush
|
||||
yield* plugins.awaitActivation
|
||||
const snapshot = yield* Snapshot.Service
|
||||
const before = yield* snapshot.capture()
|
||||
if (!before) throw new Error("Initial snapshot missing")
|
||||
|
|
|
|||
|
|
@ -85,7 +85,10 @@ const referenceInstructions = Layer.mock(ReferenceInstructions.Service, {
|
|||
})
|
||||
const mcpInstructions = Layer.mock(McpInstructions.Service, { load: () => Effect.succeed(Instructions.empty) })
|
||||
const config = Config.testLayer()
|
||||
const pluginSupervisor = Layer.succeed(PluginSupervisor.Service, PluginSupervisor.Service.of({ flush: Effect.void }))
|
||||
const pluginSupervisor = Layer.succeed(
|
||||
PluginSupervisor.Service,
|
||||
PluginSupervisor.Service.of({ awaitActivation: Effect.void }),
|
||||
)
|
||||
const promptCatalog = Layer.mock(Catalog.Service, {
|
||||
provider: {
|
||||
get: () => Effect.undefined,
|
||||
|
|
|
|||
|
|
@ -206,7 +206,7 @@ const makeRunnerState = () => {
|
|||
systemUnavailable: false,
|
||||
systemLoadHook: Effect.void,
|
||||
skillBaselines: new Map<Agent.ID, string>(),
|
||||
pluginFlushHook: Effect.void,
|
||||
pluginActivationHook: Effect.void,
|
||||
authorizations: new Array<Tool.Context>(),
|
||||
executions: new Array<string>(),
|
||||
closedTransports: new Array<Session.ID>(),
|
||||
|
|
@ -390,7 +390,7 @@ const layer = Layer.unwrap(
|
|||
const pluginSupervisor = Layer.succeed(
|
||||
PluginSupervisor.Service,
|
||||
PluginSupervisor.Service.of({
|
||||
flush: Effect.suspend(() => state.pluginFlushHook),
|
||||
awaitActivation: Effect.suspend(() => state.pluginActivationHook),
|
||||
}),
|
||||
)
|
||||
const promptCatalog = Layer.mock(Catalog.Service, {
|
||||
|
|
@ -1840,7 +1840,7 @@ describe("SessionRunnerLLM", () => {
|
|||
|
||||
scenario("waits for initial plugin readiness before constructing the model request", function* (s) {
|
||||
const release = yield* Deferred.make<void>()
|
||||
s.pluginFlushHook = Deferred.await(release)
|
||||
s.pluginActivationHook = Deferred.await(release)
|
||||
yield* s.session.prompt({ sessionID, text: "Wait for plugins", resume: false })
|
||||
|
||||
s.requests.length = 0
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ const locations = makeGlobalNode({
|
|||
list: () => Effect.succeed([info]),
|
||||
}),
|
||||
Layer.succeed(PluginSupervisor.Service, {
|
||||
flush: Effect.void,
|
||||
awaitActivation: Effect.void,
|
||||
}),
|
||||
Layer.mock(Reference.Service, { refresh: () => Effect.void }),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -130,7 +130,7 @@ const it = testEffect(
|
|||
Catalog.node.replace(catalog),
|
||||
SessionRunnerModel.node.replace(models),
|
||||
Location.node.replace(Location.boundNode({ directory: AbsolutePath.make("/project") })),
|
||||
PluginSupervisor.node.replace(Layer.mock(PluginSupervisor.Service, { flush: Effect.void })),
|
||||
PluginSupervisor.node.replace(Layer.mock(PluginSupervisor.Service, { awaitActivation: Effect.void })),
|
||||
],
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -129,7 +129,7 @@ const shellPluginSupervisor = makeLocationNode({
|
|||
service: PluginSupervisor.Service,
|
||||
layer: Layer.effect(
|
||||
PluginSupervisor.Service,
|
||||
registerToolPlugin(ShellTool.Plugin).pipe(Effect.as(PluginSupervisor.Service.of({ flush: Effect.void }))),
|
||||
registerToolPlugin(ShellTool.Plugin).pipe(Effect.as(PluginSupervisor.Service.of({ awaitActivation: Effect.void }))),
|
||||
),
|
||||
deps: [
|
||||
Config.node,
|
||||
|
|
@ -220,7 +220,7 @@ const withSession = <A, E, R>(directory: string, body: (registry: Tool.Interface
|
|||
const locationLayer = locations.get(location)
|
||||
return yield* Effect.gen(function* () {
|
||||
const plugins = yield* PluginSupervisor.Service
|
||||
yield* plugins.flush
|
||||
yield* plugins.awaitActivation
|
||||
const registry = yield* Tool.Service
|
||||
return yield* body(registry)
|
||||
}).pipe(Effect.provide(locationLayer), Effect.ensuring(locations.invalidate(location)))
|
||||
|
|
|
|||
|
|
@ -107,7 +107,9 @@ const subagentPluginSupervisor = makeLocationNode({
|
|||
service: PluginSupervisor.Service,
|
||||
layer: Layer.effect(
|
||||
PluginSupervisor.Service,
|
||||
registerToolPlugin(SubagentTool.Plugin).pipe(Effect.as(PluginSupervisor.Service.of({ flush: Effect.void }))),
|
||||
registerToolPlugin(SubagentTool.Plugin).pipe(
|
||||
Effect.as(PluginSupervisor.Service.of({ awaitActivation: Effect.void })),
|
||||
),
|
||||
),
|
||||
deps: [Agent.node, Config.node, Permission.node, PluginRuntime.node, Tool.node],
|
||||
})
|
||||
|
|
@ -155,7 +157,9 @@ const completionIt = testEffect(
|
|||
const withSubagent = (location: Location.Ref) =>
|
||||
Effect.gen(function* () {
|
||||
const locations = yield* LocationServiceMap.Service
|
||||
yield* PluginSupervisor.Service.use((supervisor) => supervisor.flush).pipe(Effect.provide(locations.get(location)))
|
||||
yield* PluginSupervisor.Service.use((supervisor) => supervisor.awaitActivation).pipe(
|
||||
Effect.provide(locations.get(location)),
|
||||
)
|
||||
yield* Agent.Service.use((agents) =>
|
||||
agents.transform((draft) => {
|
||||
// The caller identity used by executeTool; subagent permission asserts against it.
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ for (const selection of ["explicit", "default"] as const) {
|
|||
PluginSupervisor.Service,
|
||||
Effect.gen(function* () {
|
||||
const plugins = yield* PluginSupervisor.Service
|
||||
return { flush: release.open.pipe(Effect.andThen(plugins.flush)) }
|
||||
return { awaitActivation: release.open.pipe(Effect.andThen(plugins.awaitActivation)) }
|
||||
}),
|
||||
).pipe(Layer.provide(layer)),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ export const PluginHandler = HttpApiBuilder.group(Api, "server.plugin", (handler
|
|||
.handle("plugin.update", (ctx) =>
|
||||
Effect.gen(function* () {
|
||||
const supervisor = yield* PluginSupervisor.Service
|
||||
yield* supervisor.flush
|
||||
yield* supervisor.awaitActivation
|
||||
const plugins = yield* Plugin.Service
|
||||
if (
|
||||
!(yield* plugins.list()).some(
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ export const RpcHandler = HttpApiBuilder.group(Api, "server.rpc", (handlers) =>
|
|||
handlers.handle("rpc.call", ({ params, payload }) =>
|
||||
Effect.gen(function* () {
|
||||
const supervisor = yield* PluginSupervisor.Service
|
||||
yield* supervisor.flush
|
||||
yield* supervisor.awaitActivation
|
||||
const rpc = yield* Rpc.Service
|
||||
const output = yield* rpc.call(params.rpcID, params.method, payload.input)
|
||||
return output === undefined ? {} : { output }
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ const fixture = Effect.fn(function* (plugins: readonly Plugin.Plugin[]) {
|
|||
boot: (directory: string) =>
|
||||
Effect.gen(function* () {
|
||||
const supervisor = yield* PluginSupervisor.Service
|
||||
yield* supervisor.flush
|
||||
yield* supervisor.awaitActivation
|
||||
}).pipe(Effect.provide(locations.get(Location.Ref.make({ directory: AbsolutePath.make(directory) })))),
|
||||
call: (
|
||||
route: string,
|
||||
|
|
|
|||
|
|
@ -250,7 +250,7 @@ test("controls arbitrary tools through scoped SDK overlays", async () => {
|
|||
Layer.build(locations.get(Location.Ref.make({ directory: AbsolutePath.make(secondDirectory) }))),
|
||||
])
|
||||
yield* Effect.forEach([primary, secondary], (context) =>
|
||||
PluginSupervisor.Service.use((supervisor) => supervisor.flush).pipe(Effect.provide(context)),
|
||||
PluginSupervisor.Service.use((supervisor) => supervisor.awaitActivation).pipe(Effect.provide(context)),
|
||||
)
|
||||
expect(activations).toBe(2)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue