mirror of
https://github.com/deepseek-ai/deepseek-harness
synced 2026-08-15 21:04:50 +00:00
test(queue): close CI coverage gaps
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -467,6 +467,7 @@ describe('fixture session face', () => {
|
||||
await runtime.sessions.add({ id: 's1' })
|
||||
const bare = runtime.sessions.behavior('s1')
|
||||
expect(() => bare.prompt()).toThrow(/prompt is not stubbed/)
|
||||
expect(() => bare.updateQueue()).toThrow(/updateQueue is not stubbed/)
|
||||
expect(() => bare.cancel()).toThrow(/cancel is not stubbed/)
|
||||
expect(() => bare.command()).toThrow(/command is not stubbed/)
|
||||
expect(() => bare.loadOlder()).toThrow(/loadOlder is not stubbed/)
|
||||
|
||||
@@ -146,10 +146,12 @@ export class ReactLoopAgent implements Agent {
|
||||
if (queuedIndex === -1 && outboxIndex === -1) return 'not-found'
|
||||
|
||||
const pending = queuedIndex === -1 ? this.outbox[outboxIndex] : this.queued[queuedIndex]
|
||||
/* v8 ignore next 2 -- indices are derived from these arrays in this synchronous method. */
|
||||
if (pending === undefined || pending.item === undefined) {
|
||||
throw new Error(`agent "${this.id}" inbox index changed during synchronous update`)
|
||||
}
|
||||
|
||||
/* v8 ignore next -- InboxAction is a closed discriminated union; all variants are covered below. */
|
||||
switch (action.kind) {
|
||||
case 'edit': {
|
||||
const item: InboxItem = Object.freeze({
|
||||
@@ -158,10 +160,12 @@ export class ReactLoopAgent implements Agent {
|
||||
})
|
||||
if (queuedIndex !== -1) {
|
||||
const queued = this.queued[queuedIndex]
|
||||
/* v8 ignore next -- the index was resolved from this array without an async boundary. */
|
||||
if (queued === undefined) throw new Error(`agent "${this.id}" queued item disappeared during edit`)
|
||||
this.queued[queuedIndex] = { ...queued, item }
|
||||
} else {
|
||||
const outbox = this.outbox[outboxIndex]
|
||||
/* v8 ignore next -- the index was resolved from this array without an async boundary. */
|
||||
if (outbox === undefined) throw new Error(`agent "${this.id}" steering item disappeared during edit`)
|
||||
this.outbox[outboxIndex] = { ...outbox, message: item.message, item }
|
||||
}
|
||||
@@ -177,11 +181,13 @@ export class ReactLoopAgent implements Agent {
|
||||
case 'promote': {
|
||||
if (queuedIndex !== -1) {
|
||||
const queued = this.queued.splice(queuedIndex, 1)[0]
|
||||
/* v8 ignore next -- the index was resolved from this array without an async boundary. */
|
||||
if (queued === undefined) throw new Error(`agent "${this.id}" queued item disappeared during promotion`)
|
||||
this.queued.unshift({ item: queued.item, wakeup: true })
|
||||
this.scheduleKick()
|
||||
} else {
|
||||
const outbox = this.outbox.splice(outboxIndex, 1)[0]
|
||||
/* v8 ignore next -- the index was resolved from this array without an async boundary. */
|
||||
if (outbox === undefined) throw new Error(`agent "${this.id}" steering item disappeared during promotion`)
|
||||
this.outbox.unshift(outbox)
|
||||
}
|
||||
@@ -189,6 +195,7 @@ export class ReactLoopAgent implements Agent {
|
||||
return 'applied'
|
||||
}
|
||||
default:
|
||||
/* v8 ignore next -- InboxAction is a closed discriminated union. */
|
||||
return assertNever(action)
|
||||
}
|
||||
}
|
||||
@@ -710,6 +717,7 @@ export class ReactLoopAgent implements Agent {
|
||||
for (const item of this.outbox.splice(0, limit)) {
|
||||
if (item.steering) {
|
||||
steered = true
|
||||
/* v8 ignore next -- only inbox-backed steer entries carry steering:true. */
|
||||
if (item.item === undefined) throw new Error(`agent "${this.id}" steering outbox item has no inbox identity`)
|
||||
emitAgentEvent(this.loopCtx, this, 'agent/inbox/dequeue', item.item)
|
||||
this.session.append(
|
||||
|
||||
@@ -124,6 +124,63 @@ describe('addressable inbox operations', () => {
|
||||
.toEqual(['first', 'promote me', 'edited'])
|
||||
expect(agent.updateInbox(promote.id, { kind: 'remove' })).toBe('not-found')
|
||||
})
|
||||
|
||||
it('edits, removes, and promotes steering occurrences before admission commits', async () => {
|
||||
const adapter = new MockAdapter([textResponse('done')])
|
||||
const ctx = await harness(adapter)
|
||||
const agent = ctx.agentLoop.create(SessionId('steering-inbox-actions'), { provider: 'mock', model: 'mock' })
|
||||
const entered = Promise.withResolvers<undefined>()
|
||||
const decision = Promise.withResolvers<{ kind: 'allow' }>()
|
||||
ctx.on('agent/prompt-submit', async () => {
|
||||
entered.resolve(undefined)
|
||||
return decision.promise
|
||||
})
|
||||
|
||||
const pending: InboxItem[] = []
|
||||
const updates: { id: string; action: string; text: string }[] = []
|
||||
const discards: string[][] = []
|
||||
ctx.on('agent/inbox/enqueue', (subject, item) => {
|
||||
if (subject === agent && item.placement === 'steering') pending.push(item)
|
||||
})
|
||||
ctx.on('agent/inbox/update', (subject, item, action) => {
|
||||
if (subject === agent) updates.push({ id: item.id, action, text: inboxText(item) })
|
||||
})
|
||||
ctx.on('agent/inbox/discard', (subject, items) => {
|
||||
if (subject === agent) discards.push(items.map(item => item.id))
|
||||
})
|
||||
|
||||
const idle = waitForIdle(ctx, agent)
|
||||
send(agent, 'admitted prompt')
|
||||
await entered.promise
|
||||
agent.steer(createUserMessage({ content: [{ type: 'text', text: 'remove me' }], source: { kind: 'user' } }))
|
||||
agent.steer(createUserMessage({ content: [{ type: 'text', text: 'edit me' }], source: { kind: 'user' } }))
|
||||
agent.steer(createUserMessage({ content: [{ type: 'text', text: 'promote me' }], source: { kind: 'user' } }))
|
||||
expect(pending.map(inboxText)).toEqual(['remove me', 'edit me', 'promote me'])
|
||||
|
||||
const remove = pending[0]!
|
||||
const edit = pending[1]!
|
||||
const promote = pending[2]!
|
||||
expect(agent.updateInbox(edit.id, {
|
||||
kind: 'edit',
|
||||
content: [{ type: 'text', text: 'edited' }],
|
||||
})).toBe('applied')
|
||||
expect(agent.updateInbox(remove.id, { kind: 'remove' })).toBe('applied')
|
||||
expect(agent.updateInbox(promote.id, { kind: 'promote' })).toBe('applied')
|
||||
expect(updates).toEqual([
|
||||
{ id: edit.id, action: 'edit', text: 'edited' },
|
||||
{ id: promote.id, action: 'promote', text: 'promote me' },
|
||||
])
|
||||
expect(discards).toEqual([[remove.id]])
|
||||
|
||||
decision.resolve({ kind: 'allow' })
|
||||
await idle
|
||||
expect(agent.session.events
|
||||
.filter(event => event.type === 'steering/message')
|
||||
.map(event => event.type === 'steering/message'
|
||||
? event.data.message.content.flatMap(block => block.type === 'text' ? [block.text] : []).join('')
|
||||
: ''))
|
||||
.toEqual(['promote me', 'edited'])
|
||||
})
|
||||
})
|
||||
|
||||
describe('assistant replay provenance', () => {
|
||||
|
||||
@@ -59,6 +59,20 @@ describe('agent loop', () => {
|
||||
},
|
||||
)
|
||||
|
||||
it('seeds a valid AgentOptions.maxTokens into the first model request', async () => {
|
||||
const adapter = new MockAdapter([textResponse('bounded')])
|
||||
const ctx = await harness(adapter)
|
||||
const agent = ctx.agentLoop.create(
|
||||
SessionId('valid-max-tokens'),
|
||||
{ provider: 'mock', model: 'mock', maxTokens: 256 },
|
||||
)
|
||||
|
||||
send(agent, 'use the configured output limit')
|
||||
await waitForIdle(ctx, agent)
|
||||
|
||||
expect(adapter.requests[0]?.maxTokens).toBe(256)
|
||||
})
|
||||
|
||||
it('runs a simple turn: queued message → model → idle, with ordered events', async () => {
|
||||
const adapter = new MockAdapter([textResponse('hello there')])
|
||||
const ctx = await harness(adapter)
|
||||
|
||||
@@ -210,7 +210,7 @@ describe('unary round trip (handler ⇄ client, no network)', () => {
|
||||
if (!response.result.ok) expect(response.result.error.code).toBe('session-not-found')
|
||||
})
|
||||
|
||||
it('covers create/prompt/cancel/describe passthrough', async () => {
|
||||
it('covers create/prompt/updateQueue/cancel/describe passthrough', async () => {
|
||||
const c = client()
|
||||
expect((await c.sessions.create({})).result.ok).toBe(true)
|
||||
expect((await c.sessions.models({ sessionId: 's' as never })).result.ok).toBe(true)
|
||||
@@ -233,6 +233,11 @@ describe('unary round trip (handler ⇄ client, no network)', () => {
|
||||
const renamed = await c.sessions.rename({ sessionId: 's' as never, title: 'named' })
|
||||
expect(renamed.result).toMatchObject({ ok: true, value: { title: 'named', seq: 0 } })
|
||||
expect((await c.sessions.prompt({ sessionId: 's' as never, mode: 'queue', content: [{ type: 'text', text: 'x' }] })).result.ok).toBe(true)
|
||||
expect((await c.sessions.updateQueue({
|
||||
sessionId: 's' as never,
|
||||
itemId: 'item-1' as never,
|
||||
action: { kind: 'remove' },
|
||||
})).result.ok).toBe(true)
|
||||
expect((await c.sessions.cancel({ sessionId: 's' as never })).result.ok).toBe(true)
|
||||
expect((await c.host.describe({})).result.ok).toBe(true)
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user