refactor(agent-loop): simplify message machine
This commit is contained in:
@@ -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++
|
||||
|
||||
@@ -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' },
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user