mirror of
https://github.com/stablyai/orca.git
synced 2026-11-03 18:47:40 +00:00
Show Codex model capacity failures as red agent status (#28231)
* Show Codex model capacity failures in agent status * Reduce Codex capacity detection work on terminal output * Preserve Codex capacity notices across partial terminal frames
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import type { ParsedAgentStatusPayload } from './agent-status-types'
|
||||
import { mainAgentStatusEqual, type ParsedAgentStatusPayload } from './agent-status-types'
|
||||
|
||||
/** Whether a terminal status is the same hook status with only hook-owned metadata omitted.
|
||||
*
|
||||
@@ -13,6 +13,8 @@ export function terminalStatusPayloadMatchesHook(
|
||||
// OSC never carries model/children; compare only fields its protocol owns.
|
||||
return (
|
||||
cachedHookPayload.state === terminalPayload.state &&
|
||||
(terminalPayload.mainAgent === undefined ||
|
||||
mainAgentStatusEqual(cachedHookPayload.mainAgent, terminalPayload.mainAgent)) &&
|
||||
// OSC never carries workingMode either; only a stated mismatch breaks equivalence.
|
||||
(terminalPayload.workingMode === undefined ||
|
||||
cachedHookPayload.workingMode === terminalPayload.workingMode) &&
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { CODEX_CAPACITY_MESSAGE, CodexCapacityNoticeTracker } from './codex-capacity-notice'
|
||||
|
||||
describe('Codex capacity candidate bounds', () => {
|
||||
it('reads neither the screen nor its mode during ordinary output', () => {
|
||||
const tracker = new CodexCapacityNoticeTracker()
|
||||
const readScreen = vi.fn(() => [])
|
||||
const readSynchronized = vi.fn(() => false)
|
||||
tracker.observe('ordinary output'.repeat(1000), readScreen, readSynchronized)
|
||||
expect(readScreen).not.toHaveBeenCalled()
|
||||
expect(readSynchronized).not.toHaveBeenCalled()
|
||||
})
|
||||
it('does not inspect a partial synchronized frame with combined ANSI modes', () => {
|
||||
const tracker = new CodexCapacityNoticeTracker()
|
||||
const readScreen = vi.fn(() => [`■ ${CODEX_CAPACITY_MESSAGE}`, '›'])
|
||||
expect(tracker.observe(`\x1b[?25;2026h■ ${CODEX_CAPACITY_MESSAGE}`, readScreen)).toBe(false)
|
||||
expect(readScreen).not.toHaveBeenCalled()
|
||||
expect(tracker.observe('\x1b[?2026;25l', readScreen)).toBe(true)
|
||||
expect(readScreen).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
it('ignores repeated unrelated markers and still accepts a later native notice', () => {
|
||||
const tracker = new CodexCapacityNoticeTracker()
|
||||
const readScreen = vi.fn(() => [`■ ${CODEX_CAPACITY_MESSAGE}`, '›'])
|
||||
expect(tracker.observe('■'.repeat(100_000), readScreen)).toBe(false)
|
||||
expect(readScreen).not.toHaveBeenCalled()
|
||||
expect(tracker.observe(`■ ${CODEX_CAPACITY_MESSAGE}`, readScreen)).toBe(true)
|
||||
expect(readScreen).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
it('finds the notice inside a large text chunk after unrelated warning markers', () => {
|
||||
const tracker = new CodexCapacityNoticeTracker()
|
||||
const readScreen = vi.fn(() => [`■ ${CODEX_CAPACITY_MESSAGE}`, '›'])
|
||||
expect(
|
||||
tracker.observe(
|
||||
`⚠ unrelated\n${'x'.repeat(1024 * 1024)}\n■ ${CODEX_CAPACITY_MESSAGE}`,
|
||||
readScreen
|
||||
)
|
||||
).toBe(true)
|
||||
expect(readScreen).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('recognizes a split notice immediately after abandoning an unrelated warning', () => {
|
||||
const tracker = new CodexCapacityNoticeTracker()
|
||||
const readScreen = vi.fn(() => [`⚠ ${CODEX_CAPACITY_MESSAGE}`, '›'])
|
||||
expect(tracker.observe('⚠ 1 warning · f2 to view', readScreen)).toBe(false)
|
||||
expect(readScreen).not.toHaveBeenCalled()
|
||||
expect(tracker.observe('⚠ Selected model is ', readScreen)).toBe(false)
|
||||
expect(tracker.observe('at capacity. Please try a different model.', readScreen)).toBe(true)
|
||||
expect(tracker.observe('ordinary output'.repeat(100_000), readScreen)).toBe(false)
|
||||
expect(readScreen).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,87 @@
|
||||
import { createTerminalEscapeScanner } from './terminal-escape-scanner'
|
||||
|
||||
export const CODEX_CAPACITY_MESSAGE = 'Selected model is at capacity. Please try a different model.'
|
||||
|
||||
const NOTICE = /^[■⚠]\s+Selected model is at capacity\. Please try a different model\.$/
|
||||
const MAX_NOTICE_LENGTH = 160
|
||||
|
||||
/** Only a newly painted native notice at the end of the conversation can close a turn. */
|
||||
export class CodexCapacityNoticeTracker {
|
||||
private tail = ''
|
||||
private pending = false
|
||||
private synchronized = false
|
||||
private readonly scanner = createTerminalEscapeScanner({
|
||||
onCsi: (marker, params, final) => {
|
||||
if (
|
||||
marker === '?' &&
|
||||
params.split(';').includes('2026') &&
|
||||
(final === 'h' || final === 'l')
|
||||
) {
|
||||
this.synchronized = final === 'h'
|
||||
} else if ((final === 'H' || final === 'f') && this.tail && !this.tail.endsWith(' ')) {
|
||||
this.tail += ' '
|
||||
}
|
||||
},
|
||||
onText: (text) => this.observeText(text)
|
||||
})
|
||||
|
||||
private observeText(text: string): void {
|
||||
if (this.tail) {
|
||||
this.updateCandidate(this.tail + text.slice(0, MAX_NOTICE_LENGTH))
|
||||
}
|
||||
let error = text.indexOf('■')
|
||||
let warning = text.indexOf('⚠')
|
||||
while (error !== -1 || warning !== -1) {
|
||||
const start = error === -1 ? warning : warning === -1 ? error : Math.min(error, warning)
|
||||
this.updateCandidate(text.slice(start, start + MAX_NOTICE_LENGTH))
|
||||
if (start === error) {
|
||||
error = text.indexOf('■', start + 1)
|
||||
} else {
|
||||
warning = text.indexOf('⚠', start + 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private updateCandidate(text: string): void {
|
||||
const content = text.slice(1).trimStart()
|
||||
if (content && content[0] !== CODEX_CAPACITY_MESSAGE[0]) {
|
||||
this.tail = ''
|
||||
return
|
||||
}
|
||||
const candidate = text.replace(/\s+/g, ' ')
|
||||
const message = candidate.slice(1).trimStart()
|
||||
if (message.startsWith(CODEX_CAPACITY_MESSAGE)) {
|
||||
this.pending = true
|
||||
this.tail = ''
|
||||
} else {
|
||||
this.tail = CODEX_CAPACITY_MESSAGE.startsWith(message) ? candidate : ''
|
||||
}
|
||||
}
|
||||
|
||||
observe(
|
||||
data: string,
|
||||
readScreen: () => readonly string[],
|
||||
readSynchronized?: () => boolean
|
||||
): boolean {
|
||||
this.scanner.observe(data)
|
||||
if (!this.pending || (readSynchronized?.() ?? this.synchronized)) {
|
||||
return false
|
||||
}
|
||||
this.pending = false
|
||||
this.tail = ''
|
||||
const lines = readScreen()
|
||||
const composer = lines.findLastIndex((line) => /^\s*›/.test(line))
|
||||
if (composer === -1) {
|
||||
return false
|
||||
}
|
||||
const above = lines.slice(0, composer).filter((line) => line.trim() !== '')
|
||||
let notice = ''
|
||||
for (let index = above.length - 1; index >= 0 && notice.length < MAX_NOTICE_LENGTH; index--) {
|
||||
notice = `${above[index].trim()} ${notice}`.trim()
|
||||
if (NOTICE.test(notice)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user