Merge latest master into PR 555
# Conflicts: # docs/architecture.i18n.yaml # docs/core-data-structures/core.i18n.yaml # examples/acp-agent/tests/snapshots/cordis-inspect-jsdoc/session.jsonl # packages/client/runtime/README.i18n.yaml # packages/client/ui-conversation/README.i18n.yaml # packages/client/ui-conversation/src/client/chat/MessageItem.tsx # packages/compact/compact-basic/README.i18n.yaml # packages/ui/tui/README.i18n.yaml
This commit is contained in:
@@ -85,6 +85,68 @@ describe('inspectRequests', () => {
|
||||
expect(snapshot.callSchemas.get('call-1')?.name).toBe('read')
|
||||
})
|
||||
|
||||
it('preserves a standalone compaction owner without widening assistant turns', () => {
|
||||
const snapshot = inspectRequests(entriesOf([
|
||||
at(0, 'compact/start', { turn: null }),
|
||||
at(1, 'compact/summary', {
|
||||
summary: [{ type: 'text', text: 'standalone summary' }],
|
||||
provider: 'fake',
|
||||
model: 'compact-model',
|
||||
}),
|
||||
at(2, 'compact/end', { turn: null }),
|
||||
at(3, 'step/start', { turn: 2, step: 1 }),
|
||||
]))
|
||||
|
||||
const [compaction, assistant] = snapshot.requests
|
||||
expect(compaction).toMatchObject({
|
||||
purpose: 'compaction',
|
||||
turn: null,
|
||||
step: 0,
|
||||
status: 'complete',
|
||||
})
|
||||
expect(assistant).toMatchObject({
|
||||
purpose: 'assistant',
|
||||
turn: 2,
|
||||
step: 1,
|
||||
status: 'running',
|
||||
})
|
||||
if (assistant?.purpose === 'assistant') {
|
||||
const turn: number = assistant.turn
|
||||
expect(turn).toBe(2)
|
||||
}
|
||||
})
|
||||
|
||||
it('interrupts an orphaned compaction at end-seed before projecting a new attempt', () => {
|
||||
const snapshot = inspectRequests(entriesOf([
|
||||
at(0, 'compact/start', { turn: null }),
|
||||
at(1, 'session/end-seed', {}),
|
||||
at(2, 'compact/start', { turn: null }),
|
||||
at(3, 'compact/summary', {
|
||||
summary: [{ type: 'text', text: 'replacement summary' }],
|
||||
provider: 'fake',
|
||||
model: 'compact-model',
|
||||
}),
|
||||
at(4, 'compact/end', { turn: null }),
|
||||
]))
|
||||
|
||||
expect(snapshot.requests).toMatchObject([
|
||||
{
|
||||
purpose: 'compaction',
|
||||
startSeq: 0,
|
||||
status: 'error',
|
||||
completedAt: 1_700_000_000_001,
|
||||
error: 'Compaction was interrupted before completion.',
|
||||
},
|
||||
{
|
||||
purpose: 'compaction',
|
||||
startSeq: 2,
|
||||
status: 'complete',
|
||||
completedAt: 1_700_000_000_004,
|
||||
summary: [{ type: 'text', text: 'replacement summary' }],
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
it('captures schemas for nested tool dispatches from the active request header', () => {
|
||||
const snapshot = inspectRequests(entriesOf([
|
||||
at(0, 'request/header', {
|
||||
@@ -159,6 +221,33 @@ describe('inspectRequests', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('keeps provider credential fragments out of projected request errors', () => {
|
||||
const snapshot = inspectRequests(entriesOf([
|
||||
at(0, 'step/start', { turn: 1, step: 1 }),
|
||||
at(1, 'turn/end', {
|
||||
turn: 1,
|
||||
reason: {
|
||||
kind: 'error',
|
||||
step: 1,
|
||||
failure: {
|
||||
code: 'AUTH',
|
||||
message: 'Authentication Fails, Your api key: sk-preview-secret is invalid',
|
||||
},
|
||||
},
|
||||
}),
|
||||
at(2, 'step/start', { turn: 2, step: 1 }),
|
||||
at(3, 'turn/end', {
|
||||
turn: 2,
|
||||
reason: { kind: 'error', step: 1, message: 'plugin exploded' },
|
||||
}),
|
||||
]))
|
||||
|
||||
expect(snapshot.requests).toMatchObject([
|
||||
{ status: 'error', error: 'API key is invalid' },
|
||||
{ status: 'error', error: 'plugin exploded' },
|
||||
])
|
||||
})
|
||||
|
||||
it('treats a scrubbed durable-fixture tool catalog as unavailable', () => {
|
||||
const snapshot = inspectRequests(entriesOf([
|
||||
at(0, 'step/start', { turn: 1, step: 1 }),
|
||||
@@ -179,6 +268,7 @@ describe('inspectRequests', () => {
|
||||
]))
|
||||
|
||||
expect(snapshot.callSchemas).toEqual(new Map())
|
||||
expect(snapshot.requests[0]?.prompt?.tools).toEqual([])
|
||||
const [request] = snapshot.requests
|
||||
expect(request?.purpose === 'assistant' ? request.prompt?.tools : undefined).toEqual([])
|
||||
})
|
||||
})
|
||||
|
||||
@@ -211,6 +211,7 @@ describe('live event path', () => {
|
||||
for (const event of retryTurn.slice(7)) feed(event)
|
||||
snapshot = session.getSnapshot()
|
||||
expect(snapshot.nodes.slice(-2).map(node => node.kind)).toEqual(['model-retry', 'assistant'])
|
||||
expect(snapshot.nodes.some(node => node.kind === 'turn-error')).toBe(false)
|
||||
expect(snapshot.nodes.at(-2)).toMatchObject({ kind: 'model-retry', retryState: 'started' })
|
||||
expect(snapshot.nodes.at(-1)).toMatchObject({ kind: 'assistant', blocks: [{ kind: 'text', text: '完整回复' }] })
|
||||
|
||||
@@ -221,6 +222,50 @@ describe('live event path', () => {
|
||||
expect(replay.session.getSnapshot().partial).toBeNull()
|
||||
})
|
||||
|
||||
it('projects unretried terminal failures at turn/end and reproduces them from history', async () => {
|
||||
const { session } = await opened()
|
||||
const feed = (event: SessionEvent) => {
|
||||
session.handleMuxEnvelope('r' as never, { type: 'session/event', sessionId: SID, event })
|
||||
}
|
||||
const failedTurns = [
|
||||
ev.turnStart(6, 1),
|
||||
ev.user(7, '鉴权失败'),
|
||||
at(8, {
|
||||
type: 'turn/end',
|
||||
data: {
|
||||
turn: 1,
|
||||
reason: {
|
||||
kind: 'error',
|
||||
step: 0,
|
||||
failure: {
|
||||
code: 'AUTH',
|
||||
message: 'Authentication Fails, Your api key: sk-preview-secret is invalid',
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
ev.turnStart(9, 2),
|
||||
ev.user(10, '内部失败'),
|
||||
at(11, {
|
||||
type: 'turn/end',
|
||||
data: { turn: 2, reason: { kind: 'error', step: 1, message: 'plugin exploded' } },
|
||||
}),
|
||||
]
|
||||
for (const event of failedTurns) feed(event)
|
||||
|
||||
const errors = session.getSnapshot().nodes.filter(node => node.kind === 'turn-error')
|
||||
expect(errors).toMatchObject([
|
||||
{ seq: 8, turn: 1, step: 0, code: 'AUTH', message: 'API key is invalid' },
|
||||
{ seq: 11, turn: 2, step: 1, message: 'plugin exploded' },
|
||||
])
|
||||
expect('code' in errors[1]!).toBe(false)
|
||||
|
||||
const replay = makeSession()
|
||||
replay.api.onHistory = () => histResponse([...plainTurn(0, 0, 'a', 'b'), ...failedTurns])
|
||||
await replay.session.open()
|
||||
expect(replay.session.getSnapshot().nodes).toEqual(session.getSnapshot().nodes)
|
||||
})
|
||||
|
||||
it('rejects retry payloads outside the producer contract without retracting the current partial', async () => {
|
||||
const { session } = await opened()
|
||||
const feed = (event: SessionEvent) => { session.handleMuxEnvelope('r' as never, { type: 'session/event', sessionId: SID, event }) }
|
||||
|
||||
Reference in New Issue
Block a user