perf: defer agent output classification

Defers meaningful-output classification in AgentDetector until an active or starting agent session needs it.
This commit is contained in:
Neil 2026-05-30 13:07:44 -07:00 committed by GitHub
parent dce1ce1bdd
commit 2049d16bd5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 43 additions and 5 deletions

View File

@ -19,6 +19,35 @@ describe('AgentDetector', () => {
expect(stats.onAgentStop).not.toHaveBeenCalled()
})
it('does not inspect unknown non-agent output for meaningful content', () => {
const stats = {
onAgentStart: vi.fn(),
onAgentStop: vi.fn()
}
const detectMeaningfulContent = vi.fn(() => true)
const detector = new AgentDetector(stats as never, detectMeaningfulContent)
detector.onData('pty-1', '\x1b[2K\x1b[1G'.repeat(100), 100)
expect(detectMeaningfulContent).not.toHaveBeenCalled()
expect(stats.onAgentStart).not.toHaveBeenCalled()
expect(stats.onAgentStop).not.toHaveBeenCalled()
})
it('inspects content once when an agent title starts a session', () => {
const stats = {
onAgentStart: vi.fn(),
onAgentStop: vi.fn()
}
const detectMeaningfulContent = vi.fn(() => true)
const detector = new AgentDetector(stats as never, detectMeaningfulContent)
detector.onData('pty-1', `${oscTitle('⠂ Writing patch')}real output`, 100)
expect(detectMeaningfulContent).toHaveBeenCalledTimes(1)
expect(stats.onAgentStart).toHaveBeenCalledWith('pty-1', 100)
})
it('stops a session on working to idle transition using the last meaningful output time', () => {
const stats = {
onAgentStart: vi.fn(),

View File

@ -18,6 +18,8 @@ type PtyRecord = {
lastMeaningfulOutputAt: number | null
}
type MeaningfulContentDetector = (chunk: string) => boolean
/**
* Lightweight normalization to detect whether a PTY data chunk contains
* meaningful (non-ANSI, non-OSC) output. Mirrors the regex passes in
@ -72,9 +74,11 @@ function hasMeaningfulContent(chunk: string): boolean {
export class AgentDetector {
private ptys = new Map<string, PtyRecord>()
private stats: StatsCollector
private meaningfulContentDetector: MeaningfulContentDetector
constructor(stats: StatsCollector) {
constructor(stats: StatsCollector, meaningfulContentDetector = hasMeaningfulContent) {
this.stats = stats
this.meaningfulContentDetector = meaningfulContentDetector
}
/**
@ -99,8 +103,13 @@ export class AgentDetector {
return
}
const hasMeaningfulOutput = hasMeaningfulContent(rawData)
if (record.sessionOpen && hasMeaningfulOutput) {
let hasMeaningfulOutput: boolean | null = null
const getHasMeaningfulOutput = (): boolean => {
hasMeaningfulOutput ??= this.meaningfulContentDetector(rawData)
return hasMeaningfulOutput
}
if (record.sessionOpen && getHasMeaningfulOutput()) {
record.lastMeaningfulOutputAt = at
}
@ -119,7 +128,7 @@ export class AgentDetector {
record.lastStatus = status
record.sessionOpen = true
record.sessionStartAt = at
record.lastMeaningfulOutputAt = hasMeaningfulOutput ? at : null
record.lastMeaningfulOutputAt = getHasMeaningfulOutput() ? at : null
this.stats.onAgentStart(ptyId, at)
return
}
@ -139,7 +148,7 @@ export class AgentDetector {
// title is the start boundary for the next tracked session.
record.sessionOpen = true
record.sessionStartAt = at
record.lastMeaningfulOutputAt = hasMeaningfulOutput ? at : null
record.lastMeaningfulOutputAt = getHasMeaningfulOutput() ? at : null
this.stats.onAgentStart(ptyId, at)
}