import { Context } from '@deepseek-ai/cordis' import AgentRegistry, { Inbox } from '@deepseek-ai/dsh-agent' import type { Agent, ModelSelectionRef } from '@deepseek-ai/dsh-agent' import { AttachmentError, AttachmentId } from '@deepseek-ai/dsh-attachment' import type { ImageAttachmentRef } from '@deepseek-ai/dsh-attachment' import { createAssistantMessage, createUserMessage, MessageId } from '@deepseek-ai/dsh-llm' import SessionStore, { SessionId } from '@deepseek-ai/dsh-session' import type { SessionEvent, SessionHeader } from '@deepseek-ai/dsh-session' import { describe, expect, it, vi } from 'vitest' import { ApiSessionAgentController } from '../src/agent.ts' import { SessionCommandController } from '../src/commands.ts' import { installSessionReadTestServices, testSessionPersistence } from './test-remote.ts' async function commandHarness(): Promise<{ ctx: Context controller: SessionCommandController agent: Agent inbox: Inbox steer: ReturnType cancel: ReturnType }> { const ctx = new Context() await ctx.plugin(SessionStore) await ctx.plugin(AgentRegistry) const session = ctx.sessions.create(SessionId('commands-session'), { meta: { cwd: '/workspace' } }) const inbox = new Inbox(session, { inserted: () => {}, discarded: () => {}, claimed: () => {} }) const steer = vi.fn() const cancel = vi.fn() const agent = { id: session.id, session, inbox, status: 'running', ctx, steer, followup: vi.fn(), cancel, } as unknown as Agent ctx.agents.register(agent) ctx.provide('workspaceRegistry', { get: () => undefined, list: () => [] } as never) ctx.provide('agentDefaultModel', { currentSelection: () => ({ provider: 'fixture', model: 'fixture-model' }), saveSelection: () => Promise.resolve(), } as never) const selection: ModelSelectionRef = { current: { provider: 'fixture', model: 'fixture-model' }, assembled: undefined, } const agents = { resolveAgent: () => Promise.resolve({ agent }), selectionFor: () => selection, serializeImageAdmission: (_agent: Agent, operation: () => Promise) => operation(), composeAgent: () => Promise.resolve({ setup: () => {} }), } as unknown as ApiSessionAgentController return { ctx, controller: new SessionCommandController(ctx, agents, '/workspace'), agent, inbox, steer, cancel } } async function expectFailure(operation: Promise, code: string): Promise { await expect(operation).rejects.toMatchObject({ failure: { code } }) } describe('Session queue commands', () => { it('edits, removes, steers, and rejects stale queue occurrences', async () => { const { ctx, controller, agent, inbox, steer, cancel } = await commandHarness() const queued = createUserMessage({ content: [{ type: 'text', text: 'queued' }], source: { kind: 'user' } }) const nextStep = createUserMessage({ content: [{ type: 'text', text: 'step' }], source: { kind: 'user' } }) inbox.append('next-turn', queued) inbox.append('next-step', nextStep) await expectFailure(Promise.resolve().then(() => controller.updateQueue({ sessionId: agent.id, itemId: queued.id, action: { kind: 'edit', content: [{ type: 'image', attachment: { attachmentId: AttachmentId('att-edit'), mediaType: 'image/png', bytes: 1, width: 1, height: 1, }, }], }, })), 'attachment-error') await expectFailure(Promise.resolve().then(() => controller.updateQueue({ sessionId: SessionId('missing'), itemId: queued.id, action: { kind: 'remove' }, })), 'queue-item-not-found') await expectFailure(Promise.resolve().then(() => controller.updateQueue({ sessionId: agent.id, itemId: MessageId('missing'), action: { kind: 'remove' }, })), 'queue-item-not-found') await expectFailure(Promise.resolve().then(() => controller.updateQueue({ sessionId: agent.id, itemId: nextStep.id, action: { kind: 'steer' }, })), 'steer-unavailable') Object.assign(agent, { status: 'idle' }) await expectFailure(Promise.resolve().then(() => controller.updateQueue({ sessionId: agent.id, itemId: queued.id, action: { kind: 'steer' }, })), 'steer-unavailable') expect(controller.updateQueue({ sessionId: agent.id, itemId: queued.id, action: { kind: 'edit', content: [{ type: 'text', text: 'edited' }] }, })).toEqual({ accepted: true }) expect(inbox.nextTurn[0]?.content).toEqual([{ type: 'text', text: 'edited' }]) expect(controller.updateQueue({ sessionId: agent.id, itemId: nextStep.id, action: { kind: 'remove' }, })).toEqual({ accepted: true }) Object.assign(agent, { status: 'running' }) const steered = inbox.nextTurn[0] if (steered === undefined) throw new Error('missing edited queue item') expect(controller.updateQueue({ sessionId: agent.id, itemId: steered.id, action: { kind: 'steer' }, })).toEqual({ accepted: true }) expect(steer).toHaveBeenCalledWith(steered) await expectFailure(Promise.resolve().then(() => controller.cancel({ sessionId: SessionId('missing'), })), 'session-not-found') expect(controller.cancel({ sessionId: agent.id })).toEqual({ accepted: true }) expect(cancel).toHaveBeenCalledWith({ kind: 'user' }, { keepInbox: true }) await ctx.fiber.dispose() }) }) function imageRef(id: string): ImageAttachmentRef { return { attachmentId: AttachmentId(id), mediaType: 'image/png', bytes: 1, width: 1, height: 1, } } function event(type: string, seq: number, data: unknown): SessionEvent { return { type, seq, time: seq + 1, data } as SessionEvent } async function persistedController( events: SessionEvent[], readImage: (ref: ImageAttachmentRef) => Promise<{ ref: ImageAttachmentRef; data: Uint8Array }>, ): Promise<{ ctx: Context; controller: SessionCommandController; sessionId: SessionId }> { const ctx = new Context() await ctx.plugin(SessionStore) const sessionId = SessionId('cold-attachment') const meta: SessionHeader = { version: 0, id: sessionId, createdAt: 1, cwd: '/workspace' } ctx.provide('sessionPersistence', testSessionPersistence(ctx, { list: () => Promise.resolve([meta]), inspect: () => Promise.resolve({ meta, events }), }) as never) installSessionReadTestServices(ctx) ctx.provide('attachments', { readImage } as never) const agents = { resolveAgent: vi.fn() } as unknown as ApiSessionAgentController return { ctx, controller: new SessionCommandController(ctx, agents, '/workspace'), sessionId } } describe('Session attachment authorization', () => { it('finds references in direct, message, inserted, nested, and streamed content', async () => { const nested = imageRef('nested') const message = imageRef('message') const inserted = imageRef('inserted') const streamed = imageRef('streamed') const events = [ event('fixture/direct', 0, { content: [null, [], { type: 'tool-result', content: [{ type: 'text', text: 'none' }] }, { type: 'tool-result', content: [{ type: 'image', attachment: nested }], }], }), { ...event('assistant/message', 1, { turn: 1, step: 1, message: createAssistantMessage({ content: [{ type: 'image', attachment: message }], source: { provider: 'fixture', model: 'fixture' }, }), }), surfaceOp: 'append' as const }, event('agent/inbox/spliced', 2, { target: 'next-turn', start: 0, inserted: [createUserMessage({ content: [{ type: 'image', attachment: inserted }], source: { kind: 'user' }, })], }), event('assistant/chunk', 3, { turn: 1, step: 1, chunk: { type: 'block-end', index: 0, block: { type: 'image', attachment: streamed } }, }), ] const readImage = vi.fn((ref: ImageAttachmentRef) => Promise.resolve({ ref, data: Uint8Array.of(1) })) const { ctx, controller, sessionId } = await persistedController(events, readImage) for (const ref of [nested, message, inserted, streamed]) { await expect(controller.attachment({ sessionId, attachmentId: ref.attachmentId })) .resolves.toEqual({ attachment: ref, data: 'AQ==' }) } expect(readImage).toHaveBeenCalledTimes(4) await ctx.fiber.dispose() }) it('maps missing persistence identities and attachment backend failures', async () => { const noPersistence = new Context() await noPersistence.plugin(SessionStore) installSessionReadTestServices(noPersistence) const noPersistenceController = new SessionCommandController( noPersistence, { resolveAgent: vi.fn() } as unknown as ApiSessionAgentController, '/workspace', ) await expectFailure(noPersistenceController.attachment({ sessionId: SessionId('missing'), attachmentId: AttachmentId('att'), }), 'session-not-found') const missing = new Context() await missing.plugin(SessionStore) missing.provide('sessionPersistence', testSessionPersistence(missing, { list: () => Promise.resolve([]), inspect: vi.fn(), }) as never) installSessionReadTestServices(missing) const missingController = new SessionCommandController( missing, { resolveAgent: vi.fn() } as unknown as ApiSessionAgentController, '/workspace', ) await expectFailure(missingController.attachment({ sessionId: SessionId('missing'), attachmentId: 'att' as never, }), 'session-not-found') for (const thrown of [ new AttachmentError('stored image is unavailable', 'ATTACHMENT_NOT_FOUND'), new Error('backend offline'), ]) { const ref = imageRef(`failure-${thrown.name}`) const fixture = await persistedController( [event('fixture/content', 0, { content: [{ type: 'image', attachment: ref }] })], () => Promise.reject(thrown), ) await expectFailure(fixture.controller.attachment({ sessionId: fixture.sessionId, attachmentId: ref.attachmentId, }), thrown instanceof AttachmentError ? 'attachment-error' : 'internal') await fixture.ctx.fiber.dispose() } }) it('maps a cold observation failure to an internal authorization error', async () => { const ctx = new Context() await ctx.plugin(SessionStore) installSessionReadTestServices(ctx) vi.spyOn(ctx.sessionQuery, 'observeSession').mockRejectedValue(new Error('storage offline')) const controller = new SessionCommandController( ctx, { resolveAgent: vi.fn() } as unknown as ApiSessionAgentController, '/workspace', ) await expectFailure(controller.attachment({ sessionId: SessionId('unreadable'), attachmentId: AttachmentId('att'), }), 'internal') await ctx.fiber.dispose() }) })