| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105 |
- import { describe, expect, it } from 'vitest'
- import { Context, Service } from '@deepseek-ai/cordis'
- import Loader from '@deepseek-ai/cordis-plugin-loader'
- import { agentEvents } from '@deepseek-ai/dsh-agent'
- import AgentLoop from '@deepseek-ai/dsh-agent-loop'
- import { mountAgentLoopTestDependencies } from '@deepseek-ai/dsh-agent-loop-testkit'
- import { CallId } from '@deepseek-ai/dsh-llm'
- import { SessionId } from '@deepseek-ai/dsh-session'
- import * as toolSchedule from '../src/index.ts'
- class PersistenceProbe extends Service {
- constructor(ctx: Context) {
- super(ctx, 'sessionPersistence')
- }
- }
- async function harness(): Promise<Context> {
- const ctx = new Context()
- await mountAgentLoopTestDependencies(ctx)
- await ctx.plugin(PersistenceProbe)
- ctx.on('session/flush', () => {})
- await ctx.plugin(AgentLoop, { agents: [] })
- return ctx
- }
- async function settle(): Promise<void> {
- for (let index = 0; index < 8; index += 1) await Promise.resolve()
- }
- describe('Schedule plugin composition', () => {
- it('has the Loader-safe function-plugin export shape', () => {
- expect('default' in toolSchedule).toBe(false)
- expect(toolSchedule.name).toBe('tool-schedule')
- expect(toolSchedule.inject).toEqual(['agents', 'sessions', 'tools', 'sessionPersistence'])
- const loader = Object.create(Loader.prototype) as Loader
- expect(loader.unwrapExports(toolSchedule)).toBe(toolSchedule)
- })
- it('installs only on future root agents and unwinds on plugin disposal', async () => {
- const ctx = await harness()
- const existing = await ctx.agents.create({ sessionId: SessionId('schedule-existing') })
- const plugin = await ctx.plugin(toolSchedule)
- expect(ctx.tools.get('schedule_create', existing.agent)).toBeUndefined()
- expect(ctx.tools.get('schedule_create')).toBeUndefined()
- const root = await ctx.agents.create({ sessionId: SessionId('schedule-root') })
- expect(ctx.tools.get('schedule_create', root.agent)?.name).toBe('schedule_create')
- expect(ctx.tools.get('schedule_list', root.agent)?.name).toBe('schedule_list')
- expect(ctx.tools.get('schedule_delete', root.agent)?.name).toBe('schedule_delete')
- expect(ctx.tools.get('schedule_create')).toBeUndefined()
- const created = await ctx.agents.withInitiator(root.agent, () => ctx.tools.execute({
- signal: new AbortController().signal,
- callId: CallId('schedule-plugin-create'),
- name: 'schedule_create',
- arguments: { prompt: 'future reminder', after_seconds: 3_600 },
- agent: root.agent,
- }))
- expect(created.isError).toBe(false)
- if (created.isError) throw new Error('expected Schedule create value')
- expect(created.value).toMatchObject({ id: 'schedule-1', deliveryMode: 'session-local' })
- agentEvents(ctx, root.agent).emit('agent/status', { status: 'running' })
- agentEvents(ctx, root.agent).emit('agent/status', { status: 'idle' })
- const child = await root.agent.ctx.agents.create({ sessionId: SessionId('schedule-child') })
- expect(ctx.agents.roots()).toEqual([existing.agent, root.agent])
- expect(ctx.tools.get('schedule_create', child.agent)).toBeUndefined()
- const departing = await ctx.agents.create({ sessionId: SessionId('schedule-departing') })
- expect(ctx.tools.get('schedule_create', departing.agent)).toBeDefined()
- await departing.dispose()
- expect(ctx.tools.get('schedule_create', departing.agent)).toBeUndefined()
- await plugin.dispose()
- expect(ctx.tools.get('schedule_create', root.agent)).toBeUndefined()
- expect(ctx.tools.get('schedule_list', root.agent)).toBeUndefined()
- expect(ctx.tools.get('schedule_delete', root.agent)).toBeUndefined()
- await child.dispose()
- await root.dispose()
- await existing.dispose()
- await ctx.fiber.dispose()
- })
- it('does not checkpoint unrelated idle sessions', async () => {
- const ctx = await harness()
- const plugin = await ctx.plugin(toolSchedule)
- const root = await ctx.agents.create({ sessionId: SessionId('schedule-unrelated-idle') })
- await settle()
- let flushes = 0
- const stopFlush = ctx.on('session/flush', (session) => {
- if (session === root.agent.session) flushes += 1
- })
- agentEvents(ctx, root.agent).emit('agent/status', { status: 'running' })
- agentEvents(ctx, root.agent).emit('agent/status', { status: 'idle' })
- await settle()
- expect(flushes).toBe(0)
- stopFlush()
- await root.dispose()
- await plugin.dispose()
- await ctx.fiber.dispose()
- })
- })
|