Clear sidebar notification when user submits prompt (#821)
* Clear sidebar notification when user submits prompt in Claude Code Add UserPromptSubmit hook to the Claude Code wrapper that calls `cmux claude-hook prompt-submit`. This clears the workspace notification and sets status back to "Running" when the user addresses Claude's question, so the "waiting for input" preview in the sidebar goes away. Also adds --tab support to clear_notifications socket command and --workspace support to the clear-notifications CLI command for per-workspace notification clearing. Closes https://github.com/manaflow-ai/cmux/issues/799 * Address review feedback: stricter error handling - clear-notifications CLI: error on explicit --workspace failure instead of falling back to global clear. Env var still gracefully degrades. - prompt-submit hook: propagate sendV1Command errors instead of swallowing with try?. - clear_notifications socket: validate --tab flag is present before resolving, reject malformed args instead of falling back to selected tab. * Gate env workspace fallback on windowId == nil in clear-notifications Matches the pattern used by other CLI commands to avoid using CMUX_WORKSPACE_ID from the caller shell when --window targets a different window.
This commit is contained in:
parent
355012b252
commit
bfe843f0bd
3 changed files with 56 additions and 7 deletions
|
|
@ -1305,7 +1305,16 @@ struct CMUXCLI {
|
|||
}
|
||||
|
||||
case "clear-notifications":
|
||||
let response = try sendV1Command("clear_notifications", client: client)
|
||||
var socketCmd = "clear_notifications"
|
||||
if let wsFlag = optionValue(commandArgs, name: "--workspace") {
|
||||
let wsId = try resolveWorkspaceId(wsFlag, client: client)
|
||||
socketCmd += " --tab=\(wsId)"
|
||||
} else if windowId == nil,
|
||||
let envWs = ProcessInfo.processInfo.environment["CMUX_WORKSPACE_ID"],
|
||||
let wsId = try? resolveWorkspaceId(envWs, client: client) {
|
||||
socketCmd += " --tab=\(wsId)"
|
||||
}
|
||||
let response = try sendV1Command(socketCmd, client: client)
|
||||
print(response)
|
||||
|
||||
case "claude-hook":
|
||||
|
|
@ -4441,7 +4450,7 @@ struct CMUXCLI {
|
|||
"""
|
||||
case "claude-hook":
|
||||
return """
|
||||
Usage: cmux claude-hook <session-start|active|stop|idle|notification|notify> [flags]
|
||||
Usage: cmux claude-hook <session-start|active|stop|idle|notification|notify|prompt-submit> [flags]
|
||||
|
||||
Hook for Claude Code integration. Reads JSON from stdin.
|
||||
|
||||
|
|
@ -4452,6 +4461,7 @@ struct CMUXCLI {
|
|||
idle Alias for stop
|
||||
notification Forward a Claude notification
|
||||
notify Alias for notification
|
||||
prompt-submit Clear notification and set Running on user prompt
|
||||
|
||||
Flags:
|
||||
--workspace <id|ref> Target workspace (default: $CMUX_WORKSPACE_ID)
|
||||
|
|
@ -5700,6 +5710,24 @@ struct CMUXCLI {
|
|||
print("OK")
|
||||
}
|
||||
|
||||
case "prompt-submit":
|
||||
telemetry.breadcrumb("claude-hook.prompt-submit")
|
||||
var workspaceId = fallbackWorkspaceId
|
||||
if let sessionId = parsedInput.sessionId,
|
||||
let mapped = try? sessionStore.lookup(sessionId: sessionId),
|
||||
let mappedWorkspace = try? resolveWorkspaceIdForClaudeHook(mapped.workspaceId, client: client) {
|
||||
workspaceId = mappedWorkspace
|
||||
}
|
||||
_ = try sendV1Command("clear_notifications --tab=\(workspaceId)", client: client)
|
||||
try setClaudeStatus(
|
||||
client: client,
|
||||
workspaceId: workspaceId,
|
||||
value: "Running",
|
||||
icon: "bolt.fill",
|
||||
color: "#4C8DFF"
|
||||
)
|
||||
print("OK")
|
||||
|
||||
case "notification", "notify":
|
||||
telemetry.breadcrumb("claude-hook.notification")
|
||||
let summary = summarizeClaudeHookNotification(rawInput: rawInput)
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ done
|
|||
|
||||
# Build hooks settings JSON.
|
||||
# Claude Code merges --settings additively with the user's own settings.json.
|
||||
HOOKS_JSON='{"hooks":{"SessionStart":[{"matcher":"","hooks":[{"type":"command","command":"cmux claude-hook session-start","timeout":10}]}],"Stop":[{"matcher":"","hooks":[{"type":"command","command":"cmux claude-hook stop","timeout":10}]}],"Notification":[{"matcher":"","hooks":[{"type":"command","command":"cmux claude-hook notification","timeout":10}]}]}}'
|
||||
HOOKS_JSON='{"hooks":{"SessionStart":[{"matcher":"","hooks":[{"type":"command","command":"cmux claude-hook session-start","timeout":10}]}],"Stop":[{"matcher":"","hooks":[{"type":"command","command":"cmux claude-hook stop","timeout":10}]}],"Notification":[{"matcher":"","hooks":[{"type":"command","command":"cmux claude-hook notification","timeout":10}]}],"UserPromptSubmit":[{"matcher":"","hooks":[{"type":"command","command":"cmux claude-hook prompt-submit","timeout":10}]}]}}'
|
||||
|
||||
if [[ "$SKIP_SESSION_ID" == true ]]; then
|
||||
exec "$REAL_CLAUDE" --settings "$HOOKS_JSON" "$@"
|
||||
|
|
|
|||
|
|
@ -878,7 +878,7 @@ class TerminalController {
|
|||
return listNotifications()
|
||||
|
||||
case "clear_notifications":
|
||||
return clearNotifications()
|
||||
return clearNotifications(args)
|
||||
|
||||
case "set_app_focus":
|
||||
return setAppFocusOverride(args)
|
||||
|
|
@ -8790,7 +8790,7 @@ class TerminalController {
|
|||
notify_surface <id|idx> <payload> - Notify a specific surface
|
||||
notify_target <workspace_id> <surface_id> <payload> - Notify by workspace+surface
|
||||
list_notifications - List all notifications
|
||||
clear_notifications - Clear all notifications
|
||||
clear_notifications [--tab=X] - Clear notifications (all or per-tab)
|
||||
set_app_focus <active|inactive|clear> - Override app focus state
|
||||
simulate_app_active - Trigger app active handler
|
||||
set_status <key> <value> [--icon=X] [--color=#hex] [--url=X] [--priority=N] [--format=plain|markdown] [--tab=X] - Set a status entry
|
||||
|
|
@ -10124,9 +10124,30 @@ class TerminalController {
|
|||
return result.isEmpty ? "No notifications" : result
|
||||
}
|
||||
|
||||
private func clearNotifications() -> String {
|
||||
private func clearNotifications(_ args: String) -> String {
|
||||
let trimmed = args.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
if trimmed.isEmpty {
|
||||
DispatchQueue.main.sync {
|
||||
TerminalNotificationStore.shared.clearAll()
|
||||
}
|
||||
return "OK"
|
||||
}
|
||||
let parsed = parseOptions(trimmed)
|
||||
guard let tabOption = parsed.options["tab"],
|
||||
!tabOption.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else {
|
||||
return "ERROR: Usage: clear_notifications [--tab=X]"
|
||||
}
|
||||
var tabId: UUID?
|
||||
DispatchQueue.main.sync {
|
||||
TerminalNotificationStore.shared.clearAll()
|
||||
if let tab = resolveTabForReport(trimmed) {
|
||||
tabId = tab.id
|
||||
}
|
||||
}
|
||||
guard let tabId else {
|
||||
return "ERROR: Tab not found"
|
||||
}
|
||||
DispatchQueue.main.sync {
|
||||
TerminalNotificationStore.shared.clearNotifications(forTabId: tabId)
|
||||
}
|
||||
return "OK"
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue