Merge latest invariant service seam

This commit is contained in:
Tianyi Cui
2026-07-20 20:29:24 +08:00
22 changed files with 329 additions and 81 deletions

View File

@@ -243,6 +243,41 @@ describe('createStdioChat rendering', () => {
expect(out.text()).toContain('\n> ')
})
it('renders failure turn/end reasons so a failed turn is not silent', async () => {
const { ctx, out } = await setup()
const session = makeSession('main')
ctx.emit('session/event', session, {
type: 'turn/end', seq: 1, time: 0,
data: { turn: 1, reason: { kind: 'error', step: 1, message: 'fetch failed: connect ECONNREFUSED', code: 'NETWORK' } },
} as SessionEvent)
expect(out.text()).toContain('[turn failed NETWORK] fetch failed: connect ECONNREFUSED')
ctx.emit('session/event', session, {
type: 'turn/end', seq: 2, time: 0,
data: { turn: 2, reason: { kind: 'error', step: 1, message: 'uncoded failure' } },
} as SessionEvent)
expect(out.text()).toContain('[turn failed] uncoded failure')
ctx.emit('session/event', session, {
type: 'turn/end', seq: 3, time: 0, data: { turn: 3, reason: { kind: 'aborted', reason: 'user cancelled' } },
} as SessionEvent)
expect(out.text()).toContain('[turn aborted] user cancelled')
ctx.emit('session/event', session, {
type: 'turn/end', seq: 4, time: 0, data: { turn: 4, reason: { kind: 'aborted' } },
} as SessionEvent)
expect(out.text()).toContain('[turn aborted]\n> ')
ctx.emit('session/event', session, {
type: 'turn/end', seq: 5, time: 0, data: { turn: 5, reason: { kind: 'rejected', reason: 'policy veto' } },
} as SessionEvent)
expect(out.text()).toContain('[turn rejected] policy veto')
ctx.emit('session/event', session, {
type: 'turn/end', seq: 6, time: 0, data: { turn: 6, reason: { kind: 'max-tokens' } },
} as SessionEvent)
expect(out.text()).toContain('[turn hit the output-token limit]')
ctx.emit('session/event', session, {
type: 'turn/end', seq: 7, time: 0, data: { turn: 7, reason: { kind: 'interrupted' } },
} as SessionEvent)
expect(out.text()).toContain('[turn interrupted by a previous process exit]')
})
it('uses the session id as the label for a non-target session', async () => {
const { ctx, out } = await setup()
// No target exists, so the event's durable identity is the label.
@@ -867,7 +902,7 @@ describe('createStdioChat input', () => {
await new Promise(r => setImmediate(r))
expect(error).toHaveBeenCalledWith(
'ui-stdio: main agent failed to start; dropped queued stdin (1 line(s)): <unrenderable thrown value>',
'ui-stdio: main agent failed to start; dropped queued stdin (1 line(s)): <unrenderable value>',
)
})
@@ -955,7 +990,7 @@ describe('createStdioChat EOF exit', () => {
await flushExit()
expect(error).toHaveBeenCalledWith(
'ui-stdio: main agent failed to start; dropped queued stdin (1 line(s)): <unrenderable thrown value>',
'ui-stdio: main agent failed to start; dropped queued stdin (1 line(s)): <unrenderable value>',
)
expect(exit).toHaveBeenCalledWith(0)
})