test: close the per-file coverage gaps for the scoping surface
Every subject-extractor row of the invariants carrier table is exercised with a matching and a foreign-keyed carrier; the HMR re-apply seed path (sessions of agents that predate the plugin are marked started) is pinned; the scoped tool-provider disposal, plural restrict() validation, singular scopeHost absentee, tool-subagent passthrough, stale-stage drop, and disposing-parent spawn (INACTIVE_EFFECT, no orphan) each gain their test. Two genuinely defensive branches carry justified v8-ignore markers.
This commit is contained in:
@@ -283,7 +283,11 @@ export async function scopeHost(ctx: Context, services: string[]): Promise<Scope
|
||||
// callback. Name the absentees and unwind the pending fiber.
|
||||
const missing = services.filter(name => ctx.get(name) === undefined)
|
||||
await fiber.dispose()
|
||||
throw new Error(`scopeHost: service${missing.length === 1 ? '' : 's'} ${missing.map(name => `"${name}"`).join(', ') || '(unknown)'} not available on this context — load the providing plugin(s) before minting scopes`)
|
||||
/* v8 ignore next -- the '(unknown)' fallback is defensive: a pending
|
||||
* fiber with zero absent services cannot occur (an all-present inject
|
||||
* list runs the callback) */
|
||||
const named = missing.map(name => `"${name}"`).join(', ') || '(unknown)'
|
||||
throw new Error(`scopeHost: service${missing.length === 1 ? '' : 's'} ${named} not available on this context — load the providing plugin(s) before minting scopes`)
|
||||
}
|
||||
const host = hostCtx
|
||||
return {
|
||||
|
||||
@@ -227,4 +227,9 @@ describe('scopeHost', () => {
|
||||
await expect(scopeHost(ctx, ['tools', 'systemPrompt']))
|
||||
.rejects.toThrow('scopeHost: services "tools", "systemPrompt" not available')
|
||||
})
|
||||
|
||||
it('names a single absent service in the singular', async () => {
|
||||
const ctx = new Context()
|
||||
await expect(scopeHost(ctx, ['tools'])).rejects.toThrow('scopeHost: service "tools" not available')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -100,6 +100,19 @@ describe('scoped tool providers and toolOrder × restriction', () => {
|
||||
expect(global.tools.map(t => t.name)).toEqual(['global_tool'])
|
||||
})
|
||||
|
||||
it('disposing a scoped tool provider empties its layer without residue', async () => {
|
||||
const ctx = await mount()
|
||||
const scope = await mintScope(ctx, 'child')
|
||||
const dispose = scope.ctx.systemPrompt.tools(() => ({ schemas: [schema('scoped_tool')] }))
|
||||
dispose()
|
||||
const after = await ctx.systemPrompt.assemble({ scope: scopeKeyOf(scope) })
|
||||
expect(after.tools.map(t => t.name)).toEqual([])
|
||||
// Re-registering through the same scope starts a fresh layer.
|
||||
scope.ctx.systemPrompt.tools(() => ({ schemas: [schema('again')] }))
|
||||
const again = await ctx.systemPrompt.assemble({ scope: scopeKeyOf(scope) })
|
||||
expect(again.tools.map(t => t.name)).toEqual(['again'])
|
||||
})
|
||||
|
||||
it('a toolOrder entry restricted away for a scope is a normal absence, while a typo still throws', async () => {
|
||||
const ctx = await mount({ toolOrder: ['bash', TOOL_ORDER_REST] })
|
||||
// A provider mimicking the registry's restriction split: bash exists
|
||||
|
||||
@@ -148,6 +148,7 @@ describe('restrict()', () => {
|
||||
expect(() => ctx.tools.restrict({ deny: ['real'] })).toThrow(/requires a scoped context/)
|
||||
expect(() => scope.ctx.tools.restrict({})).toThrow(/no-op/)
|
||||
expect(() => scope.ctx.tools.restrict({ allow: ['reall'] })).toThrow(/unknown tool "reall"; known tools for this scope: real/)
|
||||
expect(() => scope.ctx.tools.restrict({ deny: ['ghost', 'wraith'] })).toThrow(/unknown tools "ghost", "wraith"/)
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user