Skip to content

Comments

feat: add running-terminal indicator on tasks#800

Open
nthomsencph wants to merge 4 commits intogeneralaction:mainfrom
nthomsencph:tree/add-running-terminal-indicator-on-tasks-3u5
Open

feat: add running-terminal indicator on tasks#800
nthomsencph wants to merge 4 commits intogeneralaction:mainfrom
nthomsencph:tree/add-running-terminal-indicator-on-tasks-3u5

Conversation

@nthomsencph
Copy link

@nthomsencph nthomsencph commented Feb 9, 2026

  • Adds a three-state icon to each task in the task list: spinner (agent busy) → green terminal (PTY alive, agent idle) → git-branch (no terminal)
  • Tracks terminal liveness via IPC events (pty:started, pty:exited, pty:list) instead of polling
  • State survives app reload by seeding from active PTYs on first subscribe

Closes #781

Changes

Main process

  • ptyManager.ts — Added listPtyIds() for renderer seeding
  • ptyIpc.ts — Added broadcastToAll() helper, exported getPtyTaskId() for taskId extraction, pty:list handler, pty:exited broadcasts at all PTY teardown points, enriched pty:started with taskId
  • preload.ts — Exposed ptyList, onPtyExited, updated onPtyStarted payload

Renderer

  • terminalLivenessStore.ts (new) — Singleton store tracking active PTYs per task with transition-only emission and race-safe seeding
  • useTaskHasTerminal.ts (new) — React hook wrapping the store
  • TaskItem.tsx — Three-state icon using useTaskHasTerminal

Types

  • electron-api.d.ts — Added ptyList, onPtyExited, consistent taskId: string | null on both events

Tests

  • ptyIpc.test.ts — 5 tests for getPtyTaskId regex
  • terminalLivenessStore.test.ts (new) — 9 tests covering start/exit transitions, multi-PTY, chat filtering, seeding, race guard, unsubscribe

nthomsencph and others added 3 commits February 10, 2026 00:51
Expose pty:exited broadcast, pty:list handler, and taskId on pty:started
so the renderer can track which tasks have active terminal sessions.

- Add broadcastToAll() helper with error logging
- Export getPtyTaskId() to extract taskId from PTY IDs
- Add listPtyIds() to ptyManager for seeding renderer state on reload
- Broadcast pty:exited at all PTY teardown points
- Enrich pty:started broadcasts with taskId
- Expose ptyList, onPtyExited in preload and type declarations

Closes generalaction#781

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add a three-state icon to TaskItem: spinner (busy), green terminal
(has active PTY, idle), or git-branch (no terminal). Backed by a
TerminalLivenessStore singleton that tracks active PTYs per task via
IPC events, with race-safe seeding on app reload.

Closes generalaction#781

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- getPtyTaskId: standard IDs, chat terminals, malformed input,
  underscores/digits, greedy regex limitation with -main- in taskIds
- terminalLivenessStore: start/exit transitions, multi-PTY partial
  exit, chat filtering, null taskId, ptyList seeding, early-exit
  race guard, unsubscribe

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@vercel
Copy link

vercel bot commented Feb 9, 2026

Someone is attempting to deploy a commit to the General Action Team on Vercel.

A member of the Team first needs to authorize it.

@greptile-apps
Copy link

greptile-apps bot commented Feb 9, 2026

Greptile Overview

Greptile Summary

This PR adds a three-state task icon driven by PTY liveness: the main process now broadcasts pty:started/pty:exited with an extracted taskId, exposes a pty:list IPC for renderer seeding, and the renderer introduces terminalLivenessStore + useTaskHasTerminal to render a green terminal icon when a task’s main PTY is alive.

The overall wiring is coherent (preload exposes the new APIs; renderer subscribes once and seeds from current PTYs), but there are a couple of correctness issues that can leave UI state wrong:

  • Manual PTY kills currently don’t emit pty:exited, so the liveness indicator can remain stuck on.
  • The liveness store can permanently fail to subscribe if window.electronAPI isn’t present on the first subscription.
  • The taskId parsing helper is known to misattribute terminals when taskIds contain -main-.

Additionally, the Electron API typings have an internal inconsistency for onPtyStarted (taskId optional vs required) that should be aligned to avoid hiding integration mismatches.

Confidence Score: 2/5

  • Not safe to merge until liveness edge-cases are fixed
  • The feature is largely self-contained, but there are a few definite correctness bugs that will produce wrong UI state: manual PTY kills don’t emit pty:exited, and the liveness store can permanently fail to subscribe if window.electronAPI is missing at first subscribe. There is also a documented taskId parsing limitation that can misattribute terminals to the wrong task. Once these are addressed and typings are aligned, risk should drop substantially.
  • src/main/services/ptyIpc.ts, src/renderer/lib/terminalLivenessStore.ts, src/renderer/types/electron-api.d.ts

Important Files Changed

