Files
orca/src/shared/freebuff-screen-status.ts
T
NeilandClaude 5dd5294294 perf(orcad): keep the server bundle ASCII so V8 stores it at half the size (#31495)
* perf(orcad): keep the server bundle ASCII so V8 stores it one byte per character

129 non-ASCII characters in regex literals (and three in a String.raw plugin
template) made V8 hold the whole ~11 MB orcad-server.js source as a two-byte
string. Write them as \uXXXX escapes and fail the build if any return.

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(orcad): keep license comments ASCII and name the module the guard trips on

esbuild gathers legal comments at the end of the bundle; escape that comment so
a dependency's license text cannot fail the ASCII guard, while licenses still
ship with the code. The guard now reports the offset and, from an unminified
rebuild, the module the character came from. A dark-switch build is covered.

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-21 04:06:49 -07:00

157 lines
5.2 KiB
TypeScript

import type { ParsedAgentStatusPayload } from './agent-status-types'
import { extractLastOscTitle } from './osc-title-extraction'
import { extractOscTitleScanTail } from './osc-title-scan-tail'
import { advancePartialEscapeTail } from './terminal-partial-escape-tail'
export function splitFreebuffScreenUpdates(data: string, pendingEscape: string): string[] {
const input = pendingEscape + data
const chunks: string[] = []
let start = 0
// oxlint-disable-next-line no-control-regex -- Terminal protocol delimiters contain ESC and BEL.
for (const match of input.matchAll(/\x1b\[\?2026l/g)) {
const end = match.index + match[0].length - pendingEscape.length
if (end > start) {
chunks.push(data.slice(start, end))
}
start = Math.max(start, end)
}
if (start < data.length) {
chunks.push(data.slice(start))
}
return chunks
}
function cleanBoxLine(line: string): string {
return line.replace(/^[\u2502\u2503\s]+|[\u2502\u2503\s]+$/g, '').trim()
}
/** Reads the live composer area, never status words in the conversation above it. */
export function readFreebuffScreenStatus(
lines: readonly string[],
alternate: boolean
): Omit<ParsedAgentStatusPayload, 'prompt' | 'agentType'> | null {
const text = lines.join('\n')
if (!alternate) {
if (
/freebuff found agent files in this repository/.test(text) &&
/Load and run these\? \[y\/N\]/.test(text)
) {
return { state: 'blocked', toolInput: 'Trust repository agent files? [y/N]' }
}
return null
}
if (/Press ENTER to login\.\.\./.test(text)) {
return { state: 'blocked', toolInput: 'Sign in to Freebuff' }
}
const footer = lines.findLastIndex((line) => /\/model\s+to change.*Chat:/.test(line))
if (footer === -1) {
return null
}
const dialog = lines.findLastIndex((line) =>
/[\u256D\u2500].*Some questions for you.*[\u2500\u256E]/.test(line)
)
if (
dialog !== -1 &&
dialog < footer &&
/\u2191\u2193 navigate/.test(lines.slice(dialog, footer).join('\n'))
) {
const body = lines.slice(dialog + 1, footer).map(cleanBoxLine)
const question = body
.find((line) => /^[\u25BC\u25B6\u25B8\u25BE\u25BA]/.test(line))
?.replace(/^[\u25BC\u25B6\u25B8\u25BE\u25BA]\s*/, '')
const options = body.flatMap((line) => {
const match = /^[\u25CB\u25CF]\s+(.+)$/.exec(line)
return match ? [{ label: match[1], description: '' }] : []
})
return {
state: 'waiting',
toolName: 'ask_user',
toolInput: question ?? 'Freebuff is waiting for an answer',
...(question
? { interactivePrompt: JSON.stringify({ questions: [{ question, options }] }) }
: {})
}
}
const composerTop = lines.findLastIndex(
(line, index) => index < footer && /^\s*\u256D\u2500+\u256E\s*$/.test(line)
)
if (composerTop === -1 || footer - composerTop > 12) {
return null
}
const status = lines[composerTop - 1]?.trim() ?? ''
if (
/^(?:thinking|working|connecting|retrying)\.\.\./.test(status) ||
status.startsWith('high demand — in line')
) {
return { state: 'working' }
}
if (status === '' && text.includes('Your first message starts the session.')) {
return { state: 'done', sessionBoundary: true }
}
if (/^(?:\d+[hms]\s*)+left\b|^unlimited\b/.test(status) && /End session/.test(status)) {
return { state: 'done' }
}
return null
}
/** One observation stream per execution-host terminal; recreated with its screen model. */
export class FreebuffScreenStatusTracker {
private titleTail = ''
private frameTail = ''
private insideFrame = false
private identified: boolean
private prompt = ''
private previous: string | null = null
private worked = false
constructor(identified = false) {
this.identified = identified
}
observe(
data: string,
readScreen: () => { lines: readonly string[]; alternate: boolean }
): ParsedAgentStatusPayload | null {
const titleInput = this.titleTail + data
this.titleTail = extractOscTitleScanTail(titleInput)
const title = extractLastOscTitle(titleInput)
if (title?.startsWith('Freebuff: ')) {
this.identified = true
this.prompt = title.slice('Freebuff: '.length)
}
const frameInput = this.frameTail + data
// oxlint-disable-next-line no-control-regex -- Terminal protocol delimiters contain ESC and BEL.
for (const match of frameInput.matchAll(/\x1b\[\?2026([hl])/g)) {
this.insideFrame = match[1] === 'h'
}
this.frameTail = advancePartialEscapeTail(this.frameTail, data)
if (!this.identified || this.insideFrame || this.frameTail.length > 0) {
return null
}
const { lines, alternate } = readScreen()
const result = readFreebuffScreenStatus(lines, alternate)
if (!result) {
return null
}
if (result.sessionBoundary) {
this.prompt = ''
this.worked = false
}
const payload: ParsedAgentStatusPayload = {
...result,
agentType: 'freebuff',
prompt: this.prompt,
...(result.state === 'done' && !this.worked ? { sessionBoundary: true } : {})
}
if (result.state === 'working' || result.state === 'waiting') {
this.worked = true
}
const signature = JSON.stringify(payload)
if (signature === this.previous) {
return null
}
this.previous = signature
return payload
}
}