Skip to content

Detect coding-agent state from live terminal screens - #8309

Closed
lawrencecchen wants to merge 16 commits into
mainfrom
feat-agent-terminal-state-detection
Closed

lawrencecchen wants to merge 16 commits into
mainfrom
feat-agent-terminal-state-detection

Conversation

@lawrencecchen

@lawrencecchen lawrencecchen commented Jul 17, 2026 •

Copy link
Copy Markdown
Contributor

Adds terminal-state detection for 21 coding-agent families plus existing cmux families.

The PTY callback only advances a coalesced dirty revision. A per-surface actor waits for a quiet window, reads plain rendered text from the final 48 active-screen rows, verifies process start time and terminal generation before and after capture, and publishes effective changes only. Blocked and history evidence use the full capture; transient working evidence uses the bottom 12 physical rows so stale spinners cannot override the current composer. Complete lifecycle integrations remain authoritative.

Per-surface setup and teardown are serialized. Reinstall waits for prior cleanup, and repeated drops are idempotent. Strict blocked rules require either multiple corroborating fragments or an exact rendered-line prompt.

Verification:

  • 241 CmuxTerminalCore tests and 90 CmuxTerminal tests pass
  • lifecycle race, Gemini/Kimi prompts, stale Pi spinner, multiline approvals, and CRLF rendering have test-first regression commits
  • focused app-host lifecycle test passes: https://github.com/manaflow-ai/cmux/actions/runs/29561150240
  • final tagged macOS build astate from bb89e237cb5fd5b321d74482413e6a4464a1c70e: https://github.com/manaflow-ai/cmux/actions/runs/29561148873
  • current-head Codex 0.144.3 transitioned idle → working → blocked on an approval prompt, then working → idle through /tmp/cmux-debug-astate.sock
  • nine installed agent families were exercised in isolated tmux panes, with every captured state audited against the final classifier
  • paired 15-second sample captures found no detector stacks; Instruments trace finalization hung, so no Instruments pass is claimed
  • project consistency and git diff --check pass
  • no user-facing strings were added

@coderabbitai

coderabbitai Bot commented Jul 17, 2026 •

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds agent terminal process recognition, VT screen classification, debounced revision scheduling, per-surface runtime coordination, lifecycle-state resolution, terminal output wiring, and debug payload fields.

Changes

Agent terminal detection

