fix: honor the teardown order on owner unload; make the structured commit unconditional
Adversarial-review findings (own reviewer agent), each verified and pinned: B1: agents.register() returned a wrapper lambda, so the factory composite's yield could not identity-nest it — on OWNER unload the unregistration (and agent/disposed) disposed as a concurrent sibling, firing mid-drain while the final turn was still closing (pre-existing on master; this branch's docs re-assert the order, so it must be true). register() now returns the EXACT cordis effect disposer (the Scope.rawDispose move); the composite nests it and owner unload runs stop/drain -> unregister -> detach -> scope like every other path. Regression test pins turn-end before disposed before detach on owner unload. B2: the structured two-phase commit could promote a stale stage when a later capture call REUSED the orphaned stage's call id with a body that never staged (denied downstream, or invalid args throwing pre-stage). The runtime's pre-execute listener now clears any stale stage unconditionally when a new capture call enters the pipeline — only a call's own body can stage for its commit; the call-id mismatch guard becomes a defensive second layer. Repro test: blocked capture then same-id invalid call. C1: an explicit empty toolFilter config now fails at plugin LOAD (the check is self-contained) instead of killing every delegation at child setup. C2: Scope.dispose/ScopeHost.dispose @returns state the single-shot repeat-call semantics honestly.
This commit is contained in:
@@ -184,6 +184,12 @@ export function providerWording(inherits: boolean): { description: string; promp
|
||||
}
|
||||
|
||||
export function apply(ctx: Context, config: Config): void {
|
||||
// Misconfiguration fails loud AT LOAD (the check is self-contained): an
|
||||
// explicit `toolFilter: {}` would otherwise pass the capability gate and
|
||||
// kill every delegation later, in the child-setup `restrict({})` throw.
|
||||
if (config.toolFilter !== undefined && config.toolFilter.allow === undefined && config.toolFilter.deny === undefined) {
|
||||
throw new Error('tool-subagent: `toolFilter` is configured but names neither `allow` nor `deny` — remove the key or fill the filter')
|
||||
}
|
||||
// The tool MIRRORS its provider's lifecycle instead of assuming load order:
|
||||
// the cordis Loader starts sibling entries concurrently, so "backend listed
|
||||
// first in cordis.yml" does not guarantee "provider registered first", and
|
||||
|
||||
Reference in New Issue
Block a user