Filename Overview
src/main/preload.ts Exposes new PTY IPC APIs (ptyList, onPtyExited) and extends onPtyStarted payload with taskId; looks consistent with main process broadcasts.
src/main/services/ptyIpc.ts Adds broadcastToAll, getPtyTaskId, pty:list handler and more pty:started/exited broadcasts; missing pty:exited on manual kill and taskId parsing can misattribute terminals when taskId contains '-main-'.
src/main/services/ptyManager.ts Adds listPtyIds() to expose current PTY IDs for renderer seeding; straightforward.
src/renderer/components/TaskItem.tsx Adds terminal indicator state (spinner vs green terminal vs branch) using new useTaskHasTerminal hook; minimal UI-only change.
src/renderer/hooks/useTaskHasTerminal.ts New hook subscribes to terminalLivenessStore for a taskId and returns boolean state; depends on store correctness.
src/renderer/lib/terminalLivenessStore.ts New singleton store tracks active PTYs per task via pty:started/pty:exited and initial ptyList seed; can permanently fail to subscribe if window.electronAPI is undefined on first subscribe.
src/renderer/types/electron-api.d.ts Extends electronAPI typings for new PTY APIs; onPtyStarted taskId optionality is inconsistent between global Window.electronAPI and exported ElectronAPI interface.
src/test/main/ptyIpc.test.ts Adds tests for getPtyTaskId behavior and keeps existing IPC lifecycle tests; documents current greedy parsing limitation.
src/test/renderer/terminalLivenessStore.test.ts Adds unit tests for terminalLivenessStore transitions, multi-PTY, chat filtering, seeding, early-exit race guard, and unsubscribe.

Sequence Diagram

sequenceDiagram
  participant R as Renderer (Task list)
  participant S as terminalLivenessStore
  participant P as Preload (electronAPI)
  participant I as Main (ptyIpc)
  participant M as Main (ptyManager)

  R->>S: subscribe(taskId, listener)
  S->>P: electronAPI.onPtyStarted(cb)
  S->>P: electronAPI.onPtyExited(cb)
  S->>P: electronAPI.ptyList()
  P->>I: ipcRenderer.invoke('pty:list')
  I->>M: listPtyIds()
  M-->>I: [ptyIds]
  I-->>P: [{id, taskId}]
  P-->>S: seed items
  S-->>R: listener(hasTerminal)

  Note over I,M: PTY lifecycle
  I-->>P: broadcast 'pty:started' {id, taskId}
  P-->>S: onPtyStarted callback
  S-->>R: listener(true)

  I-->>P: broadcast 'pty:exited' {id, taskId}
  P-->>S: onPtyExited callback
  S-->>R: listener(false when last PTY exits)
Loading

Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

9 files reviewed, 4 comments

Edit Code Review Agent Settings | Greptile

Comment on lines +66 to +70
/** Extract taskId from a main-terminal PTY ID, or null. */
export function getPtyTaskId(id: string): string | null {
const m = /^[a-z0-9_-]+-main-(.+)$/.exec(id);
return m ? m[1] : null;
}
Copy link

Choose a reason for hiding this comment

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

TaskId misattribution

getPtyTaskId() uses a greedy regex (/^[a-z0-9_-]+-main-(.+)$/) that will extract the wrong taskId if the taskId itself contains -main- (the added tests explicitly encode this behavior). That will cause terminal liveness (and any other future task-scoped behavior using this helper) to be attributed to the wrong task.

Copy link
Author

Choose a reason for hiding this comment

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

As discussed briefly with @arnestrickmann, this is a known limitation of the greedy regex approach. Added a test that documents the behavior (handles taskIds that themselves contain -main- (known greedy limitation)).

The proper fix is likely to store taskId as metadata on the PTY record at creation time rather than reverse-engineering it from the ID string, which would also eliminate the coupling to the ID format. That's a ptyManager structural change so figured it should be scoped as a follow-up.

@greptile-apps
Copy link

greptile-apps bot commented Feb 9, 2026

Additional Comments (1)

src/main/services/ptyIpc.ts
Missing exit broadcast

pty:kill cleans up the PTY/owner state but never broadcasts pty:exited, so the renderer’s terminalLivenessStore (which only updates on pty:exited + initial seed) can leave the green terminal indicator stuck on after a manual kill. Consider broadcasting pty:exited (with { id, taskId: getPtyTaskId(id) }) in the pty:kill handler as well.

- Fix one-shot subscribe bug: defer setting `subscribed = true` until
  after confirming `window.electronAPI` exists, so a retry succeeds if
  preload hasn't attached yet
- Broadcast pty:exited on manual pty:kill so the liveness indicator
  doesn't get stuck on
- Align onPtyStarted taskId type to `string | null` in exported
  ElectronAPI interface (was still optional)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@nthomsencph
Copy link
Author

Additional Comments (1)
src/main/services/ptyIpc.ts Missing exit broadcast

pty:kill cleans up the PTY/owner state but never broadcasts pty:exited, so the renderer’s terminalLivenessStore (which only updates on pty:exited + initial seed) can leave the green terminal indicator stuck on after a manual kill. Consider broadcasting pty:exited (with { id, taskId: getPtyTaskId(id) }) in the pty:kill handler as well.

Fixed in f957449 — pty:kill now broadcasts pty:exited before cleanup.

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.

Add running-terminal indicator on tasks

1 participant