Fix tmux notification attention routing
This commit is contained in:
parent
d4811650d7
commit
656786fb71
11 changed files with 1151 additions and 64 deletions
181
CLI/cmux.swift
181
CLI/cmux.swift
|
|
@ -1863,12 +1863,33 @@ struct CMUXCLI {
|
|||
|
||||
case "trigger-flash":
|
||||
let tfWsFlag = optionValue(commandArgs, name: "--workspace")
|
||||
let workspaceArg = tfWsFlag ?? (windowId == nil ? ProcessInfo.processInfo.environment["CMUX_WORKSPACE_ID"] : nil)
|
||||
let surfaceArg = optionValue(commandArgs, name: "--surface") ?? optionValue(commandArgs, name: "--panel") ?? (tfWsFlag == nil && windowId == nil ? ProcessInfo.processInfo.environment["CMUX_SURFACE_ID"] : nil)
|
||||
let explicitWorkspaceArg = tfWsFlag
|
||||
let callerWorkspaceArg = windowId == nil ? ProcessInfo.processInfo.environment["CMUX_WORKSPACE_ID"] : nil
|
||||
let workspaceArg = explicitWorkspaceArg ?? callerWorkspaceArg
|
||||
let explicitSurfaceArg = optionValue(commandArgs, name: "--surface") ?? optionValue(commandArgs, name: "--panel")
|
||||
let callerSurfaceArg = explicitWorkspaceArg == nil && windowId == nil
|
||||
? ProcessInfo.processInfo.environment["CMUX_SURFACE_ID"]
|
||||
: nil
|
||||
let surfaceArg = explicitSurfaceArg ?? callerSurfaceArg
|
||||
var params: [String: Any] = [:]
|
||||
let wsId = try normalizeWorkspaceHandle(workspaceArg, client: client)
|
||||
let wsId = try {
|
||||
if explicitWorkspaceArg != nil {
|
||||
return try normalizeWorkspaceHandle(workspaceArg, client: client)
|
||||
}
|
||||
return try resolveWorkspaceIdAllowingFallback(workspaceArg, client: client)
|
||||
}()
|
||||
if let wsId { params["workspace_id"] = wsId }
|
||||
let sfId = try normalizeSurfaceHandle(surfaceArg, client: client, workspaceHandle: wsId)
|
||||
let sfId = try {
|
||||
if explicitSurfaceArg != nil {
|
||||
return try normalizeSurfaceHandle(surfaceArg, client: client, workspaceHandle: wsId)
|
||||
}
|
||||
guard let wsId else { return nil }
|
||||
return try resolveSurfaceIdAllowingFallback(
|
||||
surfaceArg,
|
||||
workspaceId: wsId,
|
||||
client: client
|
||||
)
|
||||
}()
|
||||
if let sfId { params["surface_id"] = sfId }
|
||||
let payload = try client.sendV2(method: "surface.trigger_flash", params: params)
|
||||
printV2Payload(payload, jsonOutput: jsonOutput, idFormat: idFormat, fallbackText: v2OKSummary(payload, idFormat: idFormat))
|
||||
|
|
@ -2062,12 +2083,31 @@ struct CMUXCLI {
|
|||
let subtitle = optionValue(commandArgs, name: "--subtitle") ?? ""
|
||||
let body = optionValue(commandArgs, name: "--body") ?? ""
|
||||
|
||||
let notifyWsFlag = optionValue(commandArgs, name: "--workspace")
|
||||
let workspaceArg = notifyWsFlag ?? (windowId == nil ? ProcessInfo.processInfo.environment["CMUX_WORKSPACE_ID"] : nil)
|
||||
let surfaceArg = optionValue(commandArgs, name: "--surface") ?? (notifyWsFlag == nil && windowId == nil ? ProcessInfo.processInfo.environment["CMUX_SURFACE_ID"] : nil)
|
||||
let explicitWorkspaceArg = optionValue(commandArgs, name: "--workspace")
|
||||
let callerWorkspaceArg = windowId == nil ? ProcessInfo.processInfo.environment["CMUX_WORKSPACE_ID"] : nil
|
||||
let workspaceArg = explicitWorkspaceArg ?? callerWorkspaceArg
|
||||
let explicitSurfaceArg = optionValue(commandArgs, name: "--surface")
|
||||
let callerSurfaceArg = explicitWorkspaceArg == nil && windowId == nil
|
||||
? ProcessInfo.processInfo.environment["CMUX_SURFACE_ID"]
|
||||
: nil
|
||||
let surfaceArg = explicitSurfaceArg ?? callerSurfaceArg
|
||||
|
||||
let targetWorkspace = try resolveWorkspaceId(workspaceArg, client: client)
|
||||
let targetSurface = try resolveSurfaceId(surfaceArg, workspaceId: targetWorkspace, client: client)
|
||||
let targetWorkspace = try {
|
||||
if explicitWorkspaceArg != nil {
|
||||
return try resolveWorkspaceId(workspaceArg, client: client)
|
||||
}
|
||||
return try resolveWorkspaceIdAllowingFallback(workspaceArg, client: client)
|
||||
}()
|
||||
let targetSurface = try {
|
||||
if explicitSurfaceArg != nil {
|
||||
return try resolveSurfaceId(surfaceArg, workspaceId: targetWorkspace, client: client)
|
||||
}
|
||||
return try resolveSurfaceIdAllowingFallback(
|
||||
surfaceArg,
|
||||
workspaceId: targetWorkspace,
|
||||
client: client
|
||||
)
|
||||
}()
|
||||
|
||||
let payload = "\(title)|\(subtitle)|\(body)"
|
||||
let response = try sendV1Command("notify_target \(targetWorkspace) \(targetSurface) \(payload)", client: client)
|
||||
|
|
@ -10482,13 +10522,7 @@ struct CMUXCLI {
|
|||
}
|
||||
|
||||
private func resolveWorkspaceIdForClaudeHook(_ raw: String?, client: SocketClient) throws -> String {
|
||||
if let raw, !raw.isEmpty, let candidate = try? resolveWorkspaceId(raw, client: client) {
|
||||
let probe = try? client.sendV2(method: "surface.list", params: ["workspace_id": candidate])
|
||||
if probe != nil {
|
||||
return candidate
|
||||
}
|
||||
}
|
||||
return try resolveWorkspaceId(nil, client: client)
|
||||
try resolveWorkspaceIdAllowingFallback(raw, client: client)
|
||||
}
|
||||
|
||||
private func resolveSurfaceIdForClaudeHook(
|
||||
|
|
@ -10496,12 +10530,125 @@ struct CMUXCLI {
|
|||
workspaceId: String,
|
||||
client: SocketClient
|
||||
) throws -> String {
|
||||
if let raw, !raw.isEmpty, let candidate = try? resolveSurfaceId(raw, workspaceId: workspaceId, client: client) {
|
||||
try resolveSurfaceIdAllowingFallback(raw, workspaceId: workspaceId, client: client)
|
||||
}
|
||||
|
||||
private func resolveWorkspaceIdAllowingFallback(
|
||||
_ raw: String?,
|
||||
client: SocketClient
|
||||
) throws -> String {
|
||||
if let raw,
|
||||
!raw.isEmpty,
|
||||
let candidate = try? resolveWorkspaceId(raw, client: client),
|
||||
(try? client.sendV2(method: "surface.list", params: ["workspace_id": candidate])) != nil {
|
||||
return candidate
|
||||
}
|
||||
if let callerWorkspaceId = resolveCallerWorkspaceIdByTTY(client: client),
|
||||
(try? client.sendV2(method: "surface.list", params: ["workspace_id": callerWorkspaceId])) != nil {
|
||||
return callerWorkspaceId
|
||||
}
|
||||
return try resolveWorkspaceId(nil, client: client)
|
||||
}
|
||||
|
||||
private func resolveSurfaceIdAllowingFallback(
|
||||
_ raw: String?,
|
||||
workspaceId: String,
|
||||
client: SocketClient
|
||||
) throws -> String {
|
||||
if let raw,
|
||||
!raw.isEmpty,
|
||||
let candidate = try? resolveSurfaceId(raw, workspaceId: workspaceId, client: client),
|
||||
let listed = try? client.sendV2(method: "surface.list", params: ["workspace_id": workspaceId]) {
|
||||
let items = listed["surfaces"] as? [[String: Any]] ?? []
|
||||
if items.contains(where: {
|
||||
($0["id"] as? String) == candidate || ($0["ref"] as? String) == candidate
|
||||
}) {
|
||||
return candidate
|
||||
}
|
||||
}
|
||||
if let callerSurfaceId = resolveCallerSurfaceIdByTTY(workspaceId: workspaceId, client: client),
|
||||
let listed = try? client.sendV2(method: "surface.list", params: ["workspace_id": workspaceId]) {
|
||||
let items = listed["surfaces"] as? [[String: Any]] ?? []
|
||||
if items.contains(where: {
|
||||
($0["id"] as? String) == callerSurfaceId || ($0["ref"] as? String) == callerSurfaceId
|
||||
}) {
|
||||
return callerSurfaceId
|
||||
}
|
||||
}
|
||||
return try resolveSurfaceId(nil, workspaceId: workspaceId, client: client)
|
||||
}
|
||||
|
||||
private struct CallerTerminalBinding {
|
||||
let workspaceId: String
|
||||
let surfaceId: String
|
||||
}
|
||||
|
||||
private func resolveCallerWorkspaceIdByTTY(client: SocketClient) -> String? {
|
||||
resolveCallerTerminalBindingByTTY(client: client)?.workspaceId
|
||||
}
|
||||
|
||||
private func resolveCallerSurfaceIdByTTY(workspaceId: String, client: SocketClient) -> String? {
|
||||
guard let binding = resolveCallerTerminalBindingByTTY(client: client),
|
||||
binding.workspaceId == workspaceId else {
|
||||
return nil
|
||||
}
|
||||
return binding.surfaceId
|
||||
}
|
||||
|
||||
private func resolveCallerTerminalBindingByTTY(client: SocketClient) -> CallerTerminalBinding? {
|
||||
guard let ttyName = resolveCallerTTYName() else {
|
||||
return nil
|
||||
}
|
||||
guard let payload = try? client.sendV2(method: "debug.terminals") else {
|
||||
return nil
|
||||
}
|
||||
let terminals = payload["terminals"] as? [[String: Any]] ?? []
|
||||
for terminal in terminals {
|
||||
guard normalizedTTYName(terminal["tty"] as? String) == ttyName,
|
||||
let workspaceId = normalizedHandleValue(terminal["workspace_id"] as? String),
|
||||
let surfaceId = normalizedHandleValue(terminal["surface_id"] as? String) else {
|
||||
continue
|
||||
}
|
||||
return CallerTerminalBinding(workspaceId: workspaceId, surfaceId: surfaceId)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
private func resolveCallerTTYName() -> String? {
|
||||
let env = ProcessInfo.processInfo.environment
|
||||
for key in ["CMUX_CLI_TTY_NAME", "CMUX_TTY_NAME", "TTY", "SSH_TTY"] {
|
||||
if let ttyName = normalizedTTYName(env[key]) {
|
||||
return ttyName
|
||||
}
|
||||
}
|
||||
for fileDescriptor in [STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO] {
|
||||
if let rawTTYName = ttyname(fileDescriptor),
|
||||
let ttyName = normalizedTTYName(String(cString: rawTTYName)) {
|
||||
return ttyName
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
private func normalizedTTYName(_ raw: String?) -> String? {
|
||||
guard let trimmed = normalizedHandleValue(raw == "not a tty" ? nil : raw) else {
|
||||
return nil
|
||||
}
|
||||
let components = trimmed.split(separator: "/")
|
||||
if let last = components.last, !last.isEmpty {
|
||||
return String(last)
|
||||
}
|
||||
return trimmed
|
||||
}
|
||||
|
||||
private func normalizedHandleValue(_ raw: String?) -> String? {
|
||||
guard let raw = raw?.trimmingCharacters(in: .whitespacesAndNewlines),
|
||||
!raw.isEmpty else {
|
||||
return nil
|
||||
}
|
||||
return raw
|
||||
}
|
||||
|
||||
private func parseClaudeHookInput(rawInput: String) -> ClaudeHookParsedInput {
|
||||
let trimmed = rawInput.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
guard !trimmed.isEmpty,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue