refactor(agent-loop): drop the steering/message session event

Steer, inject, and followup now land as durable user/message events on the
session surface; the steering/message event type and its ConversationNode
kind are removed from the client projection. Update tests, docs, generated
catalogs, and agent notes to match, and align the steering e2e fixture and
prompt inventory assertions with the durable user/message landing.
This commit is contained in:
_Kerman
2026-08-03 16:04:26 +08:00
parent f4a2e0d10a
commit cb2f01f48c
86 changed files with 243 additions and 458 deletions

View File

@@ -75,7 +75,7 @@ const PREVIEW_OUTPUT_CHARACTERS = 512
type InputNode = Extract<
ConversationSnapshot['nodes'][number],
{ kind: 'user' | 'steering' | 'context' }
{ kind: 'user' | 'context' }
>
type OrderedLayoutEntry =
@@ -325,19 +325,17 @@ export function deriveTrajectoryLayout(input: TrajectoryLayoutInput): readonly T
continue
}
const { node, nodeIndex: i } = entry
if (node.kind === 'user' || node.kind === 'steering') {
if (node.kind === 'user') {
// user/message has no turn on the wire; enclose it in the next assistant
// (or partial) turn, else open the turn after the last assistant.
const turn = node.kind === 'steering'
? node.turn
: enclosingUserTurn(nodes, i, partial, lastAssistantTurn)
const turn = enclosingUserTurn(nodes, i, partial, lastAssistantTurn)
pushMessage(turn, {
absTime: finiteTime(node.time),
cell: {
index: ++index,
kind: 'user',
...inputCellDetail(node),
opensTurn: node.kind === 'user',
opensTurn: true,
},
})
prevAbsTime = finiteTime(node.time) ?? prevAbsTime
@@ -453,7 +451,7 @@ export function deriveTrajectoryLayout(input: TrajectoryLayoutInput): readonly T
else for (const laid of laidList) pushMessage(call.turn, laid)
}
// Orphan turn-0 cells (orphaned tools / steering turn 0) fold into Turn 1.
// Orphan turn-0 cells (orphaned tools) fold into Turn 1.
const prologue = turns.get(0)
if (prologue !== undefined) {
turns.delete(0)
@@ -733,7 +731,7 @@ function stringifySourceValue(value: unknown): string {
}
/**
* Turn that encloses a user/message: next assistant/steering turn, else the
* Turn that encloses a user/message: next assistant turn, else the
* in-flight partial, else the turn after the last finalized assistant (or 1).
*/
function enclosingUserTurn(
@@ -746,7 +744,7 @@ function enclosingUserTurn(
const n = nodes[i]
/* v8 ignore next -- dense-array guard: i stays within nodes.length, so the undefined arm needs a sparse array no caller builds. */
if (n === undefined) continue
if (n.kind === 'assistant' || n.kind === 'steering') return n.turn
if (n.kind === 'assistant') return n.turn
}
if (partial !== null) return partial.turn
if (lastAssistantTurn !== null) return lastAssistantTurn + 1
@@ -770,7 +768,7 @@ function firstVisibleTurn(
partial: ConversationSnapshot['partial'],
): number {
const turns = nodes.flatMap(node =>
(node.kind === 'assistant' || node.kind === 'steering') && node.turn > 0
node.kind === 'assistant' && node.turn > 0
? [node.turn]
: [],
)