Agent-aware tabs with hook-based status detection#4
Conversation
- Try inherited process environment first (instant for terminal launches) - Fall back to login shell only when tmux not found on PATH (.app bundle) - Use stdinNull to prevent interactive shell from blocking on stdin - Add 10s timeout with SendableResumeGuard for safe continuation handling - Use -l -i flags to source .zshrc (needed for mise/nix PATH activation)
Detect coding agents (Claude Code, Codex, Pi, Droid, Amp, OpenCode) running in tmux panes and auto-rename windows with agent identity. - AgentDetector: identify agents from pane_current_command + pattern-match capture-pane output for state (waiting, running, error, completed) - AgentTabNamer: auto-rename tmux windows (e.g. "🤖 claude"), restore original name when agent exits - Enriched notifications: "Claude Code Waiting for Input" instead of generic "Agent Waiting for Input" - Auto-upgrade WindowTag to .agent when agent process detected - RuntimeWindow.detectedAgent field for UI consumption - 33 new test assertions (208 total MoriTmux, 296 total MoriCore)
…ommand tmux pane_current_command is unreliable for agents like Claude Code that set terminal titles via OSC escapes. Instead: - Add pane_pid to TmuxPane and parser - AgentDetector.scanForAgentProcesses: single `ps` call to find agent child processes across all panes - WorkspaceManager passes child process map to AgentDetector.detect
- Claude Code: 🤖 → ✦ (Anthropic starburst) - Codex: 🧠 → 🟩 (OpenAI green)
…te tracking Replace the fragile capture-pane + pattern matching approach for agent state detection with a hook-based system where agents report their own state via tmux pane options. - Add hook scripts for Claude Code, Codex CLI, and Pi that set @mori-agent-state and @mori-agent-name pane options and rename tmux windows with status emojis (⚡ working / ✅ done) - Add AgentHookConfigurator that auto-installs hooks into ~/.claude/settings.json, ~/.codex/config.toml, and ~/.pi/agent/extensions/ on app launch - Read @mori-agent-state from pane format string during existing 5s poll instead of capture-pane + ps scan - Add stale state cleanup when agent exits (pane returns to shell) - Remove AgentTabNamer, AgentDetector state detection methods, scanForAgentProcesses, and all capture-pane state logic - Remove debug logging from NotificationManager and WorkspaceManager - 496 test assertions passing (200 tmux + 296 core)
~/Library/Application Support/ contains a space that breaks hook execution when Claude Code invokes the command via /bin/sh. Move hooks to ~/.config/mori/hooks/ instead.
`[ -z "$saved" ] && ...` returns exit code 1 when $saved is non-empty, which under `set -e` terminates the script. Use if/then/fi instead.
- Replace auto-install of agent hooks on launch with Settings > Agents page - Each agent (Claude Code, Codex CLI, Pi) has a toggle to enable/disable hooks - Add detection methods to check if hooks are currently installed - Add uninstall methods to cleanly remove hooks from agent configs - Use XDG_CONFIG_HOME for hooks dir (fallback: ~/.config/mori/hooks/) - Load hook scripts from SPM bundle resources instead of inline strings - Fix codex TOML placement: insert notify before [section] headers (top-level) - Fix codex hook: parse JSON arg from legacy notify (passes JSON as arg 1)
…ent Hooks - Pi extension installed to ~/.config/mori/ instead of ~/.pi/agent/extensions/ - Register extension path in Pi's settings.json (pi requires explicit listing) - Respect PI_CODING_AGENT_DIR env var for Pi config directory - Rename settings category from "Agents" to "Agent Hooks" - Update descriptions to clarify what each toggle modifies
2948861 to
c2e95dd
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 2948861657
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if let hookState = pane.agentState { | ||
| if isShell { | ||
| // Agent exited but pane options linger — clean up | ||
| await clearStaleAgentState(paneId: pane.paneId) | ||
| } else { |
There was a problem hiding this comment.
Preserve hook-reported completion before clearing shell panes
When an agent finishes before the next polling tick, pane_current_command is already back to zsh/bash even though @mori-agent-state is still set to done. In that common case this branch clears the pane options immediately and never maps the hook state to .waitingForInput, so completed turns do not show the waiting badge or fire the completion notification. The regression is specific to fast agent runs with the current 5s polling loop in coordinatedPoll().
Useful? React with 👍 / 👎.
| if let idx = lines.firstIndex(where: { $0.hasPrefix("notify") && $0.contains("[") }) { | ||
| // Replace existing top-level notify | ||
| lines[idx] = "notify = [\"\(hookPath)\"]" |
There was a problem hiding this comment.
Preserve existing Codex notify command when enabling hooks
If a user already has a notify = [...] entry in ~/.codex/config.toml, enabling Mori replaces that line with the Mori script, and uninstallCodexHook() later deletes it entirely. Because the previous value is never saved, toggling this setting silently breaks any pre-existing Codex notifier for existing users.
Useful? React with 👍 / 👎.
| private func mapHookState(_ state: String) -> AgentState { | ||
| switch state { | ||
| case "working": return .running | ||
| case "done": return .waitingForInput | ||
| default: return .none |
There was a problem hiding this comment.
Restore error-state detection for hooked agents
Hooked agents can no longer surface .error at all: the new hooks only write working/done, and this mapper converts those to .running/.waitingForInput with no error path. Since detectAgentStates() also removed the old capture-pane fallback, a Claude/Codex/Pi run that exits with an error now looks like a successful completion or idle pane, so .commandError badges and notifications become unreachable.
Useful? React with 👍 / 👎.
- Hook scripts rename window to just agent name (no emoji, no dirname) - Add .agentDone badge with checkmark icon for completed agent state - Fix "done" mapping: .completed instead of .waitingForInput - Sidebar shows ⚡ (working) / ✅ (done) via badge, not in window title
…y entries - P1: Process hook-reported agent state before clearing stale pane options, so fast agent completions (done before next poll) still show badge/notification - P2: Append Mori hook to existing codex notify array instead of replacing it; uninstall removes only the Mori entry, preserving other notifiers - Add parseTomlStringArray helper for safe TOML array manipulation
…deduplicate hooks - Remove save_original_name pattern from all hook scripts and Pi extension; stale cleanup now re-enables tmux automatic-rename instead (fixes stale name when agents overlap) - Extract shared set_state/set_state into mori-hook-common.sh sourced by both agent and codex hooks - Extract duplicated Claude event names array to claudeEvents constant - Add terminationHandler to osascript fallback for proper process reaping - Replace getPaneOption/renamePaneWindow with setWindowOption on TmuxBackend - Document timeout double-resume safety in TmuxCommandRunner
Summary
$XDG_CONFIG_HOME/mori/hooks/and are loaded from SPM bundle resources⚡ agent dir(working) /✅ agent dir(done), with macOS notifications on completionAgents supported
~/.claude/settings.json(UserPromptSubmit, PreToolUse, Stop, Notification)notifyin~/.codex/config.toml(legacy notify, JSON arg parsing)settings.json, file at~/.config/mori/mori-pi-extension.tsKey fixes
notifymust be top-level (before[section]headers)typefield[ -z "$var" ] && cmdunderset -ekills script; useif/then/filoadShellEnvironmenthanging fixed with PATH check + timeoutTest plan
~/.claude/settings.jsonupdated⚡ claude <dir>/✅ claude <dir>~/.codex/config.tomlhas top-levelnotify~/.config/mori/mori-pi-extension.tscreated and registered in Pi settings