refactor agent pre-step inbox lifecycle

This commit is contained in:
_Kerman
2026-07-31 19:21:16 +08:00
parent c2ff9ddec8
commit fcc2b5e282
267 changed files with 2052 additions and 1546 deletions
@@ -44,7 +44,7 @@ function agentForCwd(cwd: string): Agent {
id,
options: {},
session,
inbox: new Inbox(session),
inbox: new Inbox(session, { inserted: () => {}, discarded: () => {} }),
status: 'idle',
send: () => {},
followup: () => {},
@@ -60,7 +60,7 @@ function sessionAgent(session: Session, id = 'tool-skill-agent'): Agent {
id: SessionId(id),
options: {},
session,
inbox: new Inbox(session),
inbox: new Inbox(session, { inserted: () => {}, discarded: () => {} }),
status: 'running',
ctx: new Context(),
send: () => {},
@@ -81,7 +81,18 @@ function openMessageTurn(session: Session, turn = 1): void {
}
async function fireStep(ctx: Context, agent: Agent, turn: number, step: number): Promise<void> {
await agentEvents(ctx, agent).serial('agent/step', turn, step, new AbortController().signal)
const signal = new AbortController().signal
const decision = await agentEvents(ctx, agent).waterfall(
'agent/pre-step',
[],
{ turn, step, signal },
() => Promise.resolve({ kind: 'enter' as const, messages: [] }),
)
if (decision.kind === 'enter') {
for (const message of decision.messages) {
agent.session.append('user/message', message, { surfaceOp: 'append' })
}
}
}
function catalogMessages(session: Session): Extract<SessionEvent, { type: 'user/message' }>[] {
@@ -102,7 +113,17 @@ async function composePrefix(ctx: Context, cwd: string, signal = new AbortContro
}
async function composePrefixForAgent(ctx: Context, agent: Agent, signal = new AbortController().signal): Promise<Message[]> {
await agentEvents(ctx, agent).serial('agent/step', 1, 1, signal)
const decision = await agentEvents(ctx, agent).waterfall(
'agent/pre-step',
[],
{ turn: 1, step: 1, signal },
() => Promise.resolve({ kind: 'enter' as const, messages: [] }),
)
if (decision.kind === 'enter') {
for (const message of decision.messages) {
agent.session.append('user/message', message, { surfaceOp: 'append' })
}
}
return agent.session.deriveMessages()
}
@@ -197,16 +218,30 @@ describe('dsh-tool-skill', () => {
source: 'runtime',
content: 'User-only body.',
})
ctx.on('agent/step', (agent) => {
agent.session.append('user/message', createUserMessage({
content: [{ type: 'text', text: 'later contribution' }],
source: { kind: 'plugin', plugin: 'later-contribution' },
}), { surfaceOp: 'append' })
ctx.on('agent/pre-step', async (_agent, _messages, _context, next) => {
const decision = await next()
if (decision.kind === 'reject') return decision
return {
...decision,
messages: [
...decision.messages,
createUserMessage({
content: [{ type: 'text', text: 'later contribution' }],
source: { kind: 'plugin', plugin: 'later-contribution' },
}),
],
}
})
const prefix = await composePrefix(ctx, '/workspace')
expect(prefix).toEqual([
{
id: expect.any(String) as unknown,
role: 'user',
content: [{ type: 'text', text: 'later contribution' }],
source: { kind: 'plugin', plugin: 'later-contribution' },
},
{
id: expect.any(String) as unknown,
role: 'user',
@@ -228,14 +263,8 @@ describe('dsh-tool-skill', () => {
].join('\n'),
}],
},
{
id: expect.any(String) as unknown,
role: 'user',
content: [{ type: 'text', text: 'later contribution' }],
source: { kind: 'plugin', plugin: 'later-contribution' },
},
])
const rendered = JSON.stringify(prefix[0])
const rendered = JSON.stringify(prefix[1])
expect(rendered).not.toContain('whenToUse')
expect(rendered).not.toContain('secret-source')
expect(rendered).not.toContain('/secret/path')