fix: preserve message occurrence contracts

This commit is contained in:
_Kerman
2026-07-28 14:44:15 +08:00
parent 384535f0ae
commit 0225d598e3
7 changed files with 68 additions and 18 deletions

View File

@@ -255,7 +255,7 @@ declare module 'cordis' {
'agent/inbox/dequeue'(this: Scoped<Agent>, agent: Agent, message: UserMessage): void
/**
* Pending inbox items were dropped without delivering them, so every
* enqueued id receives exactly one terminal `agent/inbox/dequeue` OR
* enqueue occurrence receives exactly one terminal `agent/inbox/dequeue` OR
* `agent/inbox/discard`. `cancel()` without `keepInbox`, including disposal,
* emits this after `agent/cancel-requested` when applicable and before
* aborting the active work. Fires once per drop with every dropped item.

View File

@@ -224,8 +224,16 @@ function assertToolResultRewrite(
}
const originalRest = { ...original.data } as Record<string, unknown>
const replacementRest = { ...event.data } as Record<string, unknown>
originalRest['message'] = { ...original.data.message, content: null }
replacementRest['message'] = { ...event.data.message, content: null }
const originalResult = original.data.message.content[0]
const replacementResult = event.data.message.content[0]
originalRest['message'] = {
...original.data.message,
content: [{ ...originalResult, content: null }],
}
replacementRest['message'] = {
...event.data.message,
content: [{ ...replacementResult, content: null }],
}
if (!isDeepEqualJson(originalRest, replacementRest)) {
throw new Error('tool/result surface replacement may change only content')
}

View File

@@ -171,6 +171,30 @@ describe('foldSurface tool-result rewrites', () => {
expect(() => foldSurface(events)).toThrow(/may change only content/)
})
it.each([
['toolCallId', { toolCallId: CallId('changed') }],
['isError', { isError: true }],
] as const)('rejects a replacement that changes the result block %s', (_field, patch) => {
const original = toolResultEvent(0, 'original')
const data = original.data as Extract<SessionEvent, { type: 'tool/result' }>['data']
const result = data.message.content[0]
const replacement = {
...original,
seq: 1,
time: 1,
data: {
...data,
message: freezeMessage({
...data.message,
content: [{ ...result, ...patch }] as [typeof result],
}),
},
surfaceOp: { op: 'replace', start: 0, end: 0 },
sourceEventSeqs: [0],
} as SessionEvent
expect(() => foldSurface([original, replacement])).toThrow(/may change only content/)
})
it('compares array-valued rest fields structurally (meta arrays: equal accepted, drifted rejected)', () => {
const withMeta = (seq: number, meta: unknown, surfaceOp: SurfaceEvent['surfaceOp'] = 'append', sourceEventSeqs?: number[]): SessionEvent => {
const event = toolResultEvent(seq, 'c-meta', surfaceOp, sourceEventSeqs)