Layer / File(s) Summary
Classification contracts and evidence rules
Packages/macOS/CmuxTerminalCore/Sources/CmuxTerminalCore/AgentState/*, Packages/macOS/CmuxTerminalCore/Tests/CmuxTerminalCoreTests/AgentTerminalStateClassifierTests.swift
Defines process, screen, profile, semantic-state, and classification models; validates built-in profiles; recognizes foreground processes; classifies live terminal evidence; and tests recognition, precedence, history, and catalog validation.
Coalesced detection scheduling
Packages/macOS/CmuxTerminalCore/Sources/CmuxTerminalCore/AgentState/AgentTerminalDetection*.swift, Packages/macOS/CmuxTerminalCore/Sources/CmuxTerminalCore/AgentState/AgentTerminalDirtySignal.swift, Packages/macOS/CmuxTerminalCore/Sources/CmuxTerminalCore/AgentState/AgentTerminalStateDetectionScheduler.swift, Packages/macOS/CmuxTerminalCore/Tests/CmuxTerminalCoreTests/AgentTerminalStateDetectionSchedulerTests.swift
Adds revision signals, injectable timing, quiet-window and maximum-latency scheduling, stale-result rejection, publication deduplication, and scheduler tests.
Process capture and runtime classification
Packages/macOS/CmuxTerminalCore/Sources/CmuxTerminalCore/AgentState/AgentTerminalProcessInspector.swift, Sources/AgentTerminalStateSurfaceObserver.swift, Sources/AgentTerminalClassificationWorker.swift, Sources/AgentTerminalStateRuntime.swift
Captures stable process and bounded VT snapshots, caches classifications, coordinates per-surface scheduling, applies updates, and performs teardown.
Lifecycle integration and terminal wiring
Sources/Workspace+AgentTerminalStateDetection.swift, Sources/AgentHibernation/AgentHibernationLifecycleState.swift, Sources/TerminalOutputTeeContext.swift, Sources/TerminalSurfaceRuntimeWiring.swift, Sources/GhosttyTerminalView.swift, Sources/Workspace*.swift, Sources/SidebarAgentActivitySummary.swift, Sources/TerminalController.swift, cmux.xcodeproj/project.pbxproj
Maps detected states into lifecycle data, marks terminal output dirty, installs and drops per-surface runtime state, exposes debug fields, updates lifecycle consumers, and registers new sources in the project.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

  • manaflow-ai/cmux#6480: Uses the same sidebar active-agent lifecycle path affected by resolved terminal detection states.

Important

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

❌ Failed checks (4 errors, 1 warning)

Check name Status Explanation Resolution
Cmux Swift Actor Isolation ❌ Error FAIL: AgentTerminalStateRuntime uses plain Task closures to call self.apply(update) and AppDelegate.shared?.workspaceFor(...) without a MainActor hop. Hop those task bodies onto @MainActor (or use MainActor.run) before touching AppDelegate/Workspace state.
Cmux Swift Blocking Runtime ❌ Error Production code adds sleep-based debounce waits in AgentTerminalStateDetectionScheduler, via AgentTerminalDetectionClock.continuous() and clock.sleep(for:) calls. Replace the quiet-window/latency waits with an explicit signal- or state-transition-driven scheduler; keep sleep-based delays only in tests.
Cmux Architecture Rethink ❌ Error FAIL: AgentTerminalStateDetectionScheduler adds clock.sleep-driven quiet-window/max-latency debounce and stale-result retries in the PTY->classification path, a timing patch for capture races. Collapse detection into one explicit owner that snapshots on invalidation/state transitions; remove the sleep/retry debounce from production or keep coalescing test-only.
Cmux No Ambient Global State ❌ Error GhosttyApp.shared now stores AgentTerminalStateRuntime at Sources/GhosttyTerminalView.swift:385, adding app-singleton runtime state. Construct AgentTerminalStateRuntime in the app root and inject it via TerminalSurfaceRuntimeDependencies instead of storing it on GhosttyApp.shared.
Docstring Coverage ⚠️ Warning Docstring coverage is 13.75% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (20 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Cmux Browser Automation Off-Main ✅ Passed Branch diff touches only terminal-state detection files; no browser/WebKit/socket-worker automation code or main-actor routing changes appear in the PR.
Cmux Expensive Synchronous Load ✅ Passed The new agent-state path uses async process/screen capture and in-memory cataloging; no new RestorableAgentSessionIndex.load() or large history/JSON load was added to main/interactive paths.
Cmux Cache Substitution Correctness ✅ Passed Freshness-checked caches plus cold fallbacks are used; detected state stays transient/debug and isn’t persisted in session snapshots.
Cmux No Hacky Sleeps ✅ Passed No modified non-Swift runtime/build scripts were present, and the PR’s timing logic changes are all in Swift, which this check excludes.
Cmux Algorithmic Complexity ✅ Passed New scans/sorts are over fixed per-panel/status-key catalogs (~25 keys), and the scalable per-surface loops are linear fanout, not nested rescans.
Cmux Swift Concurrency ✅ Passed Changed Swift code uses actors/async/await and stored Tasks; no new DispatchQueue, Combine, completion-handler, or unmanaged fire-and-forget legacy async patterns were introduced.
Cmux Swift @Concurrent ✅ Passed The new nonisolated async process-inspection APIs are annotated @concurrent; the other async work is actor-isolated or intentionally @MainActor.
Cmux Swift Package Boundaries ✅ Passed PASS: the reusable detection/classification engine lives in CmuxTerminalCore; root Sources only add app-specific lifecycle bridging, wiring, and Ghostty glue.
Cmux Swiftpm Lockfiles ✅ Passed Diff only adds Swift source/test files and xcodeproj file refs; no Package.swift/.gitignore/workflow changes and no Package.resolved diff, so the lockfile rule isn't violated.
Cmux Swift Logging ✅ Passed The only changed Swift file adds no print/debugPrint/dump/NSLog/Logger usage; the diff only adjusts classifier evidence handling.
Cmux User-Facing Error Privacy ✅ Passed The only diff change is internal classifier logic for multiline blocked evidence; no user-facing errors, alerts, or sensitive provider details were added or altered.
Cmux Full Internationalization ✅ Passed No user-facing copy was added; the new strings are internal tokens/debug data, and no xcstrings/Info.plist/web locale assets were changed.
Cmux Swiftui State Layout ✅ Passed No SwiftUI views/state were introduced; the only modified file is a non-UI classifier with no forbidden patterns.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed PR only adds terminal-state/runtime code; no added NSWindow/NSPanel/WindowGroup or cmuxAuxiliaryWindowIdentifiers lines, so the rule isn’t triggered.
Cmux Source Artifacts ✅ Passed All changed paths are intentional source, tests, docs, or project config; no logs, caches, temp dirs, build output, or other artifact paths appear.
Cmux No Test Or Debug Seam In Production Source ✅ Passed The only changed production file is AgentTerminalStateClassifier.swift, and it adds no #if DEBUG gate, test-only member, or widened test seam.
Title check ✅ Passed The title is concise and accurately summarizes the main change: detecting coding-agent state from live terminal screens.
Description check ✅ Passed The description clearly explains what changed and why, and includes substantial verification details, so it is mostly complete despite missing some template sections.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat-agent-terminal-state-detection

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@cursor

cursor Bot commented Jul 17, 2026

Copy link
Copy Markdown

Bugbot is paused — on-demand spend limit reached

Bugbot uses usage-based billing for this team and has hit its on-demand spend limit.

A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue.

@greptile-apps

greptile-apps Bot commented Jul 17, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds a clean-room terminal-state detector that reads the live bottom 48 rows of active terminal screens to classify coding agents (idle / working / blocked / unknown) across 27 agent families. It introduces a coalesced dirty-signal pipeline — PTY callbacks advance only an atomic revision, a per-surface actor waits for a quiet window, then captures and classifies off the callback path — and wires the results into the existing AgentHibernationController alongside lifecycle integrations.

  • New subsystem (AgentState/ in CmuxTerminalCore, app-side runtime files): AgentTerminalDirtySignal for PTY-safe invalidation, AgentTerminalStateDetectionScheduler for coalesced/debounced evaluation, AgentTerminalStateClassifier + AgentTerminalProfileCatalog for recognition and classification, and AgentTerminalStateSurfaceObserver for generation-checked screen capture.
  • Authority model: authoritative lifecycle integrations remain canonical; screen detection supplements or overrides only session-only (non-authoritative) integrations, controlled by lifecycleAuthoritative per profile.
  • Workspace integration (Workspace+AgentTerminalStateDetection.swift, Workspace+AgentLifecycle.swift): resolvedStates merges screen and lifecycle keys; promptIdle shell state clears the detected screen state.

Confidence Score: 5/5

The change is safe to merge; the new pipeline is well-isolated, cancellation-aware, generation-checked, and covered by unit and lifecycle tests.

The dirty-signal → debounce → capture → classify → publish pipeline is correctly structured: PTY callbacks only flip an atomic counter, all screen I/O is off the callback path, the scheduler actor correctly serializes per-surface state, and the generation-checked capture prevents stale snapshot use. The authority model cleanly separates authoritative from session-only lifecycle integrations. The single gap found — argument needle uniqueness not enforced in catalog validation — is a missing guard on a currently non-overlapping set of needles, not a present defect.

AgentTerminalProfileCatalog.swift: the failable initializer enforces executable basename uniqueness but not argument needle uniqueness; worth hardening before the profile list grows further.

Important Files Changed

Filename Overview
Packages/macOS/CmuxTerminalCore/Sources/CmuxTerminalCore/AgentState/AgentTerminalProfileCatalog.swift Immutable validated catalog for 27 agent families; validates executable basename and hint alias uniqueness but not argument needle uniqueness, leaving a silent wrong-profile detection path for future profile additions.
Packages/macOS/CmuxTerminalCore/Sources/CmuxTerminalCore/AgentState/AgentTerminalStateDetectionScheduler.swift Coalesced debounce/deadline scheduler implemented as a Swift actor with injectable clock; quiet-window and maximum-latency bounds are correct, cancellation is properly handled at every await point.
Packages/macOS/CmuxTerminalCore/Sources/CmuxTerminalCore/AgentState/AgentTerminalStateClassifier.swift Pure recognition and classification logic; blocked → working → idle priority is correct, history-view suppression is guarded, bottom-rows scan is O(n) but off the critical path.
Packages/macOS/CmuxTerminalCore/Sources/CmuxTerminalCore/AgentState/AgentTerminalProcessInspector.swift Reads process identity and argv via KERN_PROCARGS2 sysctl; generation-checks guard against PID reuse, @Concurrent annotation correctly leaves the caller actor for the syscall work.
Sources/AgentTerminalStateRuntime.swift MainActor composition root; correctly guards apply() against removed observers on drop, single update-consumer task is lazy and correctly uses weak-self capture.
Sources/AgentTerminalStateSurfaceObserver.swift Generation-checked capture with pre/post identity validation; correctly invalidates snapshot on runtimeGeneration or foreground PID change between async hops.
Sources/AgentTerminalSurfaceTaskSequencer.swift Token-guarded install/drop sequencer; repeated drop is idempotent, reinstall correctly awaits pending teardown before starting, covered by lifecycle test.
Sources/Workspace+AgentTerminalStateDetection.swift Merges screen detection keys with lifecycle keys; authority model (authoritative profiles keep lifecycle, non-authoritative profiles defer to screen) is correctly implemented.
Sources/TerminalSurfaceRuntimeWiring.swift Injects AgentTerminalDirtySignal into TerminalOutputTeeContext and installs/drops runtime on the correct lease lifecycle boundaries.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant PTY as PTY Callback
    participant Signal as AgentTerminalDirtySignal
    participant Scheduler as AgentTerminalStateDetectionScheduler (actor)
    participant Observer as AgentTerminalStateSurfaceObserver (@MainActor)
    participant Inspector as AgentTerminalProcessInspector (@concurrent)
    participant Worker as AgentTerminalClassificationWorker (actor)
    participant Runtime as AgentTerminalStateRuntime (@MainActor)
    participant Workspace as Workspace (@MainActor)

    PTY->>Signal: markDirty() → advance atomic revision
    Signal->>Scheduler: yield revision to bufferingNewest stream
    Scheduler->>Scheduler: debounce quiet window (90ms) / max latency (350ms)
    Scheduler->>Observer: evaluate(revision)
    Observer->>Inspector: "identity(pid, generation) [@concurrent]"
    Inspector-->>Observer: AgentTerminalProcessIdentity
    Observer->>Inspector: "snapshot(pid, generation) [@concurrent]"
    Inspector-->>Observer: AgentTerminalProcessSnapshot
    Observer->>Observer: capture boundedActiveScreenTailText (via teardown actor)
    Observer->>Inspector: identity post-capture validation
    Observer-->>Scheduler: AgentTerminalScreenSnapshot
    Scheduler->>Worker: classify(surfaceID, snapshot)
    Worker-->>Scheduler: AgentTerminalStateClassification
    Scheduler->>Scheduler: "suppress if classification == lastPublished"
    Scheduler->>Runtime: yield AgentTerminalDetectionUpdate
    Runtime->>Workspace: setDetectedAgentLifecycle(statusKey, familyID, state)
    Workspace->>Workspace: resolvedStates → effective state
    Workspace->>Workspace: recordDetectedAgentLifecycleChange if changed
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant PTY as PTY Callback
    participant Signal as AgentTerminalDirtySignal
    participant Scheduler as AgentTerminalStateDetectionScheduler (actor)
    participant Observer as AgentTerminalStateSurfaceObserver (@MainActor)
    participant Inspector as AgentTerminalProcessInspector (@concurrent)
    participant Worker as AgentTerminalClassificationWorker (actor)
    participant Runtime as AgentTerminalStateRuntime (@MainActor)
    participant Workspace as Workspace (@MainActor)

    PTY->>Signal: markDirty() → advance atomic revision
    Signal->>Scheduler: yield revision to bufferingNewest stream
    Scheduler->>Scheduler: debounce quiet window (90ms) / max latency (350ms)
    Scheduler->>Observer: evaluate(revision)
    Observer->>Inspector: "identity(pid, generation) [@concurrent]"
    Inspector-->>Observer: AgentTerminalProcessIdentity
    Observer->>Inspector: "snapshot(pid, generation) [@concurrent]"
    Inspector-->>Observer: AgentTerminalProcessSnapshot
    Observer->>Observer: capture boundedActiveScreenTailText (via teardown actor)
    Observer->>Inspector: identity post-capture validation
    Observer-->>Scheduler: AgentTerminalScreenSnapshot
    Scheduler->>Worker: classify(surfaceID, snapshot)
    Worker-->>Scheduler: AgentTerminalStateClassification
    Scheduler->>Scheduler: "suppress if classification == lastPublished"
    Scheduler->>Runtime: yield AgentTerminalDetectionUpdate
    Runtime->>Workspace: setDetectedAgentLifecycle(statusKey, familyID, state)
    Workspace->>Workspace: resolvedStates → effective state
    Workspace->>Workspace: recordDetectedAgentLifecycleChange if changed
Loading

Reviews (6): Last reviewed commit: "Bound transient working evidence to term..." | Re-trigger Greptile

Comment on lines +4 to +26
func resolvedAgentLifecycleStates(
_ panelStates: [String: AgentHibernationLifecycleState]
) -> [AgentHibernationLifecycleState] {
var lifecycle = panelStates.filter {
!AgentHibernationLifecycleStatusKeys.isManualKey($0.key) &&
!AgentHibernationLifecycleStatusKeys.isDetectionKey($0.key)
}
var screen: [AgentHibernationLifecycleState] = []
for (key, state) in panelStates where AgentHibernationLifecycleStatusKeys.isDetectionKey(key) {
guard let familyID = AgentHibernationLifecycleStatusKeys.detectionFamilyID(key: key),
let profile = AgentTerminalProfileCatalog.builtIn.profile(id: familyID) else {
screen.append(state)
continue
}
if profile.lifecycleAuthoritative {
if lifecycle[profile.statusKey] == nil { screen.append(state) }
} else {
lifecycle.removeValue(forKey: profile.statusKey)
screen.append(state)
}
}
return Array(lifecycle.values) + screen
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Top-level free function used as cross-file API

resolvedAgentLifecycleStates is a file-scope func with no private/fileprivate modifier, making it accessible and actually used from two other source files: SidebarAgentActivitySummary.swift (line 16) and Workspace+AgentLifecycle.swift. This is exactly the pattern the no-ambient-global-state rule flags: a top-level free function used as API that should be a method on the type that owns the data. The natural home is a fileprivate helper within this file combined with a dedicated method on SidebarAgentActivitySummary or Workspace that replaces the free-function call site in SidebarAgentActivitySummary.swift.

Rule Used: Flag new ambient global state in production Swift:... (source)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment thread Sources/AgentTerminalStateRuntime.swift Outdated
Comment on lines +50 to +55
func debugState(workspaceID: UUID, surfaceID: UUID) -> (family: String?, state: String, source: String) {
guard let workspace = AppDelegate.shared?.workspaceFor(tabId: workspaceID) else {
return (nil, AgentTerminalSemanticState.unknown.rawValue, "none")
}
return workspace.debugDetectedAgentState(panelId: surfaceID)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Production method named debug... in production source

debugState (here) and debugDetectedAgentState on Workspace carry the debug... prefix that the no-test-debug-seam rule flags in production Sources/ files. Both have real production callers (the TerminalController V2 command handler), so they are not test-only seams, but the naming suggests debug-only intent. Consider renaming to something like terminalDetectionState/detectedTerminalAgentState to make clear these are production diagnostic fields.

Rule Used: Flag Swift files under a production Sources path (... (source)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@Packages/macOS/CmuxTerminalCore/Sources/CmuxTerminalCore/AgentState/AgentTerminalProfileCatalog.swift`:
- Around line 18-29: Update the profile validation in
AgentTerminalProfileCatalog’s catalog-building loop to reject or normalize
matcher data before insertion: trim and normalize executable basenames, reject
empty values and duplicate basenames across profiles, and reject empty contains
needles. Preserve the existing profile ID and hint validation while ensuring
each executable matcher has one unambiguous owning profile.

In
`@Packages/macOS/CmuxTerminalCore/Sources/CmuxTerminalCore/AgentState/AgentTerminalStateClassifier.swift`:
- Around line 26-41: Make AgentTerminalStateClassifier fail closed: at
Packages/macOS/CmuxTerminalCore/Sources/CmuxTerminalCore/AgentState/AgentTerminalStateClassifier.swift:26-41,
replace normalized path and argv substring matching with direct executable
identity or a registered launch descriptor/hint; at :58-60, remove terminal
titles as semantic evidence; at :67-74, require explicit idle evidence and
return .unknown when absent. Ensure correctness-critical identity and state use
one reliable structured source.
- Around line 62-65: Update the history-evidence branch in
AgentTerminalStateClassifier so that when profile.historyViewNeedles matches
liveEvidence but snapshot.previousReliableState is nil, it sets state to
.unknown and skips blocked/working classification. Preserve returning the
previous reliable state when available, and only evaluate the subsequent
evidence groups when history is not visible.

In `@Sources/AgentTerminalStateRuntime.swift`:
- Around line 57-67: Update startUpdateConsumerIfNeeded so the Task does not
bind self before the long-lived updates stream loop; remove the outer guard let
self and weakly capture self for each iteration, binding it only immediately
before applying that update while preserving cancellation handling.
- Around line 38-48: Update drop(surfaceID:) to cancel the removed
registrationTask immediately, then stop the scheduler before awaiting the task’s
completion. Preserve the subsequent classificationWorker.remove cleanup and
workspace lifecycle clearing, ensuring teardown cannot wait indefinitely on
scheduler.start.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: caaf9e21-dab6-471c-a05c-16b4729a1e24

📥 Commits

Reviewing files that changed from the base of the PR and between d1ac0da and 27ab1d3.

📒 Files selected for processing (31)
  • Packages/macOS/CmuxTerminalCore/CLEAN_ROOM.md
  • Packages/macOS/CmuxTerminalCore/Sources/CmuxTerminalCore/AgentState/AgentTerminalAuthorityResolver.swift
  • Packages/macOS/CmuxTerminalCore/Sources/CmuxTerminalCore/AgentState/AgentTerminalDetectionClock.swift
  • Packages/macOS/CmuxTerminalCore/Sources/CmuxTerminalCore/AgentState/AgentTerminalDetectionConfiguration.swift
  • Packages/macOS/CmuxTerminalCore/Sources/CmuxTerminalCore/AgentState/AgentTerminalDetectionUpdate.swift
  • Packages/macOS/CmuxTerminalCore/Sources/CmuxTerminalCore/AgentState/AgentTerminalDirtySignal.swift
  • Packages/macOS/CmuxTerminalCore/Sources/CmuxTerminalCore/AgentState/AgentTerminalFamilyProfile.swift
  • Packages/macOS/CmuxTerminalCore/Sources/CmuxTerminalCore/AgentState/AgentTerminalProcessIdentity.swift
  • Packages/macOS/CmuxTerminalCore/Sources/CmuxTerminalCore/AgentState/AgentTerminalProcessSnapshot.swift
  • Packages/macOS/CmuxTerminalCore/Sources/CmuxTerminalCore/AgentState/AgentTerminalProfileCatalog.swift
  • Packages/macOS/CmuxTerminalCore/Sources/CmuxTerminalCore/AgentState/AgentTerminalScreenSnapshot.swift
  • Packages/macOS/CmuxTerminalCore/Sources/CmuxTerminalCore/AgentState/AgentTerminalSemanticState.swift
  • Packages/macOS/CmuxTerminalCore/Sources/CmuxTerminalCore/AgentState/AgentTerminalStateClassification.swift
  • Packages/macOS/CmuxTerminalCore/Sources/CmuxTerminalCore/AgentState/AgentTerminalStateClassifier.swift
  • Packages/macOS/CmuxTerminalCore/Sources/CmuxTerminalCore/AgentState/AgentTerminalStateDetectionScheduler.swift
  • Packages/macOS/CmuxTerminalCore/Tests/CmuxTerminalCoreTests/AgentTerminalStateClassifierTests.swift
  • Packages/macOS/CmuxTerminalCore/Tests/CmuxTerminalCoreTests/AgentTerminalStateDetectionSchedulerTests.swift
  • Sources/AgentHibernation/AgentHibernationLifecycleState.swift
  • Sources/AgentTerminalClassificationWorker.swift
  • Sources/AgentTerminalProcessInspector.swift
  • Sources/AgentTerminalStateRuntime.swift
  • Sources/AgentTerminalStateSurfaceObserver.swift
  • Sources/GhosttyTerminalView.swift
  • Sources/SidebarAgentActivitySummary.swift
  • Sources/TerminalController.swift
  • Sources/TerminalOutputTeeContext.swift
  • Sources/TerminalSurfaceRuntimeWiring.swift
  • Sources/Workspace+AgentLifecycle.swift
  • Sources/Workspace+AgentTerminalStateDetection.swift
  • Sources/Workspace.swift
  • cmux.xcodeproj/project.pbxproj

Comment on lines +38 to +48
func drop(surfaceID: UUID) {
let observer = observers.removeValue(forKey: surfaceID)
let registrationTask = registrationTasks.removeValue(forKey: surfaceID)
Task {
_ = await registrationTask?.value
await scheduler.stop(surfaceID: surfaceID)
await classificationWorker.remove(surfaceID: surfaceID)
}
guard let observer, let workspace = AppDelegate.shared?.workspaceFor(tabId: observer.workspaceID) else { return }
workspace.clearDetectedAgentLifecycle(panelId: surfaceID)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win

Fix potential deadlock and task leakage by cancelling the registration task and reordering teardown.

Awaiting registrationTask?.value before calling scheduler.stop(surfaceID:) creates a deadlock risk. If scheduler.start runs continuously until explicitly stopped or cancelled, it will never complete because stop is blocked waiting for it to finish.

Additionally, the task should be explicitly cancelled to ensure any underlying suspended operations are aborted.

🔄 Proposed fix
     func drop(surfaceID: UUID) {
         let observer = observers.removeValue(forKey: surfaceID)
         let registrationTask = registrationTasks.removeValue(forKey: surfaceID)
+        registrationTask?.cancel()
         Task {
-            _ = await registrationTask?.value
             await scheduler.stop(surfaceID: surfaceID)
+            _ = await registrationTask?.value
             await classificationWorker.remove(surfaceID: surfaceID)
         }
         guard let observer, let workspace = AppDelegate.shared?.workspaceFor(tabId: observer.workspaceID) else { return }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func drop(surfaceID: UUID) {
let observer = observers.removeValue(forKey: surfaceID)
let registrationTask = registrationTasks.removeValue(forKey: surfaceID)
Task {
_ = await registrationTask?.value
await scheduler.stop(surfaceID: surfaceID)
await classificationWorker.remove(surfaceID: surfaceID)
}
guard let observer, let workspace = AppDelegate.shared?.workspaceFor(tabId: observer.workspaceID) else { return }
workspace.clearDetectedAgentLifecycle(panelId: surfaceID)
}
func drop(surfaceID: UUID) {
let observer = observers.removeValue(forKey: surfaceID)
let registrationTask = registrationTasks.removeValue(forKey: surfaceID)
registrationTask?.cancel()
Task {
await scheduler.stop(surfaceID: surfaceID)
_ = await registrationTask?.value
await classificationWorker.remove(surfaceID: surfaceID)
}
guard let observer, let workspace = AppDelegate.shared?.workspaceFor(tabId: observer.workspaceID) else { return }
workspace.clearDetectedAgentLifecycle(panelId: surfaceID)
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Sources/AgentTerminalStateRuntime.swift` around lines 38 - 48, Update
drop(surfaceID:) to cancel the removed registrationTask immediately, then stop
the scheduler before awaiting the task’s completion. Preserve the subsequent
classificationWorker.remove cleanup and workspace lifecycle clearing, ensuring
teardown cannot wait indefinitely on scheduler.start.

Comment thread Sources/AgentTerminalStateRuntime.swift

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Sources/GhosttyTerminalView.swift`:
- Around line 384-385: Remove the static singleton declaration
agentTerminalStateRuntime from GhosttyTerminalView and move its ownership to a
constructable, injectable runtime owner such as AppDelegate or AppEnvironment.
Pass that instance through the surface runtime dependencies to the consumers
that need it, preserving main-actor isolation without introducing ambient global
state.

In `@Sources/Workspace`+AgentTerminalStateDetection.swift:
- Around line 18-23: Update the lifecycleAuthoritative branch to stop appending
the screen-detected state when lifecycle[profile.statusKey] is absent. Treat the
authoritative lifecycle record as the sole source of truth and fail closed when
that record is missing; leave the non-authoritative branch unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 200c4f2d-88df-4020-a179-b9c22c2b42ab

📥 Commits

Reviewing files that changed from the base of the PR and between 27ab1d3 and 12d4886.

📒 Files selected for processing (6)
  • Sources/AgentTerminalStateRuntime.swift
  • Sources/GhosttyTerminalView.swift
  • Sources/SidebarAgentActivitySummary.swift
  • Sources/TerminalController.swift
  • Sources/Workspace+AgentLifecycle.swift
  • Sources/Workspace+AgentTerminalStateDetection.swift
💤 Files with no reviewable changes (1)
  • Sources/AgentTerminalStateRuntime.swift

Comment thread Sources/GhosttyTerminalView.swift Outdated
Comment on lines +384 to +385
@MainActor
private static let agentTerminalStateRuntime = AgentTerminalStateRuntime()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift

Avoid adding new singletons for runtime state.

Using a private static let creates ambient global state for the agent terminal runtime. As per coding guidelines, "In production Swift code, avoid ambient global state and behavior: do not add... new singletons for runtime state. Put state and behavior on a constructable, injectable owning type."

Please consider moving this instance to a proper injectable owner, such as the AppDelegate or an AppEnvironment that gets passed down to the surface runtime dependencies, instead of maintaining it globally on GhosttyApp.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Sources/GhosttyTerminalView.swift` around lines 384 - 385, Remove the static
singleton declaration agentTerminalStateRuntime from GhosttyTerminalView and
move its ownership to a constructable, injectable runtime owner such as
AppDelegate or AppEnvironment. Pass that instance through the surface runtime
dependencies to the consumers that need it, preserving main-actor isolation
without introducing ambient global state.

Source: Coding guidelines

Comment on lines +18 to +23
if profile.lifecycleAuthoritative {
if lifecycle[profile.statusKey] == nil { screen.append(state) }
} else {
lifecycle.removeValue(forKey: profile.statusKey)
screen.append(state)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

Remove the unreliable screen fallback for authoritative profiles.

When a profile is lifecycleAuthoritative, the UI must trust the single authoritative lifecycle record. Falling back to the screen-detected state when the lifecycle signal is absent violates the strict single-source-of-truth requirement. As per path instructions, "avoid “best effort” heuristics or unreliable fallback branches... replace it with the single authoritative record... failing closed when reliable signal is missing."

🐛 Proposed fix
             if profile.lifecycleAuthoritative {
-                if lifecycle[profile.statusKey] == nil { screen.append(state) }
+                continue
             } else {
                 lifecycle.removeValue(forKey: profile.statusKey)
                 screen.append(state)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if profile.lifecycleAuthoritative {
if lifecycle[profile.statusKey] == nil { screen.append(state) }
} else {
lifecycle.removeValue(forKey: profile.statusKey)
screen.append(state)
}
if profile.lifecycleAuthoritative {
continue
} else {
lifecycle.removeValue(forKey: profile.statusKey)
screen.append(state)
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Sources/Workspace`+AgentTerminalStateDetection.swift around lines 18 - 23,
Update the lifecycleAuthoritative branch to stop appending the screen-detected
state when lifecycle[profile.statusKey] is absent. Treat the authoritative
lifecycle record as the sole source of truth and fail closed when that record is
missing; leave the non-authoritative branch unchanged.

Source: Path instructions

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
Sources/AgentTerminalStateRuntime.swift (1)

25-30: 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Serialize teardown before reinstalling the same surface.

A new install can start while an earlier drop task is pending. That stale teardown subsequently calls stop(surfaceID:) and remove(surfaceID:), destroying the new registration and cache. Await the prior teardown before starting the next generation, or generation-gate these destructive operations in one per-surface owner.

As per coding guidelines, Swift architecture changes must preserve clear ownership and lifecycle invariants rather than leaving races representable.

Also applies to: 45-53

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Sources/AgentTerminalStateRuntime.swift` around lines 25 - 30, The
install/drop lifecycle for each surface can race, allowing stale teardown to
remove a newer registration and cache. Update install and the corresponding drop
task around classificationWorker, registrationTasks, and
stop(surfaceID:)/remove(surfaceID:) to serialize teardown before reinstalling
the same surface, or generation-gate destructive operations through a single
per-surface owner; ensure only the current registration can be stopped and
removed.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@Sources/AgentTerminalStateRuntime.swift`:
- Around line 25-30: The install/drop lifecycle for each surface can race,
allowing stale teardown to remove a newer registration and cache. Update install
and the corresponding drop task around classificationWorker, registrationTasks,
and stop(surfaceID:)/remove(surfaceID:) to serialize teardown before
reinstalling the same surface, or generation-gate destructive operations through
a single per-surface owner; ensure only the current registration can be stopped
and removed.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 923ea4f6-c6ea-478b-9be6-d8cd5b99553a

📥 Commits

Reviewing files that changed from the base of the PR and between 12d4886 and 0dab247.

📒 Files selected for processing (11)
  • Packages/macOS/CmuxTerminalCore/Sources/CmuxTerminalCore/AgentState/AgentTerminalProcessInspector.swift
  • Packages/macOS/CmuxTerminalCore/Sources/CmuxTerminalCore/AgentState/AgentTerminalProfileCatalog.swift
  • Packages/macOS/CmuxTerminalCore/Sources/CmuxTerminalCore/AgentState/AgentTerminalScreenSnapshot.swift
  • Packages/macOS/CmuxTerminalCore/Sources/CmuxTerminalCore/AgentState/AgentTerminalStateClassifier.swift
  • Packages/macOS/CmuxTerminalCore/Tests/CmuxTerminalCoreTests/AgentTerminalStateClassifierTests.swift
  • Sources/AgentTerminalStateRuntime.swift
  • Sources/AgentTerminalStateSurfaceObserver.swift
  • Sources/GhosttyTerminalView.swift
  • Sources/TerminalController.swift
  • Sources/Workspace+AgentTerminalStateDetection.swift
  • cmux.xcodeproj/project.pbxproj
💤 Files with no reviewable changes (3)
  • Packages/macOS/CmuxTerminalCore/Sources/CmuxTerminalCore/AgentState/AgentTerminalScreenSnapshot.swift
  • Sources/AgentTerminalStateSurfaceObserver.swift
  • cmux.xcodeproj/project.pbxproj

Comment on lines +9 to +25
!isManualKey($0.key) && !isDetectionKey($0.key)
}
var screen: [AgentHibernationLifecycleState] = []
for (key, state) in panelStates where isDetectionKey(key) {
guard let familyID = detectionFamilyID(key: key),
let profile = AgentTerminalProfileCatalog.builtIn.profile(id: familyID) else {
screen.append(state)
continue
}
if profile.lifecycleAuthoritative {
if lifecycle[profile.statusKey] == nil { screen.append(state) }
} else {
lifecycle.removeValue(forKey: profile.statusKey)
screen.append(state)
}
}
return Array(lifecycle.values) + screen

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Screen detection overrides lifecycle .running for non-authoritative agents via the .idle classifier fallback

resolvedStates unconditionally removes an existing lifecycle state for non-authoritative profiles (lifecycle.removeValue(forKey: profile.statusKey)) and substitutes the screen-detected state. The screen classifier falls through to .idle for any recognized agent whose current terminal output doesn't match a working-evidence group (e.g., the agent is processing in a background tool call and the "esc to interrupt" line has already scrolled off). For pre-existing non-authoritative status keys already in allowedStatusKeys — codex, copilot, cursor, amp, grok, etc. — that have a lifecycle integration writing .running, a single evaluate cycle where no working pattern fires would replace .running with .idle, trigger recordDetectedAgentLifecycleChange, and hand an incorrect idle signal to the hibernation controller.

A safe fix would guard the override: skip removeValue when the screen state is .idle and a lifecycle .running is already present, or treat the screen .idle default as .unknown (only store non-idle positive evidence from screen detection).

Rule Used: Flag correctness-critical detection/identity deriv... (source)

@lawrencecchen
lawrencecchen force-pushed the feat-agent-terminal-state-detection branch from cde697f to 8146f62 Compare July 17, 2026 04:34

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
Packages/macOS/CmuxTerminalCore/Sources/CmuxTerminalCore/AgentState/AgentTerminalStateClassifier.swift (1)

26-42: 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Fail closed instead of deriving agent identity and state from unreliable heuristics.

The classifier uses best-effort guesses for correctness-critical identity and state, violating the single source of truth requirement. As per path instructions, correctness-critical values must use one reliable structured source (such as direct executable identity or a registered launch hint), and missing signals must fail closed rather than relying on fallbacks.

  • Packages/macOS/CmuxTerminalCore/Sources/CmuxTerminalCore/AgentState/AgentTerminalStateClassifier.swift#L26-L42: replace arbitrary path and argv substring recognition with direct executable identity or a registered launch hint.
  • Packages/macOS/CmuxTerminalCore/Sources/CmuxTerminalCore/AgentState/AgentTerminalStateClassifier.swift#L65-L70: require explicit idle evidence and return .unknown instead of falling back to .idle.
  • Packages/macOS/CmuxTerminalCore/Tests/CmuxTerminalCoreTests/AgentTerminalStateClassifierTests.swift#L41-L44: update or remove this test to expect .unknown when explicit evidence is absent.
  • Packages/macOS/CmuxTerminalCore/Tests/CmuxTerminalCoreTests/AgentTerminalStateClassifierTests.swift#L53-L60: update or remove this test to avoid asserting unreliable argv substring matching.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@Packages/macOS/CmuxTerminalCore/Sources/CmuxTerminalCore/AgentState/AgentTerminalStateClassifier.swift`
around lines 26 - 42, Make AgentTerminalStateClassifier fail closed: replace
normalized path and argv substring matching with only direct executable identity
or a registered launch hint, and return nil when neither reliable signal exists.
In the classifier’s state resolution, require explicit idle evidence and return
.unknown rather than defaulting to .idle. Update
AgentTerminalStateClassifierTests at
Packages/macOS/CmuxTerminalCore/Tests/CmuxTerminalCoreTests/AgentTerminalStateClassifierTests.swift:41-44
to expect .unknown without explicit evidence, and at :53-60 to remove or revise
assertions based on unreliable argv substring matching.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In
`@Packages/macOS/CmuxTerminalCore/Sources/CmuxTerminalCore/AgentState/AgentTerminalStateClassifier.swift`:
- Around line 26-42: Make AgentTerminalStateClassifier fail closed: replace
normalized path and argv substring matching with only direct executable identity
or a registered launch hint, and return nil when neither reliable signal exists.
In the classifier’s state resolution, require explicit idle evidence and return
.unknown rather than defaulting to .idle. Update
AgentTerminalStateClassifierTests at
Packages/macOS/CmuxTerminalCore/Tests/CmuxTerminalCoreTests/AgentTerminalStateClassifierTests.swift:41-44
to expect .unknown without explicit evidence, and at :53-60 to remove or revise
assertions based on unreliable argv substring matching.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 78e0a815-635b-4273-b08c-f2788e7f9659

📥 Commits

Reviewing files that changed from the base of the PR and between 0dab247 and cde697f.

📒 Files selected for processing (2)
  • Packages/macOS/CmuxTerminalCore/Sources/CmuxTerminalCore/AgentState/AgentTerminalStateClassifier.swift
  • Packages/macOS/CmuxTerminalCore/Tests/CmuxTerminalCoreTests/AgentTerminalStateClassifierTests.swift

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
Packages/macOS/CmuxTerminalCore/Sources/CmuxTerminalCore/AgentState/AgentTerminalStateClassifier.swift (1)

26-31: 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Fail closed instead of deriving agent identity and state from unreliable heuristics.

As per path instructions, correctness-critical identity and state must come from a single reliable structured source. Do not use path or argv substring heuristics to guess identity, and do not fall back to an assumed .idle state when explicit evidence is missing.

  • Packages/macOS/CmuxTerminalCore/Sources/CmuxTerminalCore/AgentState/AgentTerminalStateClassifier.swift#L26-L31: remove the path substring heuristic.
  • Packages/macOS/CmuxTerminalCore/Sources/CmuxTerminalCore/AgentState/AgentTerminalStateClassifier.swift#L38-L42: remove the argv substring heuristic.
  • Packages/macOS/CmuxTerminalCore/Sources/CmuxTerminalCore/AgentState/AgentTerminalStateClassifier.swift#L65-L70: require explicit idle evidence from the profile and return .unknown otherwise.
  • Packages/macOS/CmuxTerminalCore/Tests/CmuxTerminalCoreTests/AgentTerminalStateClassifierTests.swift#L36-L44: update the assertion to expect .unknown instead of .idle.
  • Packages/macOS/CmuxTerminalCore/Tests/CmuxTerminalCoreTests/AgentTerminalStateClassifierTests.swift#L53-L60: remove or adapt the test since generic wrappers without reliable hints should not guess identity based on arguments.
  • Packages/macOS/CmuxTerminalCore/Tests/CmuxTerminalCoreTests/AgentTerminalStateClassifierTests.swift#L106-L114: remove or adapt the test since versioned paths without reliable hints should not guess identity based on path substrings.
  • Packages/macOS/CmuxTerminalCore/Tests/CmuxTerminalCoreTests/AgentTerminalStateClassifierTests.swift#L180-L190: update the assertions to expect .unknown.
  • Packages/macOS/CmuxTerminalCore/Tests/CmuxTerminalCoreTests/AgentTerminalStateClassifierTests.swift#L192-L196: update the assertion to expect .unknown.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@Packages/macOS/CmuxTerminalCore/Sources/CmuxTerminalCore/AgentState/AgentTerminalStateClassifier.swift`
around lines 26 - 31, AgentTerminalStateClassifier must fail closed: remove the
normalized-path and argv substring heuristics, require explicit idle evidence
from the matched profile, and return .unknown when evidence is absent. In
AgentTerminalStateClassifierTests, update affected expectations from .idle to
.unknown and remove or adapt generic-wrapper and versioned-path tests so they no
longer infer identity from arguments or paths. Apply these changes at
Packages/macOS/CmuxTerminalCore/Sources/CmuxTerminalCore/AgentState/AgentTerminalStateClassifier.swift
(26-31, 38-42, 65-70) and
Packages/macOS/CmuxTerminalCore/Tests/CmuxTerminalCoreTests/AgentTerminalStateClassifierTests.swift
(36-44, 53-60, 106-114, 180-190, 192-196).

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In
`@Packages/macOS/CmuxTerminalCore/Sources/CmuxTerminalCore/AgentState/AgentTerminalStateClassifier.swift`:
- Around line 26-31: AgentTerminalStateClassifier must fail closed: remove the
normalized-path and argv substring heuristics, require explicit idle evidence
from the matched profile, and return .unknown when evidence is absent. In
AgentTerminalStateClassifierTests, update affected expectations from .idle to
.unknown and remove or adapt generic-wrapper and versioned-path tests so they no
longer infer identity from arguments or paths. Apply these changes at
Packages/macOS/CmuxTerminalCore/Sources/CmuxTerminalCore/AgentState/AgentTerminalStateClassifier.swift
(26-31, 38-42, 65-70) and
Packages/macOS/CmuxTerminalCore/Tests/CmuxTerminalCoreTests/AgentTerminalStateClassifierTests.swift
(36-44, 53-60, 106-114, 180-190, 192-196).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 7e2b12dd-feac-4f71-8096-471228441c0c

📥 Commits

Reviewing files that changed from the base of the PR and between cde697f and 8146f62.

📒 Files selected for processing (2)
  • Packages/macOS/CmuxTerminalCore/Sources/CmuxTerminalCore/AgentState/AgentTerminalStateClassifier.swift
  • Packages/macOS/CmuxTerminalCore/Tests/CmuxTerminalCoreTests/AgentTerminalStateClassifierTests.swift

@cursor

cursor Bot commented Jul 17, 2026

Copy link
Copy Markdown

Bugbot is paused — on-demand spend limit reached

Bugbot uses usage-based billing for this team and has hit its on-demand spend limit.

A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue.

@cursor

cursor Bot commented Jul 17, 2026

Copy link
Copy Markdown

Bugbot is paused — on-demand spend limit reached

Bugbot uses usage-based billing for this team and has hit its on-demand spend limit.

A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue.

@lawrencecchen

Copy link
Copy Markdown
Contributor Author

Folded into #7970 so the live terminal observations and session lineage ship through one cmux agents interface.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant