mirror of
https://github.com/wu736139669/hapi.git
synced 2026-08-08 07:17:39 +00:00
fix(cursor): surface agent errors with warning styling in web UI (#871)
* test: reproduce issue #864 Assert Cursor error paths emit agent error payloads and web UI renders them as warning-styled events instead of neutral session messages. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(cursor): surface agent errors with warning styling in web UI (closes #864) Route Cursor stderr, init, prompt, and legacy exit failures through sendAgentMessage({ type: 'error' }) and teach the web chat layer to render error events with a warning icon instead of neutral info text. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -178,6 +178,27 @@ describe('normalizeDecryptedMessage', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('normalizes agent error payloads as error events', () => {
|
||||
const normalized = normalizeDecryptedMessage(makeMessage({
|
||||
role: 'agent',
|
||||
content: {
|
||||
type: 'codex',
|
||||
data: {
|
||||
type: 'error',
|
||||
message: 'Cursor Agent failed: authentication required'
|
||||
}
|
||||
}
|
||||
}))
|
||||
|
||||
expect(normalized).toMatchObject({
|
||||
role: 'event',
|
||||
content: {
|
||||
type: 'error',
|
||||
message: 'Cursor Agent failed: authentication required'
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
it('treats non-sidechain string user output as sidechain', () => {
|
||||
const message = makeMessage({
|
||||
role: 'agent',
|
||||
|
||||
@@ -577,6 +577,21 @@ export function normalizeAgentRecord(
|
||||
}
|
||||
}
|
||||
|
||||
if (data.type === 'error' && typeof data.message === 'string') {
|
||||
return {
|
||||
id: messageId,
|
||||
localId,
|
||||
createdAt,
|
||||
role: 'event',
|
||||
content: {
|
||||
type: 'error',
|
||||
message: data.message
|
||||
},
|
||||
isSidechain: false,
|
||||
meta
|
||||
}
|
||||
}
|
||||
|
||||
if (data.type === 'message' && typeof data.message === 'string') {
|
||||
const review = parseCodexReviewMessage(data.message)
|
||||
if (review) {
|
||||
|
||||
@@ -1,6 +1,18 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { getEventPresentation, formatMessageTimestamp, formatResetTime } from './presentation'
|
||||
|
||||
describe('getEventPresentation — agent errors', () => {
|
||||
it('formats error events with warning icon and message text', () => {
|
||||
const result = getEventPresentation({
|
||||
type: 'error',
|
||||
message: 'Cursor Agent failed: authentication required'
|
||||
})
|
||||
|
||||
expect(result.icon).toBe('⚠️')
|
||||
expect(result.text).toBe('Cursor Agent failed: authentication required')
|
||||
})
|
||||
})
|
||||
|
||||
describe('getEventPresentation — limit-warning', () => {
|
||||
it('formats five_hour warning', () => {
|
||||
const result = getEventPresentation({
|
||||
|
||||
@@ -182,6 +182,9 @@ export function getEventPresentation(event: AgentEvent): EventPresentation {
|
||||
const suffix = typeLabel ? ` (${typeLabel})` : ''
|
||||
return { icon: '⏳', text: endsAt ? `Usage limit reached${suffix} until ${formatUnixTimestamp(endsAt)}` : `Usage limit reached${suffix}` }
|
||||
}
|
||||
if (event.type === 'error') {
|
||||
return { icon: '⚠️', text: typeof event.message === 'string' ? event.message : 'Error' }
|
||||
}
|
||||
if (event.type === 'message') {
|
||||
return { icon: null, text: typeof event.message === 'string' ? event.message : 'Message' }
|
||||
}
|
||||
|
||||
@@ -79,6 +79,18 @@ export function dedupeAgentEvents(blocks: ChatBlock[]): ChatBlock[] {
|
||||
continue
|
||||
}
|
||||
|
||||
if (event.type === 'error' && typeof event.message === 'string') {
|
||||
const message = event.message.trim()
|
||||
const key = `error:${message}`
|
||||
if (key === prevEventKey) {
|
||||
continue
|
||||
}
|
||||
result.push(block)
|
||||
prevEventKey = key
|
||||
prevTitleChangedTo = null
|
||||
continue
|
||||
}
|
||||
|
||||
let key: string
|
||||
try {
|
||||
key = `event:${JSON.stringify(event)}`
|
||||
|
||||
@@ -180,6 +180,15 @@ function normalizeTraceMessage(
|
||||
meta: source.meta
|
||||
}
|
||||
|
||||
if (data.type === 'error' && typeof data.message === 'string') {
|
||||
return [{
|
||||
...base,
|
||||
id: traceId,
|
||||
role: 'event',
|
||||
content: { type: 'error', message: data.message }
|
||||
} as TracedMessage]
|
||||
}
|
||||
|
||||
if (data.type === 'message' && typeof data.message === 'string') {
|
||||
return [{
|
||||
...base,
|
||||
|
||||
@@ -16,6 +16,7 @@ export type UsageData = {
|
||||
export type AgentEvent =
|
||||
| { type: 'switch'; mode: 'local' | 'remote' }
|
||||
| { type: 'message'; message: string }
|
||||
| { type: 'error'; message: string }
|
||||
| { type: 'title-changed'; title: string }
|
||||
| { type: 'limit-reached'; endsAt: number; limitType: string }
|
||||
| { type: 'limit-warning'; /** 0–1 ratio (e.g. 0.9 = 90%), integer-precision via CLI pipe format */ utilization: number; endsAt: number; limitType: string }
|
||||
|
||||
Reference in New Issue
Block a user