Skip to content

feat(host-service): terminalAgents tracker + pane wiring#4901

Merged
Kitenite merged 14 commits into
mainfrom
agent-session-tracking
May 28, 2026
Merged

feat(host-service): terminalAgents tracker + pane wiring#4901
Kitenite merged 14 commits into
mainfrom
agent-session-tracking

Conversation

@Kitenite
Copy link
Copy Markdown
Collaborator

@Kitenite Kitenite commented May 24, 2026

Summary

  • New host-service module terminalAgents — in-process per-terminal agent binding store, populated from notifications.hook events and drained on terminal exit. Exposes a tRPC surface (listByWorkspace, findActive, getOrCreate, onWorkspaceChange observable) so non-renderer consumers can reuse live agent sessions instead of always spawning new ones.
  • Renderer rewired: TerminalPaneIcon now consumes the host-service tracker via a new useTerminalAgentBindings React Query hook (invalidated on agent:lifecycle / terminal:lifecycle). The renderer-side useV2AgentBindingStore (Zustand) is deleted — host store is the single source of truth.

Design plan: plans/20260523-agent-session-tracking.md.

Test plan

  • Store unit tests (9 cases — start/intermediate/exit, agent swap, findActive tie-break, change emission, filters)
  • notifications.hook regression — now also records onto the store
  • Repo-wide bun run typecheck (28/28)
  • bun run lint clean
  • Host-service tests 444/0
  • Manual verification — start the desktop app, attach an agent in a v2 terminal pane, confirm the pane icon swaps to the agent logo; /exit and confirm it reverts to the generic terminal glyph

Open in Stage

Summary by cubic

Adds a host-service terminalAgents tracker with tRPC APIs, rewires the terminal pane icon to read from it, and promotes droid to a first‑class builtin with icons and derived presets; also reruns per‑agent setup on Add. Crops the droid icon and tightens setup/cleanup reliability.

  • New Features

    • In‑memory terminalAgents store tracks terminalId → agent per workspace; filled via notifications.hook, cleared on terminal exit; emits workspace change.
    • tRPC: listByWorkspace, findActive, getOrCreate (coalesces concurrent calls; 10s timeout disposes orphaned terminal), onWorkspaceChange observable.
    • Renderer: useTerminalAgentBindings (@tanstack/react-query) mirrors the host; TerminalPaneIcon now takes workspaceId and shows the agent logo from the host tracker.
    • Catalog: droid added as a builtin with icons; HOST_AGENT_PRESETS now derived from BUILTIN_TERMINAL_AGENTS; all presets seed by default.
    • Settings: added settings.setupAgent (electron) and setupSingleAgent; Add flow reruns per‑agent setup.
  • Refactors

    • Removed renderer useV2AgentBindingStore (Zustand) and HostNotificationSubscriber mutations; host store is the single source of truth.
    • Store no longer carries stale agentSessionId/definitionId across agent swaps; Zod validates agent ids via BUILTIN_AGENT_IDS.
    • Host terminal.disposeSession also calls terminalAgentStore.markTerminalExited.
    • Removed duplicate preset‑icon SVGs from the renderer; icons sourced from @superset/ui. Cropped droid icon viewBox to match visual weight.
    • Reliability: getOrCreate now logs orphaned‑terminal dispose failures; setupSingleAgent runs bootstrap actions before per‑agent hooks.

Written for commit 5f6ecc8. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features

    • Backend terminal-agent tracking and a new terminalAgents API to list/find/subscribe to live agent bindings and coalesce get-or-create flows.
    • UI updates: workspace-scoped agent bindings now drive terminal icons and session dropdowns.
    • Settings: per-agent setup action and a new setupAgent operation.
  • Refactor

    • Agent-binding state moved from local client store to centralized host-service store; lifecycle handling consolidated.
  • Documentation

    • Design doc specifying agent-session tracking behavior and APIs added.
  • Tests

    • New unit/integration tests for terminal-agent store and router; added preset icon assets for a new "droid" agent.

Review Change Stack

Kitenite added 3 commits May 23, 2026 23:18
In-process per-terminal agent binding tracker on host-service, plus tRPC
surface (listByWorkspace, findActive, getOrCreate, onWorkspaceChange).
No consumers wired yet — module + API only. Decisions logged: in-mem
Map, per-terminal granularity, delete on exit, latest-event tie-break,
primitives over bundled send.
In-process per-terminal agent binding store on host-service, populated
by the existing notifications.hook event receiver and drained on
terminal exit. Exposes a tRPC surface (listByWorkspace, findActive,
getOrCreate, onWorkspaceChange observable) so non-renderer consumers
(automations) can reuse live agent sessions instead of always spawning
new ones.

No persistence: Map clears on host-service restart and rehydrates from
the next hook event. Per-terminal granularity; agent swap inside the
same pty overwrites in place.
Replace the renderer-side useV2AgentBindingStore (Zustand) with a
React Query hook against host-service terminalAgents.listByWorkspace,
invalidated by agent:lifecycle and terminal:lifecycle workspace
events. Host store is now the single source of truth; the renderer
just mirrors it.

TerminalPaneIcon takes a new workspaceId prop; HostNotificationSubscriber
no longer mutates the (now-deleted) Zustand store.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 24, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3883fef6-9867-4289-b91e-2cc484e60a08

📥 Commits

Reviewing files that changed from the base of the PR and between 80f50c2 and 5f6ecc8.

