diff --git a/ui-tui/src/__tests__/createGatewayEventHandler.test.ts b/ui-tui/src/__tests__/createGatewayEventHandler.test.ts index e242e5bdd0..23f7c46465 100644 --- a/ui-tui/src/__tests__/createGatewayEventHandler.test.ts +++ b/ui-tui/src/__tests__/createGatewayEventHandler.test.ts @@ -1,9 +1,9 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' import { createGatewayEventHandler } from '../app/createGatewayEventHandler.js' -import { resetOverlayState } from '../app/overlayStore.js' +import { getOverlayState, resetOverlayState } from '../app/overlayStore.js' import { turnController } from '../app/turnController.js' -import { resetTurnState } from '../app/turnStore.js' +import { getTurnState, resetTurnState } from '../app/turnStore.js' import { patchUiState, resetUiState } from '../app/uiStore.js' import { estimateTokensRough } from '../lib/text.js' import type { Msg } from '../types.js' @@ -273,4 +273,42 @@ describe('createGatewayEventHandler', () => { role: 'system' }) }) + + it('keeps gateway noise informational and approval out of Activity', async () => { + const appended: Msg[] = [] + const ctx = buildCtx(appended) + ctx.gateway.rpc = vi.fn(async () => { + throw new Error('cold start') + }) + + const onEvent = createGatewayEventHandler(ctx) + + onEvent({ payload: { line: 'Traceback: noisy but non-fatal' }, type: 'gateway.stderr' } as any) + onEvent({ payload: { preview: 'bad framing' }, type: 'gateway.protocol_error' } as any) + onEvent({ + payload: { command: 'rm -rf /tmp/nope', description: 'dangerous command' }, + type: 'approval.request' + } as any) + onEvent({ payload: {}, type: 'gateway.ready' } as any) + + await Promise.resolve() + await Promise.resolve() + + expect(getOverlayState().approval).toMatchObject({ description: 'dangerous command' }) + expect(getTurnState().activity).toMatchObject([ + { text: 'Traceback: noisy but non-fatal', tone: 'info' }, + { text: 'protocol noise detected · /logs to inspect', tone: 'info' }, + { text: 'protocol noise: bad framing', tone: 'info' }, + { text: 'command catalog unavailable: cold start', tone: 'info' } + ]) + }) + + it('still surfaces terminal turn failures as errors', () => { + const appended: Msg[] = [] + const onEvent = createGatewayEventHandler(buildCtx(appended)) + + onEvent({ payload: { message: 'boom' }, type: 'error' } as any) + + expect(getTurnState().activity).toMatchObject([{ text: 'boom', tone: 'error' }]) + }) }) diff --git a/ui-tui/src/app/createGatewayEventHandler.ts b/ui-tui/src/app/createGatewayEventHandler.ts index 35c412f6bb..e5324e4605 100644 --- a/ui-tui/src/app/createGatewayEventHandler.ts +++ b/ui-tui/src/app/createGatewayEventHandler.ts @@ -11,7 +11,6 @@ import { patchOverlayState } from './overlayStore.js' import { turnController } from './turnController.js' import { getUiState, patchUiState } from './uiStore.js' -const ERRLIKE_RE = /\b(error|traceback|exception|failed|spawn)\b/i const NO_PROVIDER_RE = /\bNo (?:LLM|inference) provider configured\b/i const statusFromBusy = () => (getUiState().busy ? 'running…' : 'ready') @@ -111,7 +110,7 @@ export function createGatewayEventHandler(ctx: GatewayEventHandlerContext): (ev: turnController.pushActivity(String(r.warning), 'warn') } }) - .catch((e: unknown) => turnController.pushActivity(`command catalog unavailable: ${rpcErrorMessage(e)}`, 'warn')) + .catch((e: unknown) => turnController.pushActivity(`command catalog unavailable: ${rpcErrorMessage(e)}`, 'info')) if (!STARTUP_RESUME_ID) { patchUiState({ status: 'forging session…' }) @@ -201,7 +200,7 @@ export function createGatewayEventHandler(ctx: GatewayEventHandlerContext): (ev: case 'gateway.stderr': { const line = String(ev.payload.line).slice(0, 120) - turnController.pushActivity(line, ERRLIKE_RE.test(line) ? 'error' : 'warn') + turnController.pushActivity(line, 'info') return } @@ -222,11 +221,11 @@ export function createGatewayEventHandler(ctx: GatewayEventHandlerContext): (ev: if (!turnController.protocolWarned) { turnController.protocolWarned = true - turnController.pushActivity('protocol noise detected · /logs to inspect', 'warn') + turnController.pushActivity('protocol noise detected · /logs to inspect', 'info') } if (ev.payload?.preview) { - turnController.pushActivity(`protocol noise: ${String(ev.payload.preview).slice(0, 120)}`, 'warn') + turnController.pushActivity(`protocol noise: ${String(ev.payload.preview).slice(0, 120)}`, 'info') } return @@ -299,7 +298,6 @@ export function createGatewayEventHandler(ctx: GatewayEventHandlerContext): (ev: const description = String(ev.payload.description ?? 'dangerous command') patchOverlayState({ approval: { command: String(ev.payload.command ?? ''), description } }) - turnController.pushActivity(`approval needed · ${description}`, 'warn') setStatus('approval needed') return