63 lines
3.1 KiB
TypeScript
63 lines
3.1 KiB
TypeScript
/** Prompt assembly must not omit plan guidance during activation. */
|
|
import { Context } from '@deepseek-ai/cordis'
|
|
import AgentLoop from '@deepseek-ai/dsh-agent-loop'
|
|
import { mountAgentLoopTestDependencies } from '@deepseek-ai/dsh-agent-loop-testkit'
|
|
import { createUserMessage } from '@deepseek-ai/dsh-llm'
|
|
import PlanModeController from '@deepseek-ai/dsh-plan-mode'
|
|
import { Session, SessionId } from '@deepseek-ai/dsh-session'
|
|
import { describe, expect, it, onTestFinished, vi } from 'vitest'
|
|
import { MockAdapter, textResponse } from '../../../core/agent-loop/tests/mock-adapter.ts'
|
|
|
|
describe('plan-mode activation', () => {
|
|
it.each([false, true])('rejects premature requests with restored active=%s, then includes the complete plan contribution', async (active) => {
|
|
const ctx = new Context()
|
|
const entered = Promise.withResolvers<undefined>()
|
|
const release = Promise.withResolvers<undefined>()
|
|
onTestFinished(async () => {
|
|
release.resolve(undefined)
|
|
await ctx.fiber.dispose()
|
|
})
|
|
await mountAgentLoopTestDependencies(ctx)
|
|
await ctx.plugin(AgentLoop, { agents: [] })
|
|
const adapter = new MockAdapter([textResponse('ready')])
|
|
ctx.llm.registerAdapter(['mock'], adapter)
|
|
const seed = Session.create(SessionId('plan-seed'))
|
|
seed.append('plan/mode', { active })
|
|
const { agent } = await ctx.agents.create({
|
|
sessionId: SessionId('initial-plan'), seed: seed.snapshotEvents(),
|
|
agentOptions: { provider: 'mock', model: 'mock' },
|
|
})
|
|
const register = ctx.sessionProjections.register.bind(ctx.sessionProjections)
|
|
const spy = vi.spyOn(ctx.sessionProjections, 'register').mockImplementation(async (definition) => {
|
|
const handle = await register(definition)
|
|
if (definition.key === 'plan') {
|
|
entered.resolve(undefined)
|
|
await release.promise
|
|
}
|
|
return handle
|
|
})
|
|
onTestFinished(() => { spy.mockRestore() })
|
|
const fiber = ctx.plugin(PlanModeController, { section: 'Plan the work before executing it.' })
|
|
await entered.promise
|
|
const errors: unknown[] = []
|
|
ctx.on('agent/error', ({ error }) => { errors.push(error) })
|
|
const prompt = createUserMessage({ content: [{ type: 'text', text: 'continue the task' }], source: { kind: 'user' } })
|
|
agent.followup(prompt)
|
|
await agent.whenIdle()
|
|
expect(adapter.requests).toHaveLength(0)
|
|
expect(errors.some(error => error instanceof Error && /plan.*not ready/.test(error.message))).toBe(true)
|
|
expect(agent.session.snapshotEvents().filter(event => event.type === 'request/header')).toHaveLength(0)
|
|
|
|
release.resolve(undefined)
|
|
await fiber
|
|
agent.followup(prompt)
|
|
await agent.whenIdle()
|
|
expect(adapter.requests).toHaveLength(1)
|
|
expect(adapter.requests[0]?.tools?.map(tool => tool.name)).toContain('exit_plan_mode')
|
|
const text = adapter.requests[0]?.messages.flatMap(message => message.content)
|
|
.filter(block => block.type === 'text').map(block => block.text).join('\n')
|
|
expect(text?.includes('Plan the work before executing it.')).toBe(active)
|
|
expect(ctx.planMode.get(agent)).toEqual({ active })
|
|
})
|
|
})
|