Files
deepseek-harness/packages/agent-loop/src/inbox.ts
T
Tianyi Cui 43f4258277 Implement the agent loop plugin
@deepseek-ai/dsh-agent-loop: LoopAgent (inbox with queued + steering
FIFOs, per-step AbortController) and the streaming-first
session/turn/step loop. Extension seams: agent/request,
agent/step-result, agent/turn-continuation waterfalls; raw chunks
logged for replay while BlockAssembler builds the assembled message;
steering drains between steps; session/flush awaited at turn end.

16 tests with a scripted mock adapter cover turn lifecycle ordering,
tool round-trips, steering, inject(), continuation override/veto,
mid-stream abort, queued turn chaining, replay equivalence, and
mid-turn fiber disposal (HMR safety).
2026-06-11 10:54:31 +08:00

58 lines
1.6 KiB
TypeScript

import type { ContentBlock, MessageSource } from '@deepseek-ai/dsh-llm'
/** One message waiting in an agent's inbox. */
export interface InboxMessage {
content: ContentBlock[]
source: MessageSource
}
/**
* Per-agent inbox: a queued FIFO (drained at turn start) and a steering FIFO
* (drained between steps of a running turn). Purely an in-memory mechanism of
* the loop — the public surface is `Agent.send()` / `Agent.steer()`.
*/
export class Inbox {
private queuedMessages: InboxMessage[] = []
private steeringMessages: InboxMessage[] = []
private wakeup: (() => void) | undefined
/** Resolves when a queued message arrives (used by the idle loop). */
get hasQueued(): boolean {
return this.queuedMessages.length > 0
}
get hasSteering(): boolean {
return this.steeringMessages.length > 0
}
enqueue(message: InboxMessage): void {
this.queuedMessages.push(message)
this.wakeup?.()
}
steer(message: InboxMessage): void {
this.steeringMessages.push(message)
}
/** Drain all queued messages (turn start). */
drainQueued(): InboxMessage[] {
return this.queuedMessages.splice(0)
}
/** Drain all steering messages (between steps). */
drainSteering(): InboxMessage[] {
return this.steeringMessages.splice(0)
}
/** Wait until a queued message arrives or `cancel` resolves. */
waitForQueued(cancel: Promise<void>): Promise<void> {
if (this.hasQueued) return Promise.resolve()
const { promise, resolve } = Promise.withResolvers<void>()
this.wakeup = resolve
void cancel.then(resolve)
return promise.finally(() => {
if (this.wakeup === resolve) this.wakeup = undefined
})
}
}