refactor(core): rename plugin flush to awaitActivation

This commit is contained in:
Kit Langton 2026-09-01 10:48:08 -04:00 committed by GitHub
parent f330f3e02b
commit d4b4dd17cc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
32 changed files with 104 additions and 87 deletions

View file

@ -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(

View file

@ -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") {}

View file

@ -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 })
}),
)

View file

@ -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"

View file

@ -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 })

View file

@ -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,

View file

@ -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)

View file

@ -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")(() =>

View file

@ -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

View file

@ -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) =>

View file

@ -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],

View file

@ -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 }),
),

View file

@ -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,

View file

@ -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")),

View file

@ -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()

View file

@ -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",

View file

@ -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({

View file

@ -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",
])
}),
)
})

View file

@ -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 }

View file

@ -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)),
}),
),
),

View file

@ -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")

View file

@ -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,

View file

@ -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

View file

@ -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 }),
),

View file

@ -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 })),
],
),
)

View file

@ -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)))

View file

@ -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.

View file

@ -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)),
)

View file

@ -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(

View file

@ -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 }

View file

@ -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,

View file

@ -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)