Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions docs/components/agent-detection.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,11 @@ points at a `~/.grok/` install (so Cursor's own `agent` entrypoint stays Cursor)
process names / argv against known agent executables, scoring argv[0] highest,
then process name, then command-line tokens.
2. **Screen heuristics.** It scans the last ~24 non-blank lines of the pane for
agent-specific UI cues — e.g. "Esc to interrupt", Oh My Pi's `Working… ⟦esc⟧`
loader or braille spinner status line (working), confirmation/permission prompts
(blocked), idle prompts. Each agent family has its own patterns (including spinner glyphs:
braille frames, symbol cycles, Cursor's hexagons, Kimi's moon phases, etc.).
agent-specific UI cues — e.g. "Esc to interrupt", Oh My Pi's
`Working… ⟦esc⟧` loader or braille spinner status line (working), its
interactive `Ask` choice prompt (blocked), confirmation/permission prompts
(blocked), idle prompts. Each agent family has its own patterns (including spinner
glyphs: braille frames, symbol cycles, Cursor's hexagons, Kimi's moon phases, etc.).
For Claude, a running **background workflow** keeps a status line *below* the
input box (e.g. `3/5 agents done · 7m 29s · ↓ 288.5k tokens`) after the turn has
ended; Prowl reads that footer as **Working**, so a churning workflow isn't
Expand Down
13 changes: 12 additions & 1 deletion supacode/Infrastructure/AgentDetection/ScreenHeuristics.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,18 @@ nonisolated private func recentLines(_ content: String, limit: Int) -> String {
}

nonisolated private func detectPi(_ content: String) -> AgentRawState {
hasPiWorkingLine(content) ? .working : .idle
if hasPiAskPrompt(content) {
return .blocked
}
return hasPiWorkingLine(content) ? .working : .idle
}

nonisolated private func hasPiAskPrompt(_ content: String) -> Bool {
let lines = content.split(separator: "\n", omittingEmptySubsequences: false)
return lines.contains { line in
let trimmed = line.trimmingCharacters(in: .whitespaces)
return trimmed.contains("Enter select") && trimmed.contains("Esc cancel")
}
}

nonisolated private func hasPiWorkingLine(_ content: String) -> Bool {
Expand Down
19 changes: 19 additions & 0 deletions supacodeTests/ScreenHeuristicsTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,25 @@ struct ScreenHeuristicsTests {
#expect(DetectedAgent.pi.detectState(in: "Done") == .idle)
}

@Test func piAskPromptIsBlocked() {
#expect(
DetectedAgent.pi.detectState(
in: """
⠏ Clarifying combined list order ⟨esc⟩

╭─ Ask ─────────────────────────────────────────────────────────────────────╮
│ Which order should the combined list use? │
├────────────────────────────────────────────────────────────────────────────┤
│   Repo first │
│  Global first │
├────────────────────────────────────────────────────────────────────────────┤
│ Enter select · n note · ↑/↓ move · Esc cancel │
╰────────────────────────────────────────────────────────────────────────────╯
"""
) == .blocked
)
}

@Test func piIgnoresStaleWorkingMentionInCompletedOutput() {
#expect(
DetectedAgent.pi.detectState(
Expand Down
Loading