Skip to content

feat: live activity push watcher, new session command, resume rename - #359

Merged
jongio merged 6 commits into
mainfrom
go/live-activity
Aug 1, 2026
Merged

feat: live activity push watcher, new session command, resume rename#359
jongio merged 6 commits into
mainfrom
go/live-activity

Conversation

@jongio

@jongio jongio commented Jul 20, 2026

Copy link
Copy Markdown
Owner

Summary

Adds push-based live activity monitoring, session launch/focus capabilities, and renames the config field from custom_command to resume_session_command with automatic migration.

Changes

Live Activity (push-based)

  • EventWatcher: fsnotify-based file watcher that monitors session-state directories for changes and re-classifies sessions instantly (replaces 30s polling). Changes are debounced and drained by a single goroutine; Stop() blocks until that goroutine exits, so no callback can fire after the TUI tears down its channel.
  • Focus Window: Win32 SetForegroundWindow API to bring a session's terminal window to front (W keybinding). The PID is resolved from the session's lock file, then the parent process chain is walked to find a visible top-level window.
  • New Session: Launch new Copilot CLI sessions from dispatch (+ keybinding)

Visual Enhancements

  • Blinking attention dot for sessions waiting on user input (ANSI blink)
  • Highlighted row background for waiting sessions (bold + selection background)
  • "Last Event" field in session detail pane showing event type + relative timestamp

Config Rename

  • custom_command renamed to resume_session_command (config v1 to v2 migration)
  • Added new_session_command config field
  • Both fields available in the Settings panel
  • All docs updated (README, SECURITY, config.astro, features.astro)

Bug fixes found while getting CI green

  • IsProcessAlive treated PID 0 as alive on Unix, because syscall.Kill(0, 0) targets the caller's own process group. Both implementations now reject non-positive PIDs. Its test coverage lived in a //go:build windows file, which is why CI never caught it; the tests moved to process_test.go so they run on every platform.
  • EventWatcher.Stop() only called Timer.Stop() on its per-session debounce timers without waiting, so an already-fired timer could invoke onChange after the TUI closed eventWatchCh and panic with "send on closed channel". Reproduced in 3 of 4 internal/tui runs before the fix.

Testing

  • EventWatcher: 7 tests; IsProcessAlive: 3 tests, now running on all platforms
  • All existing tests pass
  • go vet and golangci-lint clean

@jongio
jongio force-pushed the go/live-activity branch 2 times, most recently from e117dbd to 7518464 Compare July 22, 2026 16:52
@jongio
jongio force-pushed the go/live-activity branch from faf35ec to 08be397 Compare August 1, 2026 17:16
jongio and others added 5 commits August 1, 2026 11:32
- Add fsnotify-based EventWatcher for push session updates (replaces polling)
- Add SessionTracker for PID tracking of launched sessions
- Add focus window support (Win32 SetForegroundWindow)
- Add new session launcher with + keybinding
- Add W keybinding to focus selected session terminal window
- Rename custom_command to resume_session_command with v1->v2 migration
- Add new_session_command config field and settings panel entry
- Add blinking dot and highlighted row for waiting sessions
- Show last event type and timestamp in session detail pane
- Update all docs (README, SECURITY, config.astro, features.astro)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 37403417-09bc-4600-8056-fbb5a2fb18bb
… --resume'

- Change defaultNewSessionCommand from 'gh copilot' to 'copilot'
- Flip FindCLIBinary() to prefer 'copilot' over 'ghcs'
- Update README, spec doc, web config page with new defaults
- Add new_session_command to web config.astro options table

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 37403417-09bc-4600-8056-fbb5a2fb18bb
Two cross-platform bugs surfaced by the new SessionTracker tests on Linux.

IsProcessAlive treated PID 0 as alive on Unix: syscall.Kill(0, 0) targets the
caller's own process group, so it returns nil. Both implementations now reject
non-positive PIDs up front. The existing coverage lived in a windows-only test
file, which is why CI never caught it; the tests move to process_test.go so
they run on every platform.

EventWatcher used one time.AfterFunc per session for debouncing, and Stop only
called Timer.Stop() without waiting. A timer that had already fired could
invoke onChange after the TUI closed eventWatchCh, panicking with "send on
closed channel" (reproduced in 3 of 4 internal/tui runs). Debouncing now uses a
dirty set drained by a single goroutine, and Stop waits on a WaitGroup, so no
callback can be in flight once Stop returns.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: dac50159-d2c8-4ade-920a-333a51fc9ce3
LastSessionEvent built SessionEvent field-by-field from an identical struct
(staticcheck S1016); use a direct conversion instead. Also explicitly discard
the return values of the Win32 syscall wrappers in focus_windows.go, which
errcheck flags when linting on Windows.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: dac50159-d2c8-4ade-920a-333a51fc9ce3
SessionTracker was never populated outside its own tests. LaunchNewSession
always returns PID 0 because wt.exe spawns a detached child whose PID it
cannot recover, and the TUI discarded that value anyway, so HasLive was
always false and the focus path always fell through to the lock-file lookup.

Remove the type and its tests, and collapse handleFocusWindowKey onto
FindSessionPID, which is the mechanism that was actually doing the work. The
test plan is updated to map the focus cases onto the platform PID tests.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: dac50159-d2c8-4ade-920a-333a51fc9ce3
@jongio
jongio force-pushed the go/live-activity branch from 48a43d1 to deebcef Compare August 1, 2026 18:39
The rewritten debounce fired a fixed 50ms after the first change rather than
after changes stopped arriving, turning it into a throttle. A sustained burst
of appends therefore produced one callback per 50ms window instead of one per
quiet period, which is what TestEventWatcher_DebounceCollapses caught under
the race detector.

Reset the window on every new change, as the original per-session timers did,
and cap the total deferral at 500ms so a continuously active session still
refreshes instead of having its window extended forever.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: dac50159-d2c8-4ade-920a-333a51fc9ce3
@jongio
jongio merged commit 96b54b6 into main Aug 1, 2026
2 checks passed
@jongio
jongio deleted the go/live-activity branch August 1, 2026 18:52
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