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

@@ -30,7 +30,7 @@ const result = (overrides: Record<string, unknown> = {}) => ({
})
function startTurn(session: Session, turn = 1): void {
session.append('turn/start', { turn, trigger: { kind: 'message', source: { kind: 'user' } } })
session.append('turn/start', { turn })
}
describe('hook-protocol invariants', () => {
@@ -49,7 +49,7 @@ describe('hook-protocol invariants', () => {
const ctx = new Context()
await ctx.plugin(SessionStore)
const session = ctx.sessions.create()
session.append('turn/start', { turn: 1, trigger: { kind: 'message', source: { kind: 'user' } } })
session.append('turn/start', { turn: 1 })
session.append('hook/invoked', invoked())
await ctx.plugin(InvariantService)
await ctx.plugin(HookInvariant)
@@ -63,7 +63,7 @@ describe('hook-protocol invariants', () => {
expect(() => {
ctx.emit('session/event', session, {
type: 'turn/start', seq: 0, time: 0,
data: { turn: 1, trigger: { kind: 'message', source: { kind: 'user' } } },
data: { turn: 1 },
})
ctx.emit('session/event', session, {
type: 'hook/invoked', seq: 1, time: 1, data: invoked(),

View File

@@ -197,6 +197,11 @@ export function apply(ctx: Context, config: Config): void {
return [ours, ...theirs ?? []]
}
/** Append hook context to an admitted inbox batch. */
function appendPromptContext(theirs: UserMessage[], ours: UserMessage): UserMessage[] {
return [...theirs, ours]
}
// SessionStart injects context when its detached hook resolves; a slow hook
// may miss the first request.
// TODO(session-start-gating): add a startup gate before promising first-turn delivery.
@@ -213,8 +218,9 @@ export function apply(ctx: Context, config: Config): void {
// --- UserPromptSubmit → PromptDecision. The prompt text is the payload; no
// matcher subject (CC ignores matchers for this event). ---
ctx.on('agent/prompt-submit', async (agent, message, signal, next): Promise<PromptDecision> => {
const merged = await runPoint('UserPromptSubmit', '', promptPayload(ctx, agent, message.content), { agent, signal })
ctx.on('agent/prompt-submit', async (agent, messages, signal, next): Promise<PromptDecision> => {
const content = messages.flatMap(message => message.content)
const merged = await runPoint('UserPromptSubmit', '', promptPayload(ctx, agent, content), { agent, signal })
if (merged.decision === 'deny') {
return { kind: 'block', reason: merged.reason ?? 'blocked by UserPromptSubmit hook' }
}
@@ -225,8 +231,7 @@ export function apply(ctx: Context, config: Config): void {
if (!ours || downstream.kind !== 'allow') return downstream
return {
kind: 'allow',
...downstream.content !== undefined ? { content: downstream.content } : {},
additionalContexts: prependContext(ours, downstream.additionalContexts),
messages: appendPromptContext(downstream.messages, ours),
}
})

View File

@@ -182,6 +182,11 @@ export function apply(ctx: Context, config: Config): void {
return [ours, ...theirs ?? []]
}
/** Append hook context to an admitted inbox batch. */
function appendPromptContext(theirs: UserMessage[], ours: UserMessage): UserMessage[] {
return [...theirs, ours]
}
// SessionStart injects plain stdout when its detached hook resolves; a slow
// hook may miss the first request.
// TODO(session-start-gating): add a startup gate before promising first-turn delivery.
@@ -196,11 +201,11 @@ export function apply(ctx: Context, config: Config): void {
})
// UserPromptSubmit → PromptDecision. Codex supports block, not allow or ask.
ctx.on('agent/prompt-submit', async (agent, message, signal, next): Promise<PromptDecision> => {
ctx.on('agent/prompt-submit', async (agent, messages, signal, next): Promise<PromptDecision> => {
const payload = {
...base(ctx, agent, 'UserPromptSubmit', model),
turn_id: String(lastTurn(agent) + 1),
prompt: blocksToText(message.content),
prompt: blocksToText(messages.flatMap(message => message.content)),
}
const merged = await runPoint('UserPromptSubmit', '', payload, { agent, plainStdoutAsContext: true, signal })
/* jscpd:ignore-start */
@@ -212,8 +217,7 @@ export function apply(ctx: Context, config: Config): void {
if (!ours || downstream.kind !== 'allow') return downstream
return {
kind: 'allow',
...downstream.content !== undefined ? { content: downstream.content } : {},
additionalContexts: prependContext(ours, downstream.additionalContexts),
messages: appendPromptContext(downstream.messages, ours),
}
})