refactor(agent-loop): simplify message machine

This commit is contained in:
_Kerman
2026-07-30 13:49:57 +08:00
parent d554ae3019
commit f2e20c1ef0
212 changed files with 1326 additions and 2382 deletions

View File

@@ -1,6 +1,7 @@
// Sessions remain resident after creation so they continue consuming mux frames off-screen.
import type { Context } from 'cordis'
import type { MessageId } from '@deepseek-ai/dsh-llm/brand'
import type { ContentBlock } from '@deepseek-ai/dsh-llm/types'
import type { SessionEvent } from '@deepseek-ai/dsh-session/types'
import type {
@@ -51,9 +52,8 @@ const QUEUE_PREVIEW_CHARS = 200
/** Internal inbox-mirror entry: the snapshot row plus the retirement-matching fields the frames carry. */
interface QueuedEntry {
row: QueuedMessage
steering: boolean
/** JSON-serialized MessageSource (steering retirement matches by source, the host-mirror precedent). */
sourceJson: string
/** Stable message identity used when an admitted message retires the row. */
messageId: MessageId
}
/** Single-line queue-row preview: text blocks flattened, non-text as tags, capped by code point. */
@@ -370,8 +370,7 @@ export class Session implements SessionFace {
const key = 'rpcId' in message.source ? String(message.source.rpcId) : `f:${rpcId}`
this.queued.push({
row: { key, preview: queuePreviewOf(message.content) },
steering: frame.steering,
sourceJson: JSON.stringify(message.source),
messageId: message.id,
})
this.queueRev++
this.notifier.markDirty()
@@ -598,21 +597,16 @@ export class Session implements SessionFace {
}
}
/** Consumption-event retirement, mirroring the host queuedMirror rules: a message-triggered
* turn/start claims the oldest non-steering entry; a steering/message drains the oldest
* steering entry with the same source (loop-authored steering matches nothing and drops none). */
/** Retire the oldest queued occurrence of an admitted identified message. */
private retireQueued(event: SessionEvent): void {
if (this.queued.length === 0) return
let index = -1
if (event.type === 'turn/start') {
if (event.data.trigger.kind !== 'message') return
index = this.queued.findIndex(entry => !entry.steering)
} else if (event.type === 'steering/message') {
const source = JSON.stringify(event.data.message.source)
index = this.queued.findIndex(entry => entry.steering && entry.sourceJson === source)
} else {
return
}
const id = event.type === 'user/message'
? event.data.id
: event.type === 'steering/message'
? event.data.message.id
: undefined
if (id === undefined) return
const index = this.queued.findIndex(entry => entry.messageId === id)
if (index < 0) return
this.queued.splice(index, 1)
this.queueRev++

View File

@@ -12,7 +12,7 @@ const at = (seq: number, e: Record<string, unknown>): SessionEvent =>
export const ev = {
turnStart: (seq: number, turn: number): SessionEvent =>
at(seq, { type: 'turn/start', data: { turn, trigger: { kind: 'message', source: { kind: 'user' } } } }),
at(seq, { type: 'turn/start', data: { turn } }),
user: (seq: number, body: string): SessionEvent =>
at(seq, { type: 'user/message', surfaceOp: 'append', data: createUserMessage({
content: text(body), source: { kind: 'user' },

View File

@@ -1,6 +1,6 @@
/**
* Queue mirror semantics (web input-triggers queue cut 1): session/queued
* intake, host-rule retirement (message turn/start claims oldest non-steering;
* intake, host-rule retirement (identified user/message claims its non-steering row;
* steering/message drains by source), leave-running sweep, reconnect reset,
* pre-instantiation buffering, and snapshot reference stability.
*/
@@ -18,7 +18,11 @@ const text = (t: string): ContentBlock[] => [{ type: 'text', text: t }]
const rid = (id: string): RpcId => id as RpcId
/** session/queued frame with the wire-sourced rpcId key (the host prompt path). */
function queuedFrame(body: string, rpcId: string, steering = false): MuxFrame {
function queuedFrame(
body: string,
rpcId: string,
steering = false,
): Extract<MuxFrame, { type: 'session/queued' }> {
return {
type: 'session/queued',
sessionId: SID,
@@ -74,22 +78,30 @@ describe('queue intake', () => {
})
describe('queue retirement (host queuedMirror rules)', () => {
it('a message-triggered turn/start claims the oldest non-steering row', () => {
it('an admitted user/message claims its identified non-steering row', () => {
const session = makeSession()
session.handleMuxEnvelope(rid('e1'), queuedFrame('先', 'p-1'))
const first = queuedFrame('先', 'p-1')
session.handleMuxEnvelope(rid('e1'), first)
session.handleMuxEnvelope(rid('e2'), queuedFrame('后', 'p-2'))
session.handleMuxEnvelope(rid('e3'), { type: 'session/event', sessionId: SID, event: ev.turnStart(0, 0) })
session.handleMuxEnvelope(rid('e3'), {
type: 'session/event',
sessionId: SID,
event: {
...ev.user(0, '先'),
data: first.message,
},
})
expect(session.getSnapshot().queue.map(r => r.key)).toEqual(['p-2'])
})
it('an injection-triggered turn/start claims nothing', () => {
it('a turn/start alone claims nothing', () => {
const session = makeSession()
session.handleMuxEnvelope(rid('e1'), queuedFrame('留', 'p-1'))
const injection = {
...ev.turnStart(0, 0),
data: { turn: 0, trigger: { kind: 'injection', source: { kind: 'plugin', plugin: 'x' } } },
} as never
session.handleMuxEnvelope(rid('e2'), { type: 'session/event', sessionId: SID, event: injection })
session.handleMuxEnvelope(rid('e2'), {
type: 'session/event',
sessionId: SID,
event: ev.turnStart(0, 0),
})
expect(session.getSnapshot().queue).toHaveLength(1)
})