import { describe, expect, it } from 'vitest' import { Context } from 'cordis' import { runInNewContext } from 'node:vm' import AgentRegistry from '@deepseek-ai/dsh-agent' import type { Agent } from '@deepseek-ai/dsh-agent' import { SessionId } from '@deepseek-ai/dsh-session' function agent(id: string): Agent { return { id: SessionId(id) } as Agent } async function harness(): Promise<{ ctx: Context service: AgentRegistry dispose: () => Promise }> { const ctx = new Context() const fiber = await ctx.plugin(AgentRegistry) return { ctx, service: ctx.agents, dispose: fiber.dispose, } } /** Fail a lifecycle regression promptly instead of waiting for Vitest's suite timeout. */ async function promptly(task: Promise): Promise { const timeout = Promise.withResolvers() const timer = setTimeout(() => { timeout.reject(new Error('initiator teardown did not settle promptly')) }, 1000) try { return await Promise.race([task, timeout.promise]) } finally { clearTimeout(timer) } } describe('AgentRegistry initiator scope', () => { it('reports an absent initiator and requires an active boundary', async () => { const { service, dispose } = await harness() expect(service.currentInitiator()).toBeUndefined() expect(() => service.requireInitiator()).toThrow('no initiating agent is active') await dispose() }) it('preserves exact synchronous and Promise return identities across await', async () => { const { service, dispose } = await harness() const initiator = agent('identity') const value = { result: true } expect(service.withInitiator(initiator, () => { expect(service.requireInitiator()).toBe(initiator) return value })).toBe(value) const promise = service.withInitiator(initiator, async () => { expect(service.requireInitiator()).toBe(initiator) await Promise.resolve() expect(service.requireInitiator()).toBe(initiator) return value }) expect(service.withInitiator(initiator, () => promise)).toBe(promise) await expect(promise).resolves.toBe(value) expect(service.currentInitiator()).toBeUndefined() await dispose() }) it('tracks a branded Promise without calling its overridable then property', async () => { const { service, dispose } = await harness() const initiator = agent('overridden-then') const release = Promise.withResolvers() void Object.defineProperty(release.promise, 'then', { value: () => { throw new Error('overridden then called') }, }) const pending = service.withInitiator(initiator, () => release.promise) expect(pending).toBe(release.promise) let disposed = false const disposal = dispose().then(() => { disposed = true }) await Promise.resolve() expect(disposed).toBe(false) release.resolve(true) await new Promise((resolve, reject) => { void Promise.prototype.then.call(pending, resolve, reject) }) await disposal expect(disposed).toBe(true) }) it('preserves a settled branded Promise when its species blocks observer construction', async () => { const { service, dispose } = await harness() const initiator = agent('invalid-species') const promise = Promise.resolve() const constructor = {} Object.defineProperty(constructor, Symbol.species, { get: () => { throw new Error('invalid species') }, }) void Object.defineProperty(promise, 'constructor', { value: constructor }) expect(service.withInitiator(initiator, () => promise)).toBe(promise) await dispose() }) it('isolates overlapping initiators', async () => { const { service, dispose } = await harness() const a = agent('a') const b = agent('b') const bothStarted = Promise.withResolvers() const release = Promise.withResolvers() let starts = 0 const run = (initiator: Agent): Promise => service.withInitiator(initiator, async () => { expect(service.requireInitiator()).toBe(initiator) starts += 1 if (starts === 2) bothStarted.resolve(true) await release.promise expect(service.requireInitiator()).toBe(initiator) }) const pending = [run(a), run(b)] await bothStarted.promise expect(service.currentInitiator()).toBeUndefined() release.resolve(true) await Promise.all(pending) await dispose() }) it('restores nested and explicitly cleared boundaries', async () => { const { service, dispose } = await harness() const parent = agent('parent') const child = agent('child') service.withInitiator(parent, () => { expect(service.requireInitiator()).toBe(parent) service.withInitiator(child, () => { expect(service.requireInitiator()).toBe(child) }) expect(service.requireInitiator()).toBe(parent) service.withoutInitiator(() => { expect(service.currentInitiator()).toBeUndefined() expect(() => service.requireInitiator()).toThrow('no initiating agent is active') }) expect(service.requireInitiator()).toBe(parent) }) expect(service.currentInitiator()).toBeUndefined() await dispose() }) it('restores the parent after synchronous throws and rejected operations', async () => { const { service, dispose } = await harness() const parent = agent('parent') const child = agent('child') const syncError = new Error('sync failure') const asyncError = new Error('async failure') service.withInitiator(parent, () => { expect(() => service.withInitiator(child, () => { throw syncError })).toThrow(syncError) expect(service.requireInitiator()).toBe(parent) }) await expect(service.withInitiator(child, async () => { await Promise.resolve() throw asyncError })).rejects.toBe(asyncError) expect(service.currentInitiator()).toBeUndefined() await dispose() }) it('stops new boundaries, drains active Promises, and invalidates retained references', async () => { const { ctx, service, dispose } = await harness() const initiator = agent('draining') const release = Promise.withResolvers() const pending = service.withInitiator(initiator, async () => { await release.promise expect(service.requireInitiator()).toBe(initiator) }) let disposed = false const disposal = dispose().then(() => { disposed = true }) await Promise.resolve() expect(() => service.withInitiator(initiator, () => 1)).toThrow('agent initiator scope is disposed') expect(() => service.withoutInitiator(() => 1)).toThrow('agent initiator scope is disposed') expect(disposed).toBe(false) expect(ctx.get('agents')).toBeUndefined() release.resolve(true) await pending await disposal expect(() => service.currentInitiator()).toThrow('agent initiator scope is disposed') expect(() => service.requireInitiator()).toThrow('agent initiator scope is disposed') }) it('drains cross-realm Promise boundaries before disposal', async () => { const { service, dispose } = await harness() const initiator = agent('cross-realm') const release = Promise.withResolvers() const operation = runInNewContext( '(async () => { await release; inspect() })', { release: release.promise, inspect: () => { expect(service.requireInitiator()).toBe(initiator) }, }, ) as () => Promise const pending = service.withInitiator(initiator, operation) expect(pending).not.toBeInstanceOf(Promise) let disposed = false const disposal = dispose().then(() => { disposed = true }) await Promise.resolve() expect(disposed).toBe(false) release.resolve(true) await pending await disposal expect(disposed).toBe(true) }) it('does not self-deadlock when a boundary returns service disposal', async () => { const { service, dispose } = await harness() const initiator = agent('service-disposer') const returned = service.withInitiator(initiator, dispose) await promptly(returned) expect(() => service.currentInitiator()).toThrow('agent initiator scope is disposed') }) it('does not self-deadlock when nested boundaries return ancestor disposal', async () => { const { ctx, service } = await harness() const parent = agent('parent-disposer') const child = agent('child-disposer') let disposal: Promise | undefined const returned = service.withInitiator(parent, () => service.withInitiator(child, () => { disposal = ctx.fiber.dispose() return disposal })) expect(returned).toBe(disposal) await promptly(returned) expect(() => service.currentInitiator()).toThrow('agent initiator scope is disposed') }) it('excludes an asynchronous teardown initiator while draining unrelated boundaries', async () => { const { ctx, service } = await harness() const initiator = agent('async-disposer') const unrelated = agent('unrelated') const release = Promise.withResolvers() const pending = service.withInitiator(unrelated, async () => { await release.promise expect(service.requireInitiator()).toBe(unrelated) }) const returned = service.withInitiator(initiator, async () => { await Promise.resolve() await ctx.fiber.dispose() }) let disposed = false void returned.then(() => { disposed = true }) await Promise.resolve() await Promise.resolve() expect(disposed).toBe(false) release.resolve(true) await pending await promptly(returned) expect(() => service.currentInitiator()).toThrow('agent initiator scope is disposed') }) })