feat(dx): scopeHost, agent-aware ACP presentation, and the scoped-dispatch drift gate
scopeHost(ctx, services) is the sanctioned way to mint scopes in tests: it names absent services loudly instead of the cryptic cordis without-inject dead end, and catches the silent-no-op host (cordis resolves a dependency-pending fiber's await without running the inject callback). The ACP ToolPresenter resolves presentations through the session agent's view (tools.get(name, agent)) so a scoped/shadowed tool renders with the same definition that executed. verify-scoped-dispatch (doc-sync + pre-push) pins the dev-invariants carrier table against the declaration JSDoc set: an event enforced but undocumented, documented but unenforced, or a registry-subject notification leaking into the table fails the build. subagent/start|end docs gain their scoped-dispatch sentence (a real gap the gate caught on first run).
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { describe, expect, expectTypeOf, it } from 'vitest'
|
||||
import { Context } from 'cordis'
|
||||
import { carrierKeyOf, createScope, isScopeCarrier, scopeOf, scopeTarget } from '@deepseek-ai/dsh-scope'
|
||||
import { carrierKeyOf, createScope, isScopeCarrier, scopeHost, scopeOf, scopeTarget } from '@deepseek-ai/dsh-scope'
|
||||
import type { Scope, ScopeKey, Scoped } from '@deepseek-ai/dsh-scope'
|
||||
|
||||
declare module 'cordis' {
|
||||
@@ -207,3 +207,24 @@ describe('carrier marks', () => {
|
||||
expectTypeOf(base).not.toExtend<Scoped<{ name: string }>>()
|
||||
})
|
||||
})
|
||||
|
||||
describe('scopeHost', () => {
|
||||
it('mints scopes that reach the injected services; dispose unwinds them all', async () => {
|
||||
const ctx = new Context()
|
||||
ctx.provide('answers', { value: 42 })
|
||||
const host = await scopeHost(ctx, ['answers'])
|
||||
const scope = host.mint({ name: 'a' })
|
||||
expect((scope.ctx as Context & { answers: { value: number } }).answers.value).toBe(42)
|
||||
const order: string[] = []
|
||||
scope.ctx.effect(() => () => void order.push('scoped-disposed'))
|
||||
await host.dispose()
|
||||
expect(order).toEqual(['scoped-disposed'])
|
||||
expect(() => scope.ctx.effect(() => () => {})).toThrow(/inactive context/)
|
||||
})
|
||||
|
||||
it('fails LOUD naming absent services instead of resolving as a silent no-op host', async () => {
|
||||
const ctx = new Context()
|
||||
await expect(scopeHost(ctx, ['tools', 'systemPrompt']))
|
||||
.rejects.toThrow('scopeHost: services "tools", "systemPrompt" not available')
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user