import { describe, expect, it } from 'vitest' import { Context } from 'cordis' import Loader from '@cordisjs/plugin-loader' import AgentRegistry, { Inbox } from '@deepseek-ai/dsh-agent' import type { Agent, AgentStatus } from '@deepseek-ai/dsh-agent' import CommandService from '@deepseek-ai/dsh-commands' import SessionStore, { foldSurface, Session, SessionId } from '@deepseek-ai/dsh-session' import * as commandFeedback from '@deepseek-ai/dsh-command-feedback' interface Harness { readonly ctx: Context readonly agent: Agent readonly session: Session readonly plugin: Awaited> } /** Build a live idle agent over a store-owned session, as an app's spine does. */ function stubAgent(ctx: Context, id: string): { agent: Agent; session: Session } { const session = ctx.sessions.create(SessionId(id)) const inbox = new Inbox(session, { inserted: () => {}, discarded: () => {}, claimed: () => {} }) let status: AgentStatus = 'idle' const agent: Agent = { id: session.id, options: {}, session, inbox, ctx: new Context(), get status() { return status }, send: () => {}, followup: () => {}, steer: () => {}, inject: () => {}, cancel() { status = 'idle' }, runMaintenance: task => task(new AbortController().signal), whenIdle() { return Promise.resolve() }, } return { agent, session } } /** Mount the real command registry and this producer. */ async function harness(): Promise { const ctx = new Context() await ctx.plugin(CommandService) await ctx.plugin(AgentRegistry) await ctx.plugin(SessionStore) const plugin = await ctx.plugin(commandFeedback) const { agent, session } = stubAgent(ctx, `command-feedback-${Math.random()}`) ctx.agents.register(agent) return { ctx, agent, session, plugin } } /** Execute `/feedback` through the same registry boundary as a UI adapter. */ async function run(test: Harness, suffix = ''): Promise<{ kind: string; text?: string }> { const settled = await test.ctx.commands.execute( test.agent, `/feedback${suffix}`, new AbortController().signal, ) if (settled === undefined) throw new Error('feedback command was not registered') return settled.result } /** Authoritative feedback payloads in log order. */ function feedbackTexts(session: Session): string[] { return session.events .filter(event => event.type === 'feedback/record') .map(event => event.data.text) } describe('@deepseek-ai/dsh-command-feedback registration', () => { it('registers one global command with Loader-safe exports and disposes it', async () => { const test = await harness() expect(commandFeedback.name).toBe('command-feedback') expect(commandFeedback.inject).toEqual(['commands']) expect('default' in commandFeedback).toBe(false) const loader = Object.create(Loader.prototype) as Loader expect(loader.unwrapExports(commandFeedback)).toBe(commandFeedback) expect(test.ctx.commands.list(test.agent)).toContainEqual({ name: 'feedback', description: 'record feedback about this session', input: { hint: '' }, }) expect(test.ctx.commands.find(test.agent, 'feedback')).toMatchObject({ recordInput: false }) await test.plugin.dispose() expect(test.ctx.commands.find(test.agent, 'feedback')).toBeUndefined() }) }) describe('/feedback human command', () => { it('acknowledges feedback and records its payload exactly once in the domain event', async () => { const test = await harness() await expect(run(test, ' the diff view is unreadable')).resolves.toEqual({ kind: 'success', text: 'Feedback recorded.', }) expect(feedbackTexts(test.session)).toEqual(['the diff view is unreadable']) const commandRun = test.session.events.find(event => event.type === 'command/run') expect(commandRun?.type === 'command/run' && Object.hasOwn(commandRun.data, 'args')).toBe(false) expect(JSON.stringify(test.session.events).match(/the diff view is unreadable/gu)).toHaveLength(1) }) it('exports a command-independent feedback producer', async () => { const test = await harness() commandFeedback.recordFeedback(test.session, ' recorded outside a command ') expect(test.session.events.map(event => event.type)).toEqual(['feedback/record']) expect(feedbackTexts(test.session)).toEqual(['recorded outside a command']) expect(() => { commandFeedback.recordFeedback(test.session, ' \n\t ') }) .toThrow('feedback text must not be empty') expect(feedbackTexts(test.session)).toEqual(['recorded outside a command']) }) it('keeps command bookkeeping around the authoritative feedback event', async () => { const test = await harness() await run(test, ' nothing else happens') expect(test.session.events.map(event => event.type)).toEqual([ 'command/run', 'feedback/record', 'command/done', ]) }) it('normalizes surrounding whitespace without parsing command-like content', async () => { const test = await harness() await run(test, ' /plan felt SLOW\n\ttwice today ') expect(feedbackTexts(test.session)).toEqual(['/plan felt SLOW\n\ttwice today']) }) it('records each entry separately without replacing earlier ones', async () => { const test = await harness() await run(test, ' first') await run(test, ' second') expect(feedbackTexts(test.session)).toEqual(['first', 'second']) }) it('records concurrent submissions in dispatch order', async () => { const test = await harness() const signal = new AbortController().signal // Command adapters may dispatch concurrent requests without awaiting one another. const settled = await Promise.all([ test.ctx.commands.execute(test.agent, '/feedback first', signal), test.ctx.commands.execute(test.agent, '/feedback second', signal), ]) expect(settled.map(item => item?.result)).toEqual([ { kind: 'success', text: 'Feedback recorded.' }, { kind: 'success', text: 'Feedback recorded.' }, ]) expect(feedbackTexts(test.session)).toEqual(['first', 'second']) }) it('keeps every recorded event off the model surface and out of derived history', async () => { const test = await harness() await run(test, ' invisible to the model') for (const event of test.session.events) { expect('surfaceOp' in event).toBe(false) expect(test.session.deriveEventMessage(event)).toBeNull() } expect(foldSurface(test.session.events).nodes).toEqual([]) expect(test.session.surface.nodes).toEqual([]) expect(test.session.deriveMessages()).toEqual([]) }) it('rejects empty and whitespace-only input as a failed command record', async () => { const test = await harness() const expected = { kind: 'error', text: 'Feedback text is required. Usage: /feedback ', } await expect(run(test)).resolves.toEqual(expected) await expect(run(test, ' \n\t ')).resolves.toEqual(expected) expect(feedbackTexts(test.session)).toEqual([]) const done = test.session.events.filter(event => event.type === 'command/done') expect(done.map(event => event.data.kind)).toEqual(['error', 'error']) for (const event of test.session.events) { if (event.type === 'command/run') expect(Object.hasOwn(event.data, 'args')).toBe(false) } }) it('records nothing when dispatch rejects an already-cancelled request', async () => { const test = await harness() const controller = new AbortController() controller.abort(new Error('user cancelled the command')) await expect(test.ctx.commands.execute(test.agent, '/feedback too late', controller.signal)) .rejects.toThrow('user cancelled the command') expect(test.session.events).toEqual([]) }) })