⛔ Files ignored due to path filters (9)
  • apps/desktop/src/renderer/assets/app-icons/preset-icons/claude.svg is excluded by !**/*.svg
  • apps/desktop/src/renderer/assets/app-icons/preset-icons/codex-white.svg is excluded by !**/*.svg
  • apps/desktop/src/renderer/assets/app-icons/preset-icons/codex.svg is excluded by !**/*.svg
  • apps/desktop/src/renderer/assets/app-icons/preset-icons/cursor.svg is excluded by !**/*.svg
  • apps/desktop/src/renderer/assets/app-icons/preset-icons/gemini.svg is excluded by !**/*.svg
  • apps/desktop/src/renderer/assets/app-icons/preset-icons/opencode-white.svg is excluded by !**/*.svg
  • apps/desktop/src/renderer/assets/app-icons/preset-icons/opencode.svg is excluded by !**/*.svg
  • packages/ui/src/assets/icons/preset-icons/droid-white.svg is excluded by !**/*.svg
  • packages/ui/src/assets/icons/preset-icons/droid.svg is excluded by !**/*.svg
📒 Files selected for processing (6)
  • apps/desktop/src/lib/trpc/routers/settings/index.ts
  • apps/desktop/src/main/lib/agent-setup/desktop-agent-setup.ts
  • apps/desktop/src/renderer/routes/_authenticated/settings/agents/components/V2AgentsSettings/V2AgentsSettings.tsx
  • packages/host-service/src/terminal-agents/store.ts
  • packages/host-service/src/trpc/router/terminal-agents/terminal-agents.ts
  • packages/shared/src/host-agent-presets.ts

📝 Walkthrough

Walkthrough

This PR implements host-service TerminalAgentStore, records lifecycle events, exposes terminalAgents tRPC procedures (list/find/getOrCreate/subscribe), integrates the store into host-service context and notifications, adds renderer React Query hooks, updates UI components to use them, and removes the old local v2 agent binding store usage.

Changes

Terminal Agent Tracking

Layer / File(s) Summary
Store types and implementation
packages/host-service/src/terminal-agents/types.ts, packages/host-service/src/terminal-agents/store.ts, packages/host-service/src/terminal-agents/store.test.ts, packages/host-service/src/terminal-agents/index.ts
Defines TerminalAgentId and TerminalAgentBinding; implements TerminalAgentStore with event recording, listing, active selection, mark-exited, and "change" events; tests validate lifecycle behavior.
Host service app integration and context
packages/host-service/src/app.ts, packages/host-service/src/types.ts
Instantiates TerminalAgentStore at app startup and exposes it via the tRPC createContext; extends HostServiceContext typing.
tRPC router definition and registration
packages/host-service/src/trpc/router/terminal-agents/terminal-agents.ts, packages/host-service/src/trpc/router/terminal-agents/index.ts, packages/host-service/src/trpc/router/router.ts
Implements terminalAgentsRouter (procedures: listByWorkspace, findActive, getOrCreate, onWorkspaceChange) with input validation, coalescing getOrCreate, wait-for-binding timeout, and subscription snapshot/change semantics; registers router on appRouter.
Event recording and router integration
packages/host-service/src/trpc/router/notifications/notifications.ts, packages/host-service/src/trpc/router/notifications/notifications.test.ts, packages/host-service/src/trpc/router/terminal/terminal.ts
Notifications hook records normalized agent lifecycle events to terminalAgentStore.recordEvent using a single timestamp; terminal disposal calls markTerminalExited. Tests assert store recording for SessionStart.
Desktop React Query hooks
apps/desktop/src/renderer/hooks/host-service/useTerminalAgentBindings/useTerminalAgentBindings.ts, apps/desktop/src/renderer/hooks/host-service/useTerminalAgentBindings/index.ts
useTerminalAgentBindings(workspaceId) fetches bindings via host-service terminalAgents.listByWorkspace, converts array to a Map keyed by terminalId, and invalidates on workspace agent:lifecycle/terminal:lifecycle events. useTerminalAgentBinding(workspaceId, terminalId) returns a single binding or undefined.
Desktop component updates
apps/desktop/src/renderer/routes/.../TerminalPaneIcon/TerminalPaneIcon.tsx, apps/desktop/src/renderer/routes/.../TerminalSessionDropdown/TerminalSessionDropdown.tsx, apps/desktop/src/renderer/routes/.../usePaneRegistry/usePaneRegistry.tsx
TerminalPaneIcon now accepts workspaceId and terminalId and uses useTerminalAgentBinding; callers updated to pass workspaceId.
Notification refactor and old store removal
apps/desktop/src/renderer/stores/v2-agent-bindings/index.ts, apps/desktop/src/renderer/routes/.../HostNotificationSubscriber/HostNotificationSubscriber.tsx
Removed direct re-exports and deprecated local store usage; HostNotificationSubscriber no longer mutates bindings inline and delegates lifecycle handling via workspace lookup.
Agent setup and settings
apps/desktop/src/main/lib/agent-setup/desktop-agent-setup.ts, apps/desktop/src/lib/trpc/routers/settings/index.ts, apps/desktop/src/main/lib/agent-setup/index.ts, apps/desktop/src/renderer/routes/_authenticated/settings/agents/components/V2AgentsSettings/V2AgentsSettings.tsx
Adds setupSingleAgent(agentId) helper and setupAgent tRPC mutation; UI triggers setup as a fire-and-forget step after adding an agent.
Shared presets, builtins, and identity types
packages/shared/src/builtin-terminal-agents.ts, packages/shared/src/host-agent-presets.ts, packages/shared/src/agent-identity.ts
Introduces droid builtin agent, updates some builtin commands, tightens AgentIdentity.agentId to BuiltinAgentId, and derives HOST_AGENT_PRESETS from BUILTIN_TERMINAL_AGENTS.
Tests and seed adjustments
packages/host-service/src/trpc/router/settings/agent-configs.test.ts
Test expectations updated to derive seeded preset ids/orders from getDefaultSeedPresets() instead of hard-coded arrays; assertions adjusted accordingly.
UI preset icon additions
packages/ui/src/assets/icons/preset-icons/index.ts
Adds droid light/dark SVG assets and exports them in PRESET_ICONS.
Design doc and plan
plans/done/20260523-agent-session-tracking.md
Adds a design/plan document describing TerminalAgentStore contract, tRPC surface, getOrCreate coalescing and timeout disposal semantics, wiring points, tests, and out-of-scope concerns.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • superset-sh/superset#4901: Both PRs introduce and wire the same useTerminalAgentBindings/useTerminalAgentBinding hooks and update TerminalPaneIcon to use them.
  • superset-sh/superset#4614: Related changes to v2 notification flow and HostNotificationSubscriber wiring using workspace context.

Poem

🐰 I tunneled through code to find a store,

Bindings hop from renderer to host’s door.
tRPC sings, React Query keeps score,
Events whisper when terminals roar.
A happy rabbit hops off to explore.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 21.05% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat(host-service): terminalAgents tracker + pane wiring' clearly summarizes the main changes: a new terminalAgents tracker module in host-service and the rewiring of terminal pane components to use it.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed The PR description is comprehensive and well-structured, covering objectives, test plan, and implementation details.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch agent-session-tracking

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint skipped: no ESLint configuration detected in root package.json. To enable, add eslint to devDependencies.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@capy-ai
Copy link
Copy Markdown

capy-ai Bot commented May 24, 2026

Capy auto-review is paused for this organization because the monthly auto-review limit has been reached. Increase the limit or turn it off in billing settings to resume automatic reviews.

@stage-review
Copy link
Copy Markdown

stage-review Bot commented May 24, 2026

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 24, 2026

Greptile Summary

This PR introduces a host-service TerminalAgentStore that tracks which agent is live in each terminal, wires it into the notifications.hook and terminal dispose paths, and exposes it via a new terminalAgents tRPC router (listByWorkspace, findActive, getOrCreate, onWorkspaceChange). The renderer-side Zustand store (useV2AgentBindingStore) is deleted and replaced by a React Query hook (useTerminalAgentBindings) backed by the host store.

  • packages/host-service/src/terminal-agents/ — new in-process Map-backed store with correct upsert/delete semantics, agent-swap handling, findActive tie-breaking, and a "change" EventEmitter surface for subscriptions.
  • packages/host-service/src/trpc/router/terminal-agents/terminal-agents.ts — new tRPC router; getOrCreate has a TOCTOU race where concurrent calls can both see no active binding and both spawn a terminal, and waitForBinding timeouts leave the newly-created terminal orphaned.
  • Renderer wiringTerminalPaneIcon now consumes the host-service-backed hook; HostNotificationSubscriber no longer mutates a renderer-local store.

Confidence Score: 3/5

The store and renderer wiring are solid, but the getOrCreate mutation has a concurrency correctness gap that should be addressed before automation callers use it.

The core store, event wiring, and renderer migration are clean and well-tested. The risk concentrates in getOrCreate: two concurrent calls for the same agent can both pass the findActive check before either reaches the first await, causing two terminals to be spawned. A timeout in waitForBinding also leaves the newly-created terminal running with no owner. These issues don't affect the currently shipped pane icon path but will matter the moment any automation caller uses getOrCreate.

packages/host-service/src/trpc/router/terminal-agents/terminal-agents.ts — the getOrCreate mutation and waitForBinding timeout handling.

Important Files Changed

Filename Overview
packages/host-service/src/trpc/router/terminal-agents/terminal-agents.ts New tRPC router for terminal agent tracking; getOrCreate has a TOCTOU race that can spawn duplicate terminals, and waitForBinding timeouts leave orphaned pty processes.
packages/host-service/src/terminal-agents/store.ts New in-process TerminalAgentBinding store backed by a Map; well-structured with correct exit semantics and agent-swap handling, but missing setMaxListeners to avoid Node.js warnings at scale.
packages/host-service/src/trpc/router/notifications/notifications.ts Correctly wires terminalAgentStore.recordEvent alongside the existing broadcastAgentLifecycle call, sharing the same occurredAt timestamp; clean change.
packages/host-service/src/trpc/router/terminal/terminal.ts Adds a single markTerminalExited call after disposeSessionAndWait; correct placement and no regressions to the existing dispose path.
apps/desktop/src/renderer/hooks/host-service/useTerminalAgentBindings/useTerminalAgentBindings.ts New React Query hook replacing the deleted Zustand store; invalidates on agent:lifecycle and terminal:lifecycle events, staleTime: Infinity is appropriate given event-driven invalidation.
apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/TerminalPane/components/TerminalPaneIcon/TerminalPaneIcon.tsx Clean migration from Zustand selectV2AgentBinding to useTerminalAgentBinding; props interface now correctly carries workspaceId.
packages/host-service/src/terminal-agents/store.test.ts Thorough 9-case unit test suite covering start/intermediate/exit, agent swap, findActive tie-break, change emission, and filters; no gaps identified.
apps/desktop/src/renderer/routes/_authenticated/components/V2NotificationController/components/HostNotificationSubscriber/HostNotificationSubscriber.tsx Renderer-side Zustand store mutation calls removed cleanly; host service is now the authoritative mutation point.

Sequence Diagram

sequenceDiagram
    participant Agent as Agent Process (hook)
    participant Notif as notifications.hook
    participant Store as TerminalAgentStore
    participant TRPCRouter as terminalAgents tRPC
    participant RQ as React Query (renderer)
    participant Icon as TerminalPaneIcon

    Agent->>Notif: POST hook (Attached / Start / Detached)
    Notif->>Store: recordEvent(terminalId, agentId, eventType)
    Store-->>Store: upsert or delete byTerminal[terminalId]
    Store->>Store: emit("change", workspaceId)

    Note over Notif: eventBus.broadcastAgentLifecycle also fires
    Notif-->>RQ: agent:lifecycle workspace event

    RQ->>TRPCRouter: invalidateQueries → listByWorkspace
    TRPCRouter->>Store: listByWorkspace(workspaceId)
    Store-->>TRPCRouter: TerminalAgentBinding[]
    TRPCRouter-->>RQ: updated bindings
    RQ-->>Icon: "Map<terminalId, binding>"
    Icon-->>Icon: swap to agent logo (or terminal glyph)

    Note over TRPCRouter: getOrCreate path
    TRPCRouter->>Store: findActive(workspaceId, agentId)
    alt binding exists
        Store-->>TRPCRouter: existing binding (created: false)
    else no binding
        TRPCRouter->>TRPCRouter: createTerminalSessionInternal
        TRPCRouter->>Store: on("change") → waitForBinding (10s)
        Agent->>Notif: POST hook (Attached)
        Notif->>Store: recordEvent
        Store->>TRPCRouter: emit("change") resolves waitForBinding
        TRPCRouter-->>TRPCRouter: "return { binding, created: true }"
    end
Loading
Prompt To Fix All With AI
Fix the following 3 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 3
packages/host-service/src/trpc/router/terminal-agents/terminal-agents.ts:63-113
**TOCTOU race in `getOrCreate` spawns duplicate terminals**

Two concurrent `getOrCreate` calls for the same `(workspaceId, agentId, definitionId)` triple can both reach `findActive` before either call advances past the first `await` inside `createTerminalSessionInternal`. Both see `undefined`, both fall through, and both spawn a new terminal — producing two live agent processes where one was requested. In an automation or rapid UI-retry scenario this is a real path: the caller gets `{ created: true }` twice with two different `terminalId`s, each holding a running agent session that the other caller has no reference to.

### Issue 2 of 3
packages/host-service/src/trpc/router/terminal-agents/terminal-agents.ts:97-110
**Orphaned terminal when `waitForBinding` times out**

If the agent's hook never fires within 10 seconds, `waitForBinding` rejects with `TIMEOUT` and the caller receives an error. The terminal created by `createTerminalSessionInternal` at line 85 is left running indefinitely — it is not disposed and has no owner tracking it after the `getOrCreate` call returns. Over time (or after multiple failed launches) this leaks pty/process resources. A `try/finally` that calls `disposeSessionAndWait(created.terminalId, ...)` on rejection would close the gap.

### Issue 3 of 3
packages/host-service/src/terminal-agents/store.ts:31-32
**No `setMaxListeners` — default limit of 10 may be exceeded**

Every active `onWorkspaceChange` subscription and every concurrent in-flight `waitForBinding` call (inside `getOrCreate`) attaches one listener to the store's `"change"` event. Node.js emits a `MaxListenersExceededWarning` and prints a stack-trace-like warning when more than 10 listeners exist, which would surface in production logs. Calling `this.setMaxListeners(0)` (or a generous ceiling like `100`) in the constructor removes the noise without masking real leaks — listener teardown is already correct in both code paths.

Reviews (1): Last reviewed commit: "refactor(desktop): wire TerminalPaneIcon..." | Re-trigger Greptile

Comment on lines +63 to +113
getOrCreate: protectedProcedure
.input(
z.object({
workspaceId: z.string(),
agentId: terminalAgentIdSchema,
definitionId: agentDefinitionIdSchema.optional(),
initialCommand: z.string().trim().min(1).optional(),
cwd: z.string().optional(),
}),
)
.mutation(async ({ ctx, input }) => {
const { workspaceId, agentId, definitionId } = input;
const existing = ctx.terminalAgentStore.findActive(
workspaceId,
agentId,
definitionId,
);
if (existing) {
return { binding: existing, created: false as const };
}

const terminalId = crypto.randomUUID();
const created = await createTerminalSessionInternal({
terminalId,
workspaceId,
db: ctx.db,
eventBus: ctx.eventBus,
...(input.initialCommand
? { initialCommand: input.initialCommand }
: {}),
...(input.cwd ? { cwd: input.cwd } : {}),
});

if ("error" in created) {
throw new TRPCError({
code: "INTERNAL_SERVER_ERROR",
message: created.error,
});
}

const binding = await waitForBinding({
store: ctx.terminalAgentStore,
workspaceId,
agentId,
definitionId,
terminalId: created.terminalId,
timeoutMs: GET_OR_CREATE_TIMEOUT_MS,
});

return { binding, created: true as const };
}),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 TOCTOU race in getOrCreate spawns duplicate terminals

Two concurrent getOrCreate calls for the same (workspaceId, agentId, definitionId) triple can both reach findActive before either call advances past the first await inside createTerminalSessionInternal. Both see undefined, both fall through, and both spawn a new terminal — producing two live agent processes where one was requested. In an automation or rapid UI-retry scenario this is a real path: the caller gets { created: true } twice with two different terminalIds, each holding a running agent session that the other caller has no reference to.

Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/host-service/src/trpc/router/terminal-agents/terminal-agents.ts
Line: 63-113

Comment:
**TOCTOU race in `getOrCreate` spawns duplicate terminals**

Two concurrent `getOrCreate` calls for the same `(workspaceId, agentId, definitionId)` triple can both reach `findActive` before either call advances past the first `await` inside `createTerminalSessionInternal`. Both see `undefined`, both fall through, and both spawn a new terminal — producing two live agent processes where one was requested. In an automation or rapid UI-retry scenario this is a real path: the caller gets `{ created: true }` twice with two different `terminalId`s, each holding a running agent session that the other caller has no reference to.

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +97 to +110
throw new TRPCError({
code: "INTERNAL_SERVER_ERROR",
message: created.error,
});
}

const binding = await waitForBinding({
store: ctx.terminalAgentStore,
workspaceId,
agentId,
definitionId,
terminalId: created.terminalId,
timeoutMs: GET_OR_CREATE_TIMEOUT_MS,
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Orphaned terminal when waitForBinding times out

If the agent's hook never fires within 10 seconds, waitForBinding rejects with TIMEOUT and the caller receives an error. The terminal created by createTerminalSessionInternal at line 85 is left running indefinitely — it is not disposed and has no owner tracking it after the getOrCreate call returns. Over time (or after multiple failed launches) this leaks pty/process resources. A try/finally that calls disposeSessionAndWait(created.terminalId, ...) on rejection would close the gap.

Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/host-service/src/trpc/router/terminal-agents/terminal-agents.ts
Line: 97-110

Comment:
**Orphaned terminal when `waitForBinding` times out**

If the agent's hook never fires within 10 seconds, `waitForBinding` rejects with `TIMEOUT` and the caller receives an error. The terminal created by `createTerminalSessionInternal` at line 85 is left running indefinitely — it is not disposed and has no owner tracking it after the `getOrCreate` call returns. Over time (or after multiple failed launches) this leaks pty/process resources. A `try/finally` that calls `disposeSessionAndWait(created.terminalId, ...)` on rejection would close the gap.

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +31 to +32
export class TerminalAgentStore extends EventEmitter {
private readonly byTerminal = new Map<string, TerminalAgentBinding>();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 No setMaxListeners — default limit of 10 may be exceeded

Every active onWorkspaceChange subscription and every concurrent in-flight waitForBinding call (inside getOrCreate) attaches one listener to the store's "change" event. Node.js emits a MaxListenersExceededWarning and prints a stack-trace-like warning when more than 10 listeners exist, which would surface in production logs. Calling this.setMaxListeners(0) (or a generous ceiling like 100) in the constructor removes the noise without masking real leaks — listener teardown is already correct in both code paths.

Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/host-service/src/terminal-agents/store.ts
Line: 31-32

Comment:
**No `setMaxListeners` — default limit of 10 may be exceeded**

Every active `onWorkspaceChange` subscription and every concurrent in-flight `waitForBinding` call (inside `getOrCreate`) attaches one listener to the store's `"change"` event. Node.js emits a `MaxListenersExceededWarning` and prints a stack-trace-like warning when more than 10 listeners exist, which would surface in production logs. Calling `this.setMaxListeners(0)` (or a generous ceiling like `100`) in the constructor removes the noise without masking real leaks — listener teardown is already correct in both code paths.

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/host-service/src/terminal-agents/store.ts`:
- Around line 64-79: The code currently carries over existing.agentSessionId and
existing.definitionId into next even when a swap changes the agent identity,
causing stale metadata to surface; update the logic in store.ts (around
variables existing, next, agentSessionId, definitionId, agentId, startedAt) so
that when existing exists and the agentId changes (or agentSessionId is provided
and differs) you not only reset next.startedAt = occurredAt but also clear any
carried-over identity fields—set next.agentSessionId = undefined and
next.definitionId = undefined unless an explicit agentSessionId/definitionId was
passed in; ensure explicit values still override.

In `@packages/host-service/src/trpc/router/terminal-agents/terminal-agents.ts`:
- Around line 12-15: Replace the unsafe type assertions on terminalAgentIdSchema
and agentDefinitionIdSchema with real runtime validation against the allowed ID
set: remove the "as z.ZodType<...>" casts on terminalAgentIdSchema and
agentDefinitionIdSchema and instead construct Zod validators using z.enum(...)
or a z.union of z.literal(...) values built from the shared agent catalog (the
same source used by getOrCreate/findActive/waitForBinding), so incoming
agentId/definitionId are validated at runtime before being passed into
getOrCreate, findActive, or waitForBinding; ensure the new schemas export the
correct TypeScript types (TerminalAgentId/AgentDefinitionId) without casting.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1e1867be-acd0-4319-9b73-15dcbc5b5dc3

📥 Commits

Reviewing files that changed from the base of the PR and between f6d0894 and d6afee0.

📒 Files selected for processing (22)
  • apps/desktop/src/renderer/hooks/host-service/useTerminalAgentBindings/index.ts
  • apps/desktop/src/renderer/hooks/host-service/useTerminalAgentBindings/useTerminalAgentBindings.ts
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/TerminalPane/components/TerminalPaneIcon/TerminalPaneIcon.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/TerminalPane/components/TerminalSessionDropdown/TerminalSessionDropdown.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/usePaneRegistry.tsx
  • apps/desktop/src/renderer/routes/_authenticated/components/V2NotificationController/components/HostNotificationSubscriber/HostNotificationSubscriber.tsx
  • apps/desktop/src/renderer/stores/v2-agent-bindings/index.ts
  • apps/desktop/src/renderer/stores/v2-agent-bindings/store.test.ts
  • apps/desktop/src/renderer/stores/v2-agent-bindings/store.ts
  • packages/host-service/src/app.ts
  • packages/host-service/src/terminal-agents/index.ts
  • packages/host-service/src/terminal-agents/store.test.ts
  • packages/host-service/src/terminal-agents/store.ts
  • packages/host-service/src/terminal-agents/types.ts
  • packages/host-service/src/trpc/router/notifications/notifications.test.ts
  • packages/host-service/src/trpc/router/notifications/notifications.ts
  • packages/host-service/src/trpc/router/router.ts
  • packages/host-service/src/trpc/router/terminal-agents/index.ts
  • packages/host-service/src/trpc/router/terminal-agents/terminal-agents.ts
  • packages/host-service/src/trpc/router/terminal/terminal.ts
  • packages/host-service/src/types.ts
  • plans/20260523-agent-session-tracking.md
💤 Files with no reviewable changes (4)
  • apps/desktop/src/renderer/stores/v2-agent-bindings/store.test.ts
  • apps/desktop/src/renderer/stores/v2-agent-bindings/index.ts
  • apps/desktop/src/renderer/stores/v2-agent-bindings/store.ts
  • apps/desktop/src/renderer/routes/_authenticated/components/V2NotificationController/components/HostNotificationSubscriber/HostNotificationSubscriber.tsx

Comment thread packages/host-service/src/terminal-agents/store.ts Outdated
Comment thread packages/host-service/src/trpc/router/terminal-agents/terminal-agents.ts Outdated
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

3 issues found across 22 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread packages/host-service/src/terminal-agents/store.ts Outdated
Comment thread packages/host-service/src/trpc/router/terminal-agents/terminal-agents.ts Outdated
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 24, 2026

🚀 Preview Deployment

🔗 Preview Links

Service Status Link
Neon Database (Neon) View Branch
Vercel API (Vercel) Open Preview
Vercel Web (Vercel) Open Preview
Vercel Marketing (Vercel) Open Preview
Vercel Admin (Vercel) Open Preview
Vercel Docs (Vercel) Open Preview

Preview updates automatically with new commits

Plan shipped in this PR — module + tRPC surface + renderer pane wiring.
Favicon source had ~85px of empty padding (16%) on all sides of the
508×508 canvas. Crop the viewBox to the path bbox so droid renders at
the same visual weight as the other agent icons.
- terminal-agents.ts: log disposeSessionAndWait failures from the
  orphaned-pty cleanup path so observability isn't a black hole.
- desktop-agent-setup.ts: run bootstrap setup actions (notify-script,
  etc.) in setupSingleAgent too. Per-agent hooks reference the shared
  notify script; if boot setup didn't run, per-agent setup needs to be
  self-sufficient.
Pass focused on removing restatement and stale references while keeping
intent comments. Net -29 lines across 6 files.

- store.ts: drop stale "(plan decision #3)" reference; collapse class
  docstring.
- terminal-agents.ts: tighten getOrCreate docstring; trim coalesce
  comment.
- host-agent-presets.ts: move HOST_AGENT_PRESETS docstring from above
  tokenize() to above the export it actually documents.
- desktop-agent-setup.ts, settings/index.ts, V2AgentsSettings.tsx:
  compress 4-line "why" comments to 1-2 lines without losing intent.
@Kitenite Kitenite merged commit 69ea3c3 into main May 28, 2026
8 of 10 checks passed
@Kitenite Kitenite deleted the agent-session-tracking branch May 28, 2026 00:13
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