Skip to content
Closed
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
4 changes: 4 additions & 0 deletions GhosttyTabs.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
A5001540 /* PortScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5001541 /* PortScanner.swift */; };
A5001542 /* TerminalImageTransfer.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5001544 /* TerminalImageTransfer.swift */; };
A5001543 /* TerminalSSHSessionDetector.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5001545 /* TerminalSSHSessionDetector.swift */; };
A5001547 /* TerminalAgentDetector.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5001546 /* TerminalAgentDetector.swift */; };
A5001006 /* GhosttyKit.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = A5001016 /* GhosttyKit.xcframework */; };
A5001007 /* TerminalController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5001019 /* TerminalController.swift */; };
A5001500 /* CmuxWebView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5001510 /* CmuxWebView.swift */; };
Expand Down Expand Up @@ -220,6 +221,7 @@
A5001541 /* PortScanner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PortScanner.swift; sourceTree = "<group>"; };
A5001544 /* TerminalImageTransfer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TerminalImageTransfer.swift; sourceTree = "<group>"; };
A5001545 /* TerminalSSHSessionDetector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TerminalSSHSessionDetector.swift; sourceTree = "<group>"; };
A5001546 /* TerminalAgentDetector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TerminalAgentDetector.swift; sourceTree = "<group>"; };
A5001016 /* GhosttyKit.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = GhosttyKit.xcframework; sourceTree = "<group>"; };
A5001017 /* ghostty.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ghostty.h; sourceTree = "<group>"; };
A5001018 /* cmux-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "cmux-Bridging-Header.h"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -477,6 +479,7 @@
A5001541 /* PortScanner.swift */,
A5001544 /* TerminalImageTransfer.swift */,
A5001545 /* TerminalSSHSessionDetector.swift */,
A5001546 /* TerminalAgentDetector.swift */,
A5001225 /* SocketControlSettings.swift */,
A5001600 /* SentryHelper.swift */,
A5001620 /* AppleScriptSupport.swift */,
Expand Down Expand Up @@ -803,6 +806,7 @@
A5001540 /* PortScanner.swift in Sources */,
A5001542 /* TerminalImageTransfer.swift in Sources */,
A5001543 /* TerminalSSHSessionDetector.swift in Sources */,
A5001547 /* TerminalAgentDetector.swift in Sources */,
A5001226 /* SocketControlSettings.swift in Sources */,
A5001601 /* SentryHelper.swift in Sources */,
A5001621 /* AppleScriptSupport.swift in Sources */,
Expand Down
38 changes: 37 additions & 1 deletion Sources/TabManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1991,8 +1991,19 @@ class TabManager: ObservableObject {
let insertIndex = newTabInsertIndex(snapshot: snapshot, placementOverride: placementOverride)
let ordinal = Self.nextPortOrdinal
Self.nextPortOrdinal += 1
// Derive workspace title: explicit title > directory basename > "Terminal N"
let derivedTitle: String = {
if let title { return title }
if let dir = explicitWorkingDirectory {
let basename = URL(fileURLWithPath: dir).lastPathComponent
if !basename.isEmpty, basename != "/" {
return basename
}
}
return "Terminal \(nextTabCount)"
}()
Comment on lines +1994 to +2004

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Localize the new default workspace title.

Line 2003 introduces a new bare UI string, so this path will stay English in localized builds. Please route the fallback through String(localized:defaultValue:) and add the key to Resources/Localizable.xcstrings.

🌐 Suggested fix
-                return "Terminal \(nextTabCount)"
+                return String(
+                    localized: "workspace.defaultTitle",
+                    defaultValue: "Terminal \(nextTabCount)"
+                )
As per coding guidelines, "All user-facing strings must be localized. Use `String(localized: "key.name", defaultValue: "English text")` for every string shown in the UI."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/TabManager.swift` around lines 1994 - 2004, The fallback literal
"Terminal \(nextTabCount)" in the derivedTitle closure must be localized:
replace that bare string with a localized lookup (e.g. obtain a localized
template via String(localized: "terminal.default", defaultValue: "Terminal %d")
and format it with nextTabCount using String(format:) or
LocalizedStringKey-style formatting) inside the derivedTitle closure; update the
Resources/Localizable.xcstrings to add the "terminal.default" key with the
default "Terminal %d" entry so the formatted title shown by
TabManager/derivedTitle is localized.

let newWorkspace = makeWorkspaceForCreation(
title: title ?? "Terminal \(nextTabCount)",
title: derivedTitle,
workingDirectory: workingDirectory,
portOrdinal: ordinal,
configTemplate: inheritedConfig,
Expand Down Expand Up @@ -4615,6 +4626,31 @@ class TabManager: ObservableObject {
if selectedTabId == tabId && tab.focusedPanelId == panelId {
updateWindowTitle(for: tab)
}

// If the title matches a known agent binary, try to enhance it with task info
if TerminalAgentDetector.isKnownAgentName(title),
tab.panelCustomTitles[panelId] == nil,
let ttyName = tab.surfaceTTYNames[panelId] {
Comment on lines +4631 to +4633

@cubic-dev-ai cubic-dev-ai Bot Apr 12, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1: This triggers repeated ps spawns and visible title flickering. When the shell re-broadcasts the raw agent name (e.g. "claude"), updatePanelTitle overwrites the enhanced title "claude: Fix auth bug" back to "claude", which re-matches isKnownAgentName, spawning another background detection. The user sees the title briefly revert on each OSC cycle. Add a guard to bail out if the current panel title already has an enhanced form (e.g. starts with title + ": ").

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At Sources/TabManager.swift, line 4631:

<comment>This triggers repeated `ps` spawns and visible title flickering. When the shell re-broadcasts the raw agent name (e.g. `"claude"`), `updatePanelTitle` overwrites the enhanced title `"claude: Fix auth bug"` back to `"claude"`, which re-matches `isKnownAgentName`, spawning another background detection. The user sees the title briefly revert on each OSC cycle. Add a guard to bail out if the current panel title already has an enhanced form (e.g. starts with `title + ": "`).</comment>

<file context>
@@ -4615,6 +4626,31 @@ class TabManager: ObservableObject {
         }
+
+        // If the title matches a known agent binary, try to enhance it with task info
+        if TerminalAgentDetector.isKnownAgentName(title),
+           tab.panelCustomTitles[panelId] == nil,
+           let ttyName = tab.surfaceTTYNames[panelId] {
</file context>
Suggested change
if TerminalAgentDetector.isKnownAgentName(title),
tab.panelCustomTitles[panelId] == nil,
let ttyName = tab.surfaceTTYNames[panelId] {
if TerminalAgentDetector.isKnownAgentName(title),
tab.panelCustomTitles[panelId] == nil,
let ttyName = tab.surfaceTTYNames[panelId],
!(tab.panelTitles[panelId]?.hasPrefix(title + ": ") ?? false) {
Fix with Cubic

enhanceAgentTitle(tabId: tabId, panelId: panelId, ttyName: ttyName)
}
Comment on lines +4631 to +4635

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 Title flickering and repeated ps spawns on every OSC update

enhanceAgentTitle is triggered on every call to updatePanelTitle where the title is a known agent name. The enhanced title is written back via tab.updatePanelTitle (the workspace method directly), not through the TabManager coalescer. When the next OSC update arrives with "claude" again — which happens any time the shell re-broadcasts the foreground process name, or when the agent itself refreshes its own terminal title — tab.updatePanelTitle returns true (changed from "claude: task" back to "claude"), so isKnownAgentName matches again and another background DispatchQueue.global task is spawned, running ps again. The user sees the tab title briefly revert to "claude" on each such OSC cycle.

A minimal fix is to bail out early if the title is already in enhanced form for this panel:

// If the title matches a known agent binary, try to enhance it with task info
if TerminalAgentDetector.isKnownAgentName(title),
   tab.panelCustomTitles[panelId] == nil,
   let ttyName = tab.surfaceTTYNames[panelId],
   !(tab.panelTitles[panelId]?.hasPrefix(title + ": ") ?? false) {
    enhanceAgentTitle(tabId: tabId, panelId: panelId, ttyName: ttyName)
}

This stops re-detection once an enhanced title is already in place and only re-runs when the raw agent name reappears after a session change.

}

/// Asynchronously detect the agent's task from process arguments and update the panel title.
private func enhanceAgentTitle(tabId: UUID, panelId: UUID, ttyName: String) {
DispatchQueue.global(qos: .utility).async { [weak self] in
guard let agent = TerminalAgentDetector.detect(forTTY: ttyName),
let task = agent.taskDescription, !task.isEmpty else { return }
let enhanced = "\(agent.executableName): \(task)"
DispatchQueue.main.async { [weak self] in
guard let self,
let tab = self.tabs.first(where: { $0.id == tabId }),
tab.panelCustomTitles[panelId] == nil else { return }
_ = tab.updatePanelTitle(panelId: panelId, title: enhanced)

@cubic-dev-ai cubic-dev-ai Bot Apr 12, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: Async agent title enhancement can apply stale results and overwrite newer panel title state because completion lacks title/TTY consistency checks.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At Sources/TabManager.swift, line 4648:

<comment>Async agent title enhancement can apply stale results and overwrite newer panel title state because completion lacks title/TTY consistency checks.</comment>

<file context>
@@ -4615,6 +4626,31 @@ class TabManager: ObservableObject {
+                guard let self,
+                      let tab = self.tabs.first(where: { $0.id == tabId }),
+                      tab.panelCustomTitles[panelId] == nil else { return }
+                _ = tab.updatePanelTitle(panelId: panelId, title: enhanced)
+                if self.selectedTabId == tabId && tab.focusedPanelId == panelId {
+                    self.updateWindowTitle(for: tab)
</file context>
Fix with Cubic

if self.selectedTabId == tabId && tab.focusedPanelId == panelId {
self.updateWindowTitle(for: tab)
}
}
}
}
Comment on lines +4630 to 4654

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Revalidate the title/TTY before applying the async enhancement.

Lines 4644-4648 only re-check panelCustomTitles. If a newer OSC/process title lands, or surfaceTTYNames[panelId] changes while detection is running, the late callback will still write a stale agent: task title back and break the intended precedence.

🧵 Suggested fix
-            enhanceAgentTitle(tabId: tabId, panelId: panelId, ttyName: ttyName)
+            enhanceAgentTitle(
+                tabId: tabId,
+                panelId: panelId,
+                ttyName: ttyName,
+                expectedBaseTitle: title
+            )
         }
     }
 
     /// Asynchronously detect the agent's task from process arguments and update the panel title.
-    private func enhanceAgentTitle(tabId: UUID, panelId: UUID, ttyName: String) {
+    private func enhanceAgentTitle(
+        tabId: UUID,
+        panelId: UUID,
+        ttyName: String,
+        expectedBaseTitle: String
+    ) {
         DispatchQueue.global(qos: .utility).async { [weak self] in
             guard let agent = TerminalAgentDetector.detect(forTTY: ttyName),
                   let task = agent.taskDescription, !task.isEmpty else { return }
             let enhanced = "\(agent.executableName): \(task)"
             DispatchQueue.main.async { [weak self] in
                 guard let self,
                       let tab = self.tabs.first(where: { $0.id == tabId }),
-                      tab.panelCustomTitles[panelId] == nil else { return }
+                      tab.panelCustomTitles[panelId] == nil,
+                      tab.surfaceTTYNames[panelId] == ttyName,
+                      tab.panelTitles[panelId] == expectedBaseTitle else { return }
                 _ = tab.updatePanelTitle(panelId: panelId, title: enhanced)
                 if self.selectedTabId == tabId && tab.focusedPanelId == panelId {
                     self.updateWindowTitle(for: tab)
                 }
             }
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/TabManager.swift` around lines 4630 - 4654, Pass and capture the
panel's current title when calling enhanceAgentTitle and, before applying the
async update, revalidate that the tab still exists, panelCustomTitles[panelId]
is nil, surfaceTTYNames[panelId] still equals the original ttyName, and the
current panel title still equals the original title that matched the agent name;
only then call updatePanelTitle and updateWindowTitle. Specifically, change
enhanceAgentTitle(tabId:panelId:ttyName:) to accept the originalTitle (or
capture it) and in the DispatchQueue.main.async block check
tab.surfaceTTYNames[panelId] == ttyName and tab.currentPanelTitle(for: panelId)
== originalTitle (plus the existing panelCustomTitles check) before writing the
enhanced title.


func focusedSurfaceTitleDidChange(tabId: UUID) {
Expand Down
165 changes: 165 additions & 0 deletions Sources/TerminalAgentDetector.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
import Foundation
import Darwin

/// Detects AI coding agent processes running in a terminal and extracts
/// their task descriptions from command-line arguments.
enum TerminalAgentDetector {

struct DetectedAgent {
let executableName: String // e.g. "claude", "codex"
let taskDescription: String? // extracted from process args
let pid: Int32
}

/// Known agent binary names (matched case-insensitively against ucomm from ps).
static let knownAgentBinaries: Set<String> = [
"claude", "codex", "gemini", "opencode", "aider", "ft-claude",
]

/// Quick check whether a terminal title matches a known agent binary name.
static func isKnownAgentName(_ name: String) -> Bool {
knownAgentBinaries.contains(name.lowercased())
}

/// Detect a foreground agent process on the given TTY device.
/// Returns `nil` if no known agent is in the foreground process group.
static func detect(forTTY ttyName: String) -> DetectedAgent? {
let snapshots = processSnapshots(forTTY: ttyName)
// Find foreground processes (pgid == tpgid) that are known agents
let foreground = snapshots.filter { $0.pgid == $0.tpgid }

@cubic-dev-ai cubic-dev-ai Bot Apr 12, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: Missing positive-value guards before the foreground check. When pgid and tpgid are both 0 (background/daemon processes within the same TTY session), pgid == tpgid evaluates to true, falsely treating them as foreground. The existing TerminalSSHSessionDetector guards against this with pgid > 0 && tpgid > 0.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At Sources/TerminalAgentDetector.swift, line 29:

<comment>Missing positive-value guards before the foreground check. When `pgid` and `tpgid` are both `0` (background/daemon processes within the same TTY session), `pgid == tpgid` evaluates to `true`, falsely treating them as foreground. The existing `TerminalSSHSessionDetector` guards against this with `pgid > 0 && tpgid > 0`.</comment>

<file context>
@@ -0,0 +1,165 @@
+    static func detect(forTTY ttyName: String) -> DetectedAgent? {
+        let snapshots = processSnapshots(forTTY: ttyName)
+        // Find foreground processes (pgid == tpgid) that are known agents
+        let foreground = snapshots.filter { $0.pgid == $0.tpgid }
+        guard let match = foreground.first(where: { knownAgentBinaries.contains($0.executableName) }) else {
+            return nil
</file context>
Suggested change
let foreground = snapshots.filter { $0.pgid == $0.tpgid }
let foreground = snapshots.filter { $0.pgid > 0 && $0.tpgid > 0 && $0.pgid == $0.tpgid }
Fix with Cubic

guard let match = foreground.first(where: { knownAgentBinaries.contains($0.executableName) }) else {
return nil
}
let args = commandLineArguments(forPID: match.pid)
let task = args.flatMap { extractTaskDescription(from: $0, agent: match.executableName) }
return DetectedAgent(executableName: match.executableName, taskDescription: task, pid: match.pid)
}

Comment on lines +32 to +37

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 Missing pgid > 0 && tpgid > 0 foreground guards from TerminalSSHSessionDetector

TerminalSSHSessionDetector.isForegroundSSHProcess explicitly guards process.pgid > 0 && process.tpgid > 0 before comparing them — this ensures orphaned or daemon processes (where tpgid may be 0 or -1) are never falsely treated as foreground. The agent detector omits these bounds checks. While ps -t <tty> already filters by session, background processes within the same session can appear with tpgid == 0, and pgid == tpgid would then incorrectly match them.

let foreground = snapshots.filter { $0.pgid > 0 && $0.tpgid > 0 && $0.pgid == $0.tpgid }

// MARK: - Process enumeration (same pattern as TerminalSSHSessionDetector)

private struct ProcessSnapshot {
let pid: Int32
let pgid: Int32
let tpgid: Int32
let executableName: String
}

private static let psPath = "/bin/ps"

private static func processSnapshots(forTTY ttyName: String) -> [ProcessSnapshot] {
let process = Process()
let pipe = Pipe()
process.executableURL = URL(fileURLWithPath: psPath)
process.arguments = ["-ww", "-t", ttyName, "-o", "pid=,pgid=,tpgid=,ucomm="]
process.standardInput = FileHandle.nullDevice
process.standardOutput = pipe
process.standardError = FileHandle.nullDevice

do { try process.run() } catch { return [] }

let data = pipe.fileHandleForReading.readDataToEndOfFile()
process.waitUntilExit()
guard process.terminationStatus == 0,
let output = String(data: data, encoding: .utf8) else { return [] }

return output.split(separator: "\n").compactMap { line in
let parts = line.split(maxSplits: 3, whereSeparator: \.isWhitespace)
guard parts.count == 4,
let pid = Int32(parts[0]),
let pgid = Int32(parts[1]),
let tpgid = Int32(parts[2]) else { return nil }
return ProcessSnapshot(
pid: pid, pgid: pgid, tpgid: tpgid,
executableName: String(parts[3]).trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
)
}
}

// MARK: - Command-line argument reading (KERN_PROCARGS2)

private static func commandLineArguments(forPID pid: Int32) -> [String]? {
var mib = [CTL_KERN, KERN_PROCARGS2, pid]
var size: size_t = 0
guard sysctl(&mib, u_int(mib.count), nil, &size, nil, 0) == 0, size > 4 else { return nil }

var buffer = [UInt8](repeating: 0, count: size)
let ok = buffer.withUnsafeMutableBytes { raw in
sysctl(&mib, u_int(mib.count), raw.baseAddress, &size, nil, 0) == 0
}
guard ok else { return nil }
return parseKernProcArgs(Array(buffer.prefix(Int(size))))
}

private static func parseKernProcArgs(_ bytes: [UInt8]) -> [String]? {
guard bytes.count > 4 else { return nil }
var argcRaw: Int32 = 0
withUnsafeMutableBytes(of: &argcRaw) { $0.copyBytes(from: bytes.prefix(4)) }
let argc = Int(Int32(littleEndian: argcRaw))
guard argc > 0 else { return nil }

// Skip past argc + executable path + padding nulls
var i = 4
while i < bytes.count, bytes[i] != 0 { i += 1 }
while i < bytes.count, bytes[i] == 0 { i += 1 }

var args: [String] = []
while i < bytes.count, args.count < argc {
let start = i
while i < bytes.count, bytes[i] != 0 { i += 1 }
guard let arg = String(bytes: bytes[start..<i], encoding: .utf8) else { return nil }
args.append(arg)
while i < bytes.count, bytes[i] == 0 { i += 1 }
}
return args.count == argc ? args : nil
}

// MARK: - Task description extraction

/// Extract a human-readable task description from the agent's command-line arguments.
/// Returns `nil` if no meaningful description can be determined (e.g. interactive mode).
private static func extractTaskDescription(from args: [String], agent: String) -> String? {
// args[0] is the executable path; positional args start from args[1..]
guard args.count > 1 else { return nil }
let tail = Array(args.dropFirst())

// For "codex exec <prompt>" and "opencode run <prompt>", skip the subcommand
let positional: [String]
if (agent == "codex" || agent == "opencode"), tail.count > 1 {
let sub = tail[0].lowercased()
if sub == "exec" || sub == "run" {
positional = Array(tail.dropFirst())
} else {
positional = tail
}
Comment on lines +127 to +133

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Subcommand-only agent invocations are misclassified as task text.

For codex exec / opencode run without a prompt argument, this path currently returns "exec"/"run" as taskDescription instead of nil.

Proposed fix
-        if (agent == "codex" || agent == "opencode"), tail.count > 1 {
-            let sub = tail[0].lowercased()
-            if sub == "exec" || sub == "run" {
-                positional = Array(tail.dropFirst())
-            } else {
-                positional = tail
-            }
-        } else {
-            positional = tail
-        }
+        if (agent == "codex" || agent == "opencode"), let sub = tail.first?.lowercased() {
+            if sub == "exec" || sub == "run" {
+                positional = Array(tail.dropFirst())
+            } else {
+                positional = tail
+            }
+        } else {
+            positional = tail
+        }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/TerminalAgentDetector.swift` around lines 127 - 133, The code in
TerminalAgentDetector handling agent == "codex" || agent == "opencode" currently
only special-cases subcommands when tail.count > 1, so invocations like "codex
exec" or "opencode run" (tail.count == 1) get misclassified as task text; update
the logic in the block that examines agent, tail and sets
positional/taskDescription so that when tail.count == 1 and the single tail
element (sub) lowercased is "exec" or "run" you treat it as a subcommand-only
invocation by setting positional to an empty array and ensuring taskDescription
is nil (instead of leaving the sub as task text). Reference variables: agent,
tail, sub, positional, taskDescription in TerminalAgentDetector.swift.

} else {
positional = tail
}

// Find first non-flag argument (doesn't start with -)
// Also skip known flag values (the argument after a flag that takes a value)
let flagsWithValue: Set<String> = [
"--model", "-m", "--settings", "--agent", "-c",
"--append-system-prompt", "--append-system-prompt-file",
"--system-prompt", "--system-prompt-file",
"--prompt-file",
]

var skipNext = false
for arg in positional {
if skipNext { skipNext = false; continue }
if arg.hasPrefix("-") {
if flagsWithValue.contains(arg) { skipNext = true }
continue
}
// Found a positional argument — this is likely the prompt/task
let trimmed = arg.trimmingCharacters(in: .whitespacesAndNewlines)
guard !trimmed.isEmpty else { continue }
// Truncate to keep tab names readable
if trimmed.count > 60 {
return String(trimmed.prefix(57)) + "..."
}
return trimmed
}
return nil
}
}
Comment on lines +88 to +165

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 parseKernProcArgs and commandLineArguments duplicated from TerminalSSHSessionDetector

Both implementations are functionally identical (just variable names differ). If the kernel procargs parsing logic ever needs a fix, it would need to be applied in two places. Consider extracting a shared KernProcArgsReader helper or moving the functions into a common TerminalProcessUtils namespace that both detectors can import.