-
Notifications
You must be signed in to change notification settings - Fork 54
feat(desktop): add 3-color workspace status indicators #588
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
📝 WalkthroughWalkthroughReplaces per-pane boolean attention with a typed PaneStatus system, renames AGENT_COMPLETE → AGENT_LIFECYCLE across notification plumbing, adds shared notification types, templates for notify/opencode scripts, introduces status aggregation/utilities, and propagates status-driven UI, store, terminal env, and hook-server changes. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant Hook as Hook Script (terminal)
participant Server as Hook Server (/hook/complete)
participant Emitter as notificationsEmitter
participant Renderer as Renderer (useAgentHookListener / store)
participant UI as Workspace UI (StatusIndicator)
Note over Hook,Server: Local hook posts normalized lifecycle payload
Hook->>Server: POST /hook/complete { eventType, paneId?, tabId?, workspaceId?, env, version }
Server->>Emitter: emit next { type: "agent-lifecycle", data: AgentLifecycleEvent }
Emitter->>Renderer: deliver AgentLifecycleEvent
Renderer->>Renderer: mapEventType() -> ("Start"|"Stop"|"PermissionRequest") or null
Renderer->>Renderer: resolvePaneId(...) -> paneId?
Renderer->>Renderer: determine status (working/permission/review/idle)
Renderer->>Renderer: setPaneStatus(paneId, status)
Renderer->>UI: UI re-renders affected panes, aggregate via getHighestPriorityStatus/pickHigherStatus
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
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. Comment |
391f555 to
cf56018
Compare
🚀 Preview Deployment🔗 Preview Links
Preview updates automatically with new commits |
cf56018 to
2b7b66d
Compare
9788ed3 to
bcc8317
Compare
f2b3230 to
4290fee
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🤖 Fix all issues with AI Agents
In
@apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx:
- Around line 228-233: The handler currently reads pane?.status from the render
closure (stale) when deciding to call setPaneStatus(paneId, "idle"); instead,
read the current pane status at event time (e.g., call the state
getter/selectors or store getter for the pane ID such as getPane(paneId) or
usePaneStore.getState().panes[paneId]) and base the check on that fresh value,
or use a functional/state-getter API to update only if the current status is
"working" or "permission" (for example, call a get-current-pane-status(paneId)
and then setPaneStatus(paneId, "idle") only if that returns "working" or
"permission"). Ensure you reference paneId when fetching the live pane and
remove reliance on the closed-over pane variable.
🧹 Nitpick comments (4)
apps/desktop/src/main/lib/agent-setup/templates/notify-hook.template.sh (1)
37-46: Consider adding&for background execution to fully decouple.While the timeouts prevent long blocking, the curl call still waits up to 2 seconds. For truly non-blocking behavior, consider backgrounding the curl:
🔎 Optional: Background the curl call
# Timeouts prevent blocking agent completion if notification server is unresponsive curl -sG "http://127.0.0.1:${SUPERSET_PORT:-{{DEFAULT_PORT}}}/hook/complete" \ --connect-timeout 1 --max-time 2 \ --data-urlencode "paneId=$SUPERSET_PANE_ID" \ --data-urlencode "tabId=$SUPERSET_TAB_ID" \ --data-urlencode "workspaceId=$SUPERSET_WORKSPACE_ID" \ --data-urlencode "eventType=$EVENT_TYPE" \ --data-urlencode "env=$SUPERSET_ENV" \ --data-urlencode "version=$SUPERSET_HOOK_VERSION" \ - > /dev/null 2>&1 + > /dev/null 2>&1 &apps/desktop/src/main/lib/agent-setup/templates/opencode-plugin.template.js (1)
67-90: Consider bounding the childSessionCache size.The
childSessionCacheMap grows indefinitely during long-running sessions. While unlikely to be problematic in practice, consider adding a size limit or LRU eviction for very long sessions with many subagents.🔎 Optional: Add cache size limit
const childSessionCache = new Map(); + const MAX_CACHE_SIZE = 100; const isChildSession = async (sessionID) => { if (!sessionID) return true; // No sessionID = can't verify, skip if (!client?.session?.list) return true; // Can't check, skip // Check cache first if (childSessionCache.has(sessionID)) { return childSessionCache.get(sessionID); } try { const sessions = await client.session.list(); const session = sessions.data?.find((s) => s.id === sessionID); const isChild = !!session?.parentID; + // Prevent unbounded cache growth + if (childSessionCache.size >= MAX_CACHE_SIZE) { + const firstKey = childSessionCache.keys().next().value; + childSessionCache.delete(firstKey); + } childSessionCache.set(sessionID, isChild);apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupStrip/GroupItem.tsx (1)
12-12: Consider usingActivePaneStatus | nullfor stricter typing.The prop type is
PaneStatus | null, butGroupStrippassesActivePaneStatus | null(which excludes"idle"). UsingActivePaneStatus | nullhere would:
- More accurately reflect the actual data being passed
- Make the
status !== "idle"check on line 43 unnecessaryThis is a minor type refinement and the current implementation is functionally correct.
🔎 Suggested type refinement
+import type { ActivePaneStatus } from "shared/tabs-types"; -import type { PaneStatus, Tab } from "renderer/stores/tabs/types"; +import type { Tab } from "renderer/stores/tabs/types"; interface GroupItemProps { tab: Tab; isActive: boolean; - status: PaneStatus | null; + status: ActivePaneStatus | null; onSelect: () => void; onClose: () => void; }And simplify line 43:
-{status && status !== "idle" && <StatusIndicator status={status} />} +{status && <StatusIndicator status={status} />}apps/desktop/src/renderer/stores/tabs/store.ts (1)
551-583: Consider extracting duplicate status transition logic.The status transition logic (lines 567-578) is identical to lines 206-217 in
setActiveTab. Consider extracting this to a helper function to reduce duplication and ensure consistent behavior.🔎 Suggested refactor
// Helper at module level or within the store function transitionStatusOnActivation( panes: Record<string, Pane>, paneIds: string[], ): { newPanes: Record<string, Pane>; hasChanges: boolean } { const newPanes = { ...panes }; let hasChanges = false; for (const paneId of paneIds) { const currentStatus = newPanes[paneId]?.status; if (currentStatus === "review") { newPanes[paneId] = { ...newPanes[paneId], status: "idle" }; hasChanges = true; } else if (currentStatus === "permission") { newPanes[paneId] = { ...newPanes[paneId], status: "working" }; hasChanges = true; } } return { newPanes, hasChanges }; }
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (30)
.gitignoreapps/desktop/docs/EXTERNAL_FILES.mdapps/desktop/src/lib/trpc/routers/notifications.tsapps/desktop/src/lib/trpc/routers/ui-state/index.tsapps/desktop/src/main/lib/agent-setup/agent-wrappers.tsapps/desktop/src/main/lib/agent-setup/index.tsapps/desktop/src/main/lib/agent-setup/notify-hook.tsapps/desktop/src/main/lib/agent-setup/templates/notify-hook.template.shapps/desktop/src/main/lib/agent-setup/templates/opencode-plugin.template.jsapps/desktop/src/main/lib/notifications/server.test.tsapps/desktop/src/main/lib/notifications/server.tsapps/desktop/src/main/lib/terminal/env.test.tsapps/desktop/src/main/lib/terminal/env.tsapps/desktop/src/main/windows/main.tsapps/desktop/src/renderer/screens/main/components/StatusIndicator/StatusIndicator.tsxapps/desktop/src/renderer/screens/main/components/StatusIndicator/index.tsapps/desktop/src/renderer/screens/main/components/TopBar/WorkspaceSidebarControl.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceListItem.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupStrip/GroupItem.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupStrip/GroupStrip.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsxapps/desktop/src/renderer/stores/tabs/store.tsapps/desktop/src/renderer/stores/tabs/types.tsapps/desktop/src/renderer/stores/tabs/useAgentHookListener.tsapps/desktop/src/renderer/stores/tabs/utils/resolve-notification-target.tsapps/desktop/src/shared/constants.tsapps/desktop/src/shared/debug.tsapps/desktop/src/shared/notification-types.tsapps/desktop/src/shared/tabs-types.tsbiome.jsonc
🚧 Files skipped from review as they are similar to previous changes (12)
- apps/desktop/src/main/windows/main.ts
- apps/desktop/src/main/lib/notifications/server.test.ts
- apps/desktop/src/shared/notification-types.ts
- apps/desktop/src/renderer/screens/main/components/StatusIndicator/index.ts
- apps/desktop/src/main/lib/terminal/env.ts
- apps/desktop/src/main/lib/terminal/env.test.ts
- apps/desktop/src/shared/debug.ts
- .gitignore
- apps/desktop/src/lib/trpc/routers/notifications.ts
- apps/desktop/src/lib/trpc/routers/ui-state/index.ts
- apps/desktop/src/renderer/stores/tabs/utils/resolve-notification-target.ts
- apps/desktop/src/renderer/screens/main/components/TopBar/WorkspaceSidebarControl.tsx
🧰 Additional context used
📓 Path-based instructions (6)
apps/desktop/**/*.{ts,tsx}
📄 CodeRabbit inference engine (apps/desktop/AGENTS.md)
apps/desktop/**/*.{ts,tsx}: For Electron interprocess communication, ALWAYS use tRPC as defined insrc/lib/trpc
Use alias as defined intsconfig.jsonwhen possible
Prefer zustand for state management if it makes sense. Do not use effect unless absolutely necessary.
For tRPC subscriptions with trpc-electron, ALWAYS use the observable pattern from@trpc/server/observableinstead of async generators, as the library explicitly checksisObservable(result)and throws an error otherwise
Files:
apps/desktop/src/renderer/stores/tabs/useAgentHookListener.tsapps/desktop/src/renderer/screens/main/components/StatusIndicator/StatusIndicator.tsxapps/desktop/src/main/lib/agent-setup/notify-hook.tsapps/desktop/src/renderer/stores/tabs/types.tsapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupStrip/GroupStrip.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceListItem.tsxapps/desktop/src/shared/tabs-types.tsapps/desktop/src/main/lib/notifications/server.tsapps/desktop/src/shared/constants.tsapps/desktop/src/main/lib/agent-setup/agent-wrappers.tsapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupStrip/GroupItem.tsxapps/desktop/src/renderer/stores/tabs/store.tsapps/desktop/src/main/lib/agent-setup/index.tsapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx
**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{ts,tsx}: Use object parameters for functions with 2+ parameters instead of positional arguments
Functions with 2+ parameters should accept a single params object with named properties for self-documentation and extensibility
Use prefixed console logging with context pattern: [domain/operation] message
Extract magic numbers and hardcoded values to named constants at module top
Use lookup objects/maps instead of repeated if (type === ...) conditionals
Avoid usinganytype - maintain type safety in TypeScript code
Never swallow errors silently - at minimum log them with context
Import from concrete files directly when possible - avoid barrel file abuse that creates circular dependencies
Avoid deep nesting (4+ levels) - use early returns, extract functions, and invert conditions
Use named properties in options objects instead of boolean parameters to avoid boolean blindness
Files:
apps/desktop/src/renderer/stores/tabs/useAgentHookListener.tsapps/desktop/src/renderer/screens/main/components/StatusIndicator/StatusIndicator.tsxapps/desktop/src/main/lib/agent-setup/notify-hook.tsapps/desktop/src/renderer/stores/tabs/types.tsapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupStrip/GroupStrip.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceListItem.tsxapps/desktop/src/shared/tabs-types.tsapps/desktop/src/main/lib/notifications/server.tsapps/desktop/src/shared/constants.tsapps/desktop/src/main/lib/agent-setup/agent-wrappers.tsapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupStrip/GroupItem.tsxapps/desktop/src/renderer/stores/tabs/store.tsapps/desktop/src/main/lib/agent-setup/index.tsapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx
apps/desktop/src/renderer/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Never import Node.js modules (fs, path, os, net) in renderer process or shared code - they are externalized for browser compatibility
Files:
apps/desktop/src/renderer/stores/tabs/useAgentHookListener.tsapps/desktop/src/renderer/screens/main/components/StatusIndicator/StatusIndicator.tsxapps/desktop/src/renderer/stores/tabs/types.tsapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupStrip/GroupStrip.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceListItem.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupStrip/GroupItem.tsxapps/desktop/src/renderer/stores/tabs/store.tsapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx
apps/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Use Drizzle ORM for all database operations - never use raw SQL
Files:
apps/desktop/src/renderer/stores/tabs/useAgentHookListener.tsapps/desktop/src/renderer/screens/main/components/StatusIndicator/StatusIndicator.tsxapps/desktop/src/main/lib/agent-setup/notify-hook.tsapps/desktop/src/renderer/stores/tabs/types.tsapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupStrip/GroupStrip.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceListItem.tsxapps/desktop/src/shared/tabs-types.tsapps/desktop/src/main/lib/notifications/server.tsapps/desktop/src/shared/constants.tsapps/desktop/src/main/lib/agent-setup/agent-wrappers.tsapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupStrip/GroupItem.tsxapps/desktop/src/renderer/stores/tabs/store.tsapps/desktop/src/main/lib/agent-setup/index.tsapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
Use Biome for formatting and linting - run at root level with
bun run lint:fixorbiome check --write
Files:
apps/desktop/src/renderer/stores/tabs/useAgentHookListener.tsapps/desktop/src/renderer/screens/main/components/StatusIndicator/StatusIndicator.tsxapps/desktop/src/main/lib/agent-setup/notify-hook.tsapps/desktop/src/renderer/stores/tabs/types.tsapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupStrip/GroupStrip.tsxapps/desktop/src/main/lib/agent-setup/templates/opencode-plugin.template.jsapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceListItem.tsxapps/desktop/src/shared/tabs-types.tsapps/desktop/src/main/lib/notifications/server.tsapps/desktop/src/shared/constants.tsapps/desktop/src/main/lib/agent-setup/agent-wrappers.tsapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupStrip/GroupItem.tsxapps/desktop/src/renderer/stores/tabs/store.tsapps/desktop/src/main/lib/agent-setup/index.tsapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx
**/*.tsx
📄 CodeRabbit inference engine (AGENTS.md)
One component per file - do not create multi-component files
Files:
apps/desktop/src/renderer/screens/main/components/StatusIndicator/StatusIndicator.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupStrip/GroupStrip.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceListItem.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupStrip/GroupItem.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx
🧠 Learnings (3)
📚 Learning: 2025-11-24T21:32:21.725Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: apps/desktop/CLAUDE.md:0-0
Timestamp: 2025-11-24T21:32:21.725Z
Learning: Applies to apps/desktop/**/AGENTS.md : Document agent responsibilities, capabilities, and interaction patterns in AGENTS.md
Applied to files:
apps/desktop/src/renderer/stores/tabs/useAgentHookListener.tsapps/desktop/docs/EXTERNAL_FILES.mdapps/desktop/src/main/lib/agent-setup/templates/notify-hook.template.sh
📚 Learning: 2026-01-02T06:50:28.671Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-02T06:50:28.671Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Use Biome for formatting and linting - run at root level with `bun run lint:fix` or `biome check --write`
Applied to files:
biome.jsonc
📚 Learning: 2026-01-02T06:50:28.671Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-02T06:50:28.671Z
Learning: Applies to apps/desktop/src/shared/ipc-channels.ts : Define IPC channel types in apps/desktop/src/shared/ipc-channels.ts before implementing handlers
Applied to files:
apps/desktop/src/shared/tabs-types.tsapps/desktop/src/main/lib/notifications/server.ts
🧬 Code graph analysis (8)
apps/desktop/src/renderer/stores/tabs/useAgentHookListener.ts (2)
apps/desktop/src/shared/constants.ts (1)
NOTIFICATION_EVENTS(42-45)apps/desktop/src/shared/debug.ts (1)
debugLog(26-30)
apps/desktop/src/renderer/screens/main/components/StatusIndicator/StatusIndicator.tsx (2)
apps/desktop/src/shared/tabs-types.ts (1)
ActivePaneStatus(23-23)apps/desktop/src/renderer/screens/main/components/StatusIndicator/index.ts (3)
ActivePaneStatus(2-2)StatusIndicator(4-4)getStatusTooltip(3-3)
apps/desktop/src/renderer/stores/tabs/types.ts (1)
apps/desktop/src/shared/tabs-types.ts (1)
PaneStatus(20-20)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupStrip/GroupStrip.tsx (3)
apps/desktop/src/renderer/screens/main/components/StatusIndicator/StatusIndicator.tsx (1)
ActivePaneStatus(5-5)apps/desktop/src/shared/tabs-types.ts (2)
ActivePaneStatus(23-23)pickHigherStatus(40-48)apps/desktop/src/renderer/screens/main/components/StatusIndicator/index.ts (1)
ActivePaneStatus(2-2)
apps/desktop/src/shared/tabs-types.ts (3)
apps/desktop/src/renderer/stores/tabs/types.ts (1)
PaneStatus(12-12)apps/desktop/src/renderer/screens/main/components/StatusIndicator/StatusIndicator.tsx (1)
ActivePaneStatus(5-5)apps/desktop/src/renderer/screens/main/components/StatusIndicator/index.ts (1)
ActivePaneStatus(2-2)
apps/desktop/src/main/lib/agent-setup/agent-wrappers.ts (2)
apps/desktop/src/main/lib/agent-setup/templates/opencode-plugin.template.js (1)
notifyPath(32-32)apps/desktop/src/main/lib/agent-setup/notify-hook.ts (1)
getNotifyScriptPath(15-17)
apps/desktop/src/renderer/stores/tabs/store.ts (2)
apps/desktop/src/renderer/stores/tabs/utils.ts (1)
extractPaneIdsFromLayout(54-65)apps/desktop/src/renderer/stores/tabs/types.ts (1)
TabsState(26-28)
apps/desktop/src/main/lib/agent-setup/index.ts (1)
apps/desktop/src/main/lib/agent-setup/agent-wrappers.ts (1)
cleanupGlobalOpenCodePlugin(191-209)
🪛 LanguageTool
apps/desktop/docs/EXTERNAL_FILES.md
[style] ~85-~85: This phrase is redundant. Consider using “outside”.
Context: ...External Files Before adding new files outside of ~/.superset[-dev]/: 1. **Consider if...
(OUTSIDE_OF)
🪛 Shellcheck (0.11.0)
apps/desktop/src/main/lib/agent-setup/templates/notify-hook.template.sh
[error] 2-2: You need a space after the '{'.
(SC1054)
[error] 2-2: Couldn't parse this brace group. Fix to allow more checks.
(SC1073)
[error] 2-2: You need a space after the '{'.
(SC1054)
[warning] 2-2: This } is literal. Check expression (missing ;/\n?) or quote it.
(SC1083)
[warning] 2-2: This } is literal. Check expression (missing ;/\n?) or quote it.
(SC1083)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
- GitHub Check: Deploy Web
- GitHub Check: Deploy Marketing
- GitHub Check: Deploy API
- GitHub Check: Deploy Admin
- GitHub Check: Deploy Docs
- GitHub Check: Build
🔇 Additional comments (42)
biome.jsonc (1)
9-9: LGTM!The template file exclusions are appropriate since
{{MARKER}}and similar placeholders in the template files would cause Biome parsing errors. This aligns with the template-driven approach introduced in this PR.apps/desktop/docs/EXTERNAL_FILES.md (1)
1-99: Well-documented external file management.This documentation clearly explains the environment-specific directories, shell integration, and debugging steps. It will be valuable for preventing dev/prod conflicts and onboarding new contributors.
apps/desktop/src/main/lib/agent-setup/agent-wrappers.ts (4)
15-24: LGTM! Good versioning and template approach.The versioned marker allows detection of stale plugins during cleanup, and the template path approach improves maintainability as noted in prior review feedback.
80-80: LGTM! UserPromptSubmit hook enables "Start" event detection.Adding this hook allows the shell script to detect when a user submits a prompt, enabling the working indicator to activate immediately rather than waiting for agent activity.
191-209: LGTM! Safe cleanup with signature verification.The cleanup function correctly:
- Checks for file existence before reading
- Verifies the signature to avoid deleting user plugins
- Catches and logs errors without crashing
- Uses informative logging
143-148: Verify that thetemplates/directory exists and is properly bundled with the application.The
OPENCODE_PLUGIN_TEMPLATE_PATHresolves topath.join(__dirname, "../../templates/opencode-plugin-template.txt"). In production builds,__dirnamepoints to the directory of the bundled main process. Ensure the template file is either:
- Present in the expected location relative to the bundled output, or
- Configured to be copied as an asset during the build process
apps/desktop/src/main/lib/agent-setup/index.ts (1)
38-39: LGTM! Correct cleanup ordering.Placing the global plugin cleanup after directory creation but before creating new plugins ensures the migration from global to environment-specific paths is clean.
apps/desktop/src/main/lib/agent-setup/notify-hook.ts (1)
9-24: LGTM! Consistent template-based generation.The template approach matches the pattern used in
agent-wrappers.tsfor the OpenCode plugin. The placeholder replacement for{{MARKER}}and{{DEFAULT_PORT}}is straightforward and correct.apps/desktop/src/main/lib/agent-setup/templates/notify-hook.template.sh (2)
1-3: Shellcheck errors are false positives for template placeholders.The Shellcheck errors on line 2 (
SC1054,SC1073,SC1083) are expected because{{MARKER}}is a template placeholder that will be replaced with# Superset agent notification hookat generation time. The resulting script will be valid.
15-24: Robust event type extraction with good fallback handling.The regex patterns handle optional whitespace around JSON colons, and the Codex-specific mapping (
agent-turn-complete→Stop) is correct. The decision to not default to "Stop" on parse failure (lines 26-28) prevents false completion notifications.apps/desktop/src/main/lib/agent-setup/templates/opencode-plugin.template.js (3)
25-28: LGTM! Singleton pattern prevents duplicate registration.Using a global flag (
__supersetOpencodeNotifyPluginV8) ensures the plugin only registers once even if the module is loaded multiple times. The version suffix allows future versions to re-register if needed.
96-143: Well-designed state machine for session lifecycle.The
handleBusyandhandleStopfunctions correctly implement:
- Root session tracking (first busy session becomes root)
- State transitions with deduplication (
idle→busyfor Start,busy→idlefor Stop)- Reset of
rootSessionIDafter Stop to allow tracking new conversationsThe
stopSentflag prevents duplicate Stop notifications within a busy period.
145-186: LGTM! Comprehensive event handling with backward compatibility.The event handler covers:
- Modern
session.statuswithbusy/idlesubtypes- Legacy
session.busy/session.idleevents- Error handling via
session.error- Permission requests via
permission.askChild session filtering at line 151 ensures subagent events don't trigger notifications.
apps/desktop/src/shared/constants.ts (1)
43-43: LGTM!The rename from
AGENT_COMPLETEtoAGENT_LIFECYCLEaccurately reflects the broader lifecycle event model (idle → working → permission → review) introduced in this PR.apps/desktop/src/shared/tabs-types.ts (4)
13-34: Well-designed type system with proper documentation.The
PaneStatushierarchy andSTATUS_PRIORITYconstant provide a clear, type-safe foundation for status aggregation. Good use ofsatisfiesto enforce theRecord<PaneStatus, number>constraint while preserving literal types.
40-48: LGTM!The
pickHigherStatusfunction correctly implements priority comparison with proper handling of undefined values.
54-69: Efficient implementation with early exit optimization.The
getHighestPriorityStatusfunction correctly aggregates statuses with an early exit for maximum priority ("permission"). Returningnullfor all-idle scenarios is semantically appropriate for UI components.
114-114: LGTM!The migration from
needsAttention?: booleantostatus?: PaneStatusprovides richer state modeling. The migration path (version 2) in the store handles the breaking change appropriately.apps/desktop/src/renderer/stores/tabs/types.ts (1)
3-12: LGTM!The type imports and API method updates (
setPaneStatus,clearWorkspaceAttentionStatus) are consistent with the new status-based model and maintain clear, descriptive naming.Also applies to: 82-83
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx (3)
318-325: LGTM!Correctly uses
getState()to access current pane status within the effect, avoiding stale closure issues.
387-394: LGTM!The Ctrl+C handler correctly uses
getState()to read and update pane status, clearing transient indicators when the user interrupts agent execution.
395-403: LGTM!The Escape handler correctly uses
getState()to read and update pane status, supporting agent stop operations (e.g., Claude Code "stop generating").apps/desktop/src/renderer/screens/main/components/StatusIndicator/StatusIndicator.tsx (3)
7-30: Excellent use of lookup object pattern.The
STATUS_CONFIGlookup follows the coding guideline to use lookup objects instead of repeated conditionals, and thesatisfiesconstraint ensures type safety while preserving literal types.
43-64: LGTM!The
StatusIndicatorcomponent cleanly implements the three-state visual system (red pulsing, amber pulsing, green static) with proper conditional rendering and accessibility structure.
67-69: LGTM!The
getStatusTooltiphelper provides a clean public API for tooltip integration while maintaining the single source of truth inSTATUS_CONFIG.apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupStrip/GroupStrip.tsx (2)
78-89: LGTM! Clean status aggregation logic.The
tabStatusMapcorrectly aggregates pane statuses per tab using the sharedpickHigherStatusutility. The logic properly filters out idle statuses and only stores active statuses in the map.
140-146: LGTM!The status prop correctly falls back to
nullwhen no aggregated status exists for the tab, matching the expectedPaneStatus | nulltype inGroupItem.apps/desktop/src/renderer/stores/tabs/useAgentHookListener.ts (3)
10-34: Excellent documentation of status mapping and known limitations.The comprehensive JSDoc clearly documents the status transitions and external limitations (Ctrl+C, permission denied, tool failures). This will help maintainers understand the expected behavior and edge cases.
68-73: LGTM!The
StartandPermissionRequestevent handling correctly sets the appropriate statuses, withpermissionproperly overridingworkingas per the priority system.
74-98: LGTM! Well-reasoned Stop event handling.The logic correctly distinguishes between active and inactive panes—setting
idlewhen the user is already watching (avoiding unnecessary indicators) andreviewwhen they need to be notified. The debug logging will be valuable for troubleshooting.apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceListItem.tsx (3)
115-132: Nice optimization using a generator to avoid array allocation.The generator pattern in
paneStatuses()is a good performance optimization, avoiding intermediate array creation when computing the aggregate status. The memoization dependencies are correct.
134-141: LGTM!The click handler correctly clears workspace attention status when the user selects a workspace, ensuring indicators are acknowledged.
228-239: LGTM! Correct priority ordering for indicators.Status indicators correctly take precedence over unread dots, ensuring users see the most important information first.
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupStrip/GroupItem.tsx (1)
40-44: LGTM!The StatusIndicator is correctly positioned within the tab button and conditionally rendered based on status.
apps/desktop/src/renderer/stores/tabs/store.ts (4)
202-230: LGTM! Well-designed status clearing on tab activation.The status transition logic is correct:
review→idle: User acknowledged completionpermission→working: Assume permission grantedworkingpersists: Only cleared by Stop eventThe
hasChangesgate prevents unnecessary state updates.
538-549: LGTM!The guard correctly prevents corrupting the panes map when called with an unknown
paneId. This defensive approach protects against race conditions where a pane might be removed between the lifecycle event and status update.
799-828: LGTM! Robust migration and merge logic.The migration correctly converts legacy
needsAttentionto the newstatusfield. The merge logic appropriately resets transient states (working,permission) on startup while preservingreviewso users don't miss completion notifications.
525-536: LGTM! Consistent guard pattern across pane mutations.The guards in
markPaneAsUsed,updatePaneCwd, andclearPaneInitialDataconsistently protect against corrupting the panes map when called with unknown pane IDs.Also applies to: 585-617
apps/desktop/src/main/lib/notifications/server.ts (4)
40-68: LGTM! Well-documented event normalization.The
mapEventTypefunction correctly normalizes various event type strings to canonical lifecycle events. The explicitnullreturn for unknown events ensures forward compatibility, and the documentation clearly explains the design decision to not default missingeventTypeto "Stop".
78-116: LGTM! Robust pane resolution with fallbacks.The resolution logic correctly validates that
paneIdexists in state before returning it, preventing stale references from corrupting the store. The fallback chain (paneId → tabId → workspaceId) provides resilience when direct pane IDs aren't available.
119-181: LGTM! Well-designed non-blocking endpoint.The endpoint handler correctly:
- Logs environment mismatches without blocking the agent
- Handles unknown event types gracefully for forward compatibility
- Includes resolved
paneIdin the response for debugging- Emits to
AGENT_LIFECYCLEwith the normalized event type
10-14: LGTM!Re-exporting types from the shared module maintains backward compatibility for existing consumers while centralizing type definitions.
...renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (1)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx (1)
228-238: Consider extracting the transient status clearing pattern.The pattern of checking and clearing transient statuses (
working|permission→idle) is repeated four times throughout the file. Extracting this into a helper would reduce duplication and make the intent clearer.🔎 Proposed helper function
Add at module level:
/** * Clear transient pane status (working/permission) if currently set. * Review status persists (user needs to acknowledge completed work). */ function clearTransientPaneStatus(paneId: string) { const currentPane = useTabsStore.getState().panes[paneId]; if ( currentPane?.status === "working" || currentPane?.status === "permission" ) { useTabsStore.getState().setPaneStatus(paneId, "idle"); } }Then replace the four duplicated blocks with:
-const currentPane = useTabsStore.getState().panes[paneId]; -if ( - currentPane?.status === "working" || - currentPane?.status === "permission" -) { - setPaneStatus(paneId, "idle"); -} +clearTransientPaneStatus(paneId);Also applies to: 323-330, 392-408
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsxapps/desktop/tsconfig.json
🧰 Additional context used
📓 Path-based instructions (6)
apps/desktop/**/*.{ts,tsx}
📄 CodeRabbit inference engine (apps/desktop/AGENTS.md)
apps/desktop/**/*.{ts,tsx}: For Electron interprocess communication, ALWAYS use tRPC as defined insrc/lib/trpc
Use alias as defined intsconfig.jsonwhen possible
Prefer zustand for state management if it makes sense. Do not use effect unless absolutely necessary.
For tRPC subscriptions with trpc-electron, ALWAYS use the observable pattern from@trpc/server/observableinstead of async generators, as the library explicitly checksisObservable(result)and throws an error otherwise
Files:
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx
**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{ts,tsx}: Use object parameters for functions with 2+ parameters instead of positional arguments
Functions with 2+ parameters should accept a single params object with named properties for self-documentation and extensibility
Use prefixed console logging with context pattern: [domain/operation] message
Extract magic numbers and hardcoded values to named constants at module top
Use lookup objects/maps instead of repeated if (type === ...) conditionals
Avoid usinganytype - maintain type safety in TypeScript code
Never swallow errors silently - at minimum log them with context
Import from concrete files directly when possible - avoid barrel file abuse that creates circular dependencies
Avoid deep nesting (4+ levels) - use early returns, extract functions, and invert conditions
Use named properties in options objects instead of boolean parameters to avoid boolean blindness
Files:
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx
apps/desktop/src/renderer/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Never import Node.js modules (fs, path, os, net) in renderer process or shared code - they are externalized for browser compatibility
Files:
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx
**/*.tsx
📄 CodeRabbit inference engine (AGENTS.md)
One component per file - do not create multi-component files
Files:
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx
apps/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Use Drizzle ORM for all database operations - never use raw SQL
Files:
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
Use Biome for formatting and linting - run at root level with
bun run lint:fixorbiome check --write
Files:
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx
🧠 Learnings (7)
📚 Learning: 2025-12-21T04:39:28.543Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: apps/desktop/AGENTS.md:0-0
Timestamp: 2025-12-21T04:39:28.543Z
Learning: Applies to apps/desktop/**/*.{ts,tsx} : Use alias as defined in `tsconfig.json` when possible
Applied to files:
apps/desktop/tsconfig.json
📚 Learning: 2026-01-02T06:50:28.671Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-02T06:50:28.671Z
Learning: Applies to apps/desktop/src/renderer/**/*.{ts,tsx} : Never import Node.js modules (fs, path, os, net) in renderer process or shared code - they are externalized for browser compatibility
Applied to files:
apps/desktop/tsconfig.json
📚 Learning: 2026-01-02T06:50:28.671Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-02T06:50:28.671Z
Learning: Applies to apps/*/src/components/{ui,ai-elements,react-flow}/*.tsx : Use kebab-case single files for shadcn/ui components (e.g., button.tsx, base-node.tsx) in src/components/ui/, src/components/ai-elements, and src/components/react-flow/
Applied to files:
apps/desktop/tsconfig.json
📚 Learning: 2026-01-02T06:50:28.671Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-02T06:50:28.671Z
Learning: Applies to **/*.{ts,tsx} : Avoid using `any` type - maintain type safety in TypeScript code
Applied to files:
apps/desktop/tsconfig.json
📚 Learning: 2025-12-21T04:39:28.543Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: apps/desktop/AGENTS.md:0-0
Timestamp: 2025-12-21T04:39:28.543Z
Learning: Applies to apps/desktop/**/*.{ts,tsx} : For Electron interprocess communication, ALWAYS use tRPC as defined in `src/lib/trpc`
Applied to files:
apps/desktop/tsconfig.json
📚 Learning: 2026-01-02T06:50:28.671Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-02T06:50:28.671Z
Learning: Applies to apps/desktop/src/lib/*.ts : Never import Node.js modules in shared code like electron-router-dom.ts - it runs in both main and renderer processes
Applied to files:
apps/desktop/tsconfig.json
📚 Learning: 2026-01-02T06:50:28.671Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-02T06:50:28.671Z
Learning: Applies to apps/desktop/src/lib/electron-router-dom.ts : Do not import Node.js modules like node:path or dotenv in electron-router-dom.ts and similar shared files - they run in both main and renderer processes
Applied to files:
apps/desktop/tsconfig.json
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
- GitHub Check: Deploy Docs
- GitHub Check: Deploy Admin
- GitHub Check: Deploy API
- GitHub Check: Deploy Marketing
- GitHub Check: Deploy Web
- GitHub Check: Build
🔇 Additional comments (2)
apps/desktop/tsconfig.json (1)
17-23: LGTM! Appropriate exclusion of template files.The addition of
"src/**/templates/**"correctly excludes the runtime template assets (.template.js,.template.sh) introduced in this PR from TypeScript compilation. These files are copied/used as-is at runtime and should not be type-checked or compiled.apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx (1)
228-238: Race condition fix confirmed—correctly uses fresh state.The previous race condition has been properly addressed. Line 232 now reads
useTabsStore.getState().panes[paneId]to get fresh pane status at event time instead of relying on the stale closure. This ensures the status check reflects the current state when the terminal exit event arrives.
PR #588 replaced needsAttention with PaneStatus (idle/working/permission/review). Remove the obsolete setNeedsAttention method and update plan doc references.
PR #588 replaced needsAttention with PaneStatus (idle/working/permission/review). Remove the obsolete setNeedsAttention method and update plan doc references.
PR #588 replaced needsAttention with PaneStatus (idle/working/permission/review). Remove the obsolete setNeedsAttention method and update plan doc references.
* feat(desktop): terminal persistence via daemon process Add terminal session persistence using a background daemon process that survives app restarts. Terminals can now be resumed with full scrollback and TUI state (like Claude Code) exactly where they left off. Key changes: - New terminal host daemon with per-session PTY subprocesses - DaemonTerminalManager as drop-in replacement for TerminalManager - Settings toggle for terminal persistence (requires app restart) - Schema migration for terminal_persistence setting - Smooth workspace/tab switching via CSS visibility (avoids remount) * fix(desktop): add @xterm/headless dependency and fix type error - Add missing @xterm/headless package to desktop dependencies - Use @ts-expect-error for known xterm addon type mismatch (SerializeAddon types expect @xterm/xterm but works with @xterm/headless) * fix(desktop): address CodeRabbit review feedback - Await async handler in terminal-host/index.ts to prevent unhandled promise rejections - Track session cleanup timeouts in daemon-manager.ts and clear on dispose to prevent memory leaks - Move headless-emulator.test.ts to co-locate with implementation (from __tests__/ subfolder) - Fix timeout race conditions in test files by tracking settlement state and clearing timeouts * fix(desktop): fix CI errors after test file move - Update relative imports in headless-emulator.test.ts after moving from __tests__/ - Externalize @xterm/* packages in Vite config (incorrect package exports for bundlers) * refactor(desktop): centralize DEFAULT_TERMINAL_PERSISTENCE constant Extract the terminal persistence default value (false) into a shared constant to avoid duplicating magic values across the codebase. * refactor(desktop): address PR review comments - Add SUPERSET_TERMINAL_DEBUG env var for conditional debug logging - Wrap verbose console.log statements in debug checks (terminal router and terminal host client) - Make sendRequest<T> generic to eliminate type casts in public API - Use switch statement for event payload narrowing - Remove unused imports (IpcSuccessResponse, IpcErrorResponse) * fix(desktop): align @xterm/headless version with @xterm/xterm Downgrade @xterm/headless from ^6.0.0 to ^5.5.0 to match @xterm/xterm version. Mismatched major versions can cause runtime failures. Also removes now-unnecessary @ts-expect-error directive since matching versions resolve the type compatibility issue. * fix(desktop): address code review feedback for terminal persistence P0 Fixes: - Use getActiveTerminalManager() instead of direct terminalManager import in workspaces.ts, projects.ts, and main/windows/main.ts - Add missing await for async getSessionCountByWorkspaceId() call - Update reconcileOnStartup() to preserve sessions for true app restart persistence, only killing orphaned sessions (deleted workspaces) P1 Fixes: - Call shutdownOrphanedDaemon() on startup when persistence is disabled - Fix error code mismatch: QUEUE_FULL -> WRITE_QUEUE_FULL in client.ts P2 Fixes: - Only keep terminal-containing tabs mounted when persistence enabled, non-terminal tabs use normal unmount behavior to save memory Additional Fixes (from runtime testing): - Fix resize race condition: forward resize to daemon regardless of local session cache state (handles startup race) - Add safety checks in initHistoryWriter: validate scrollback is string and cap at 512KB to prevent RangeError: Invalid array length * fix(desktop): guard pane updates against deleted panes When async processes like Claude Code still hold pane references after the terminal is closed, calling setNeedsAttention (or similar) would create an undefined entry in the panes record. This caused GroupStrip to crash when iterating Object.values(panes). Now all pane update functions check if the pane exists first and return early (no-op) if it doesn't, preventing undefined entries. * fix(desktop): split terminal host control/stream sockets Prevents createOrAttach timeouts by removing head-of-line blocking when terminal output backpressures. Adds protocol v2 hello with clientId/role, pairs control+stream sockets, and adds a backpressure isolation integration test. * chore(desktop): biome format + archive execplan Formats a few files to satisfy biome format checks and moves the terminal host dual-socket ExecPlan to apps/desktop/plans/done/. * fix(desktop): spawn daemon when token missing Read terminal-host auth token after ensuring a daemon exists; if token is missing with a live socket, restart the daemon to re-create a coherent socket+token pair. * fix(desktop): harden terminal persistence data perms Ensure ~/.superset* is created/repair-chmodded to 0700 and history/log files are written with 0600 where applicable. Also closes the daemon.log fd in the parent after spawning the daemon. * fix(desktop): address terminal persistence review feedback - Keep terminal tabs mounted even when switching to empty workspaces - Gate noisy logs behind SUPERSET_TERMINAL_DEBUG - Use TRPCError for settings validation - Fix history reinit sizing + avoid silent catch blocks - Gate PTY subprocess spawn logging * fix(desktop): bundle @xterm packages for terminal-host daemon The daemon runs as standalone Node.js outside app.asar and needs @xterm/headless and @xterm/addon-serialize bundled to function. * wip: dx hardening plan * fix(desktop): remove obsolete setNeedsAttention after rebase PR #588 replaced needsAttention with PaneStatus (idle/working/permission/review). Remove the obsolete setNeedsAttention method and update plan doc references. * docs(desktop): update terminal persistence exec plan * fix(desktop): harden terminal persistence DX * fix(desktop): prevent history init buffer loop * fix(desktop): enable terminal stream when snapshot empty * fix(desktop): prevent scheduler deadlock on React StrictMode unmount React StrictMode simulates mount → unmount → mount cycles. The terminal attach scheduler was deadlocking because: 1. First mount starts a task (inFlight++) 2. Unmount cancels the task but it's still executing 3. Second mount queues a new task 4. tRPC callbacks for unmounted components don't fire reliably 5. done() never gets called → inFlight stays stuck at MAX_CONCURRENT Fix: Track running tasks per paneId and immediately decrement inFlight when canceling a running task. Also add optional debug logging (enable via localStorage.setItem('SUPERSET_TERMINAL_DEBUG', '1')). * fix(desktop): address PR review blocking issues - P0: Fix attach-scheduler race condition where inFlight counter could be double-decremented when cancel() and done() both fire for the same task. Added `released` flag to ensure idempotent completion. - P1: Fix sendRequestOnStream NDJSON parsing bug that dropped messages arriving in the same TCP read as the hello response. Now feeds remainder data to streamParser after parsing first response. - P1: Fix maybeApplyInitialState catch block that would wedge terminal on restoration error. Now fail-open by setting isStreamReady and flushing pending events even on error. - P2: Fix coldRestoreState memory leak by cleaning up on unmount. Previously scrollback (potentially MBs per pane) was only cleared on "Start Shell" click, not on component unmount. * fix(desktop): address oracle feedback on PR fixes - Move coldRestoreState cleanup into detachTimeout to preserve StrictMode unmount/remount semantics. The module-level Map is specifically designed to survive quick remounts, so deleting immediately on unmount was wrong. - Harden attach-scheduler cancel path: when a running task is canceled, re-queue any waiting task for the same paneId. This mirrors the done() behavior and protects against the "done never fires" scenario. * docs(desktop): document ordering assumption in sendRequestOnStream Per oracle review suggestion: add JSDoc comment explaining that the daemon's hello handler guarantees response is first frame. Documents when this assumption would need to change (if daemon ever emits events before hello response). * fix(desktop): resolve type errors after rebase onto main - Add stub terminal-history module (real impl in Phase 4) - Fix port-manager: add checkOutputForHint method, await async calls - Fix TabsContent: add panes store selector - Fix TabView: add Pane type import - Fix daemon-manager: handle null scrollback from stub reader * fix(desktop): implement daemon signal() support for SIGINT/SIGTERM The daemon's signal() method was a no-op, which meant Ctrl+C through the daemon pathway would silently fail. This adds full signal support: - Add SignalRequest type and signal to RequestTypeMap - Add Signal IPC frame type (6) distinct from Kill - Implement handleSignal in pty-subprocess without kill escalation - Add sendSignal chain through session → terminal-host → daemon → client - Update daemon-manager to use client.signal() instead of no-op Unlike kill(), signal() does not mark the session as terminating and does not escalate to SIGKILL, allowing the process to continue running. * docs(desktop): add terminal host event semantics documentation Documents the event delivery model for the daemon protocol: - Event types (data, exit, error) - Dual-socket model (control vs stream) - At-most-once delivery semantics (no durability/retries) - In-order guarantees within sessions - Multi-level backpressure handling - Error codes and race condition handling * feat(desktop): implement cold restore terminal history persistence Replaces the stub terminal-history.ts with a working implementation for Phase 4 of terminal persistence. Enables terminal recovery after app/system restarts when the daemon is not running. Storage format: - scrollback.bin: Raw PTY output (append-only) - meta.json: Session metadata (cols, rows, cwd, timestamps) Cold restore detection: - meta.json without endedAt → unclean shutdown → can restore - meta.json with endedAt → clean shutdown → no restore HistoryWriter API: - init(initialScrollback?) - create directory and files - write(data) - append PTY output - flush() - flush pending writes - close(exitCode?) - write endedAt to meta.json - reinitialize() - reset for clear scrollback - deleteHistory() - remove all files HistoryReader API: - exists() - check if history available - readMetadata() - get cols/rows/cwd/endedAt - readScrollback() - get terminal content - cleanup() - delete history files * feat(desktop): add telemetry for terminal persistence events Adds tracking for key terminal persistence lifecycle events: - terminal_cold_restored: Triggered when recovering terminal after reboot with scrollback_bytes to measure restoration payload size - terminal_warm_attached: Triggered when reconnecting to existing daemon session with snapshot_bytes for payload metrics - terminal_daemon_disconnected: Triggered on daemon connection loss with active_session_count for impact assessment * fix(desktop): trigger cold restore on daemon session loss When daemon restarts and loses sessions, "Session not found" errors were treated as non-fatal (just showed toast). This prevented the retry UI from appearing and cold restore from triggering. Changes: - Promote "Session not found" WRITE_FAILED errors to connection error so retry UI appears instead of endless toast spam - Handle isColdRestore in handleRetryConnection so clicking retry can trigger cold restore if disk history is available - Update both event handler locations for consistency Now when daemon dies and user clicks retry, cold restore kicks in if history exists on disk (meta.json without endedAt). * fix(desktop): suppress toast when showing retry UI for session loss When daemon restarts and loses terminal sessions, show only the retry UI without also showing a toast notification. This prevents confusing UX where both a toast and the retry overlay appear simultaneously. * fix(desktop): suppress toast for transient PTY not spawned errors During daemon recovery, writes may arrive before the PTY subprocess is fully initialized. Treat "PTY not spawned" as a transient error that doesn't need a toast notification - just log to terminal. * fix(desktop): clear connection error on successful initial attach When daemon restarts and component remounts, the background createOrAttach may succeed while the error overlay is still visible. Clear connectionError on success to dismiss the overlay automatically. * fix(desktop): trigger cold restore on daemon session loss When "Session not found" error occurs, clear the stale cache entry so the next createOrAttach properly checks disk history and triggers cold restore instead of creating a new session. * fix(desktop): re-focus terminal after successful retry connection After clicking "Retry Connection" and the connection succeeds, re-focus the terminal so keyboard input works immediately. Skip focus for cold restore since user needs to click overlay button. * fix(desktop): cold restore for TUI apps with empty scrollback Two fixes: 1. Check rawScrollback === null instead of !rawScrollback. TUI apps in alternate screen may have empty normal buffer, which is still valid for cold restore (empty string is truthy check fix). 2. Use fresh xterm ref in handleRetryConnection onSuccess callback to handle potential component remount during async operation. * fix(desktop): focus terminal after clicking Start Shell After cold restore, clicking "Start Shell" creates a new session but wasn't focusing the terminal, causing keystrokes to go elsewhere. * fix(desktop): keep terminal stream alive on exit * chore(desktop): fix biome check * chore(desktop): fix lint warnings * fix(desktop): address persistence review blockers * fix(desktop): harden terminal history caps * docs(desktop): add terminal runtime abstraction plan * docs(desktop): expand plan for Terminal.tsx decomposition * docs(desktop): add target architecture snippets to plan * docs(desktop): refine terminal runtime abstraction plan * docs(desktop): add remote runner notes to plan * docs(desktop): align terminal runtime plan with cloud provider direction * docs(desktop): add terminal runtime architecture review packet * docs(desktop): narrow changes router reference list * docs(desktop): incorporate architecture feedback into runtime rewrite plan * refactor(desktop): introduce WorkspaceRuntime abstraction Adds a provider-neutral runtime layer that abstracts terminal backend selection: - WorkspaceRuntimeRegistry: process-scoped registry for runtime selection - LocalTerminalRuntime: adapts TerminalManager/DaemonTerminalManager - Capability-based checks: uses `terminal.management !== null` instead of `instanceof DaemonTerminalManager` Key changes: - New workspace-runtime module with types, registry, and local implementation - tRPC terminal router migrated to use registry pattern - All call sites updated to use getForWorkspaceId when workspaceId is in-hand - Regression tests for capability presence and stream contract This foundation enables future cloud workspace providers without spreading backend-specific branching throughout the codebase. * WIP: route-based settings/dashboard structure alignment with upstream - Add _dashboard route group with workspace/tasks pages - Rebuild settings routing/layout/sidebar under routes - Port settings pages: account, appearance, keyboard, presets, team, ringtones, project, workspace - Add terminal settings page at /settings/terminal - Update navigation components to use router instead of app-state - Remove obsolete route pages that conflict with upstream _dashboard structure Still needs: - Full merge with origin/main - Complete app-state removal - Terminal backend conflict resolution * fix(desktop): improve terminal kill-all reliability and UI feedback Fixes two bugs in the terminal settings page: 1. UI not refreshing after kill: Added optimistic UI clearing - Clear session list immediately on click for instant feedback - Rollback on error, delayed refetch on success 2. Sessions surviving kill: Added wait/verify loop in backend - Poll daemon up to 10x100ms until sessions are actually dead - Return accurate killedCount and remainingCount - Show warning toast if some sessions survive Also adds diagnostic logging for troubleshooting kill operations. * fix(desktop): keep killed terminals dead * fix(desktop): disable terminal session actions when none * fix(desktop): memoize terminal session lists * remove old code * fix(desktop): log best-effort failures in terminal utils * fix: harden terminal persistence paths and logs * chore: format terminal router and client
Summary
UserPromptSubmit,Stop,PermissionRequest) and OpenCode plugin (session.status,permission.ask)Demo Loom
P.s. the bug in the loom is fixed now.
Why / Context
Users managing multiple agent workspaces need visual feedback on agent state without constantly switching tabs. Status indicators surface:
How It Works
Status Flow:
notify.shscript sends HTTP POST to local notification server (port 31416)useAgentHookListenerupdates pane status in Zustand storeStatus Priority: permission (red) > working (amber) > review (green)
UI Locations:
GroupItem+StatusIndicatorWorkspaceListItem+StatusIndicatorClick Behavior:
review→idle(acknowledged)permission→working(assumes permission granted, agent continues)working→ unchanged (persists until Stop event)Manual QA Checklist
Claude Code Integration
OpenCode Integration
session.statuswithstatus.type === "busy"session.statuswithstatus.type === "idle"permission.askparentID)Indicator Colors
App Lifecycle
Dev/Prod Separation
Testing
bun run typecheck✓bun run lint✓bun test- 450/451 pass (1 unrelated pre-existing failure)Debug Mode
Enable debug logging for future QA with:
Logs:
[debug:notifications](server),[debug:agent-hooks](renderer)Design Decisions
UserPromptSubmitfor Start instead ofPreToolUse: Simpler mental model; user submits prompt = agent starts working. Tool-level granularity adds complexity without clear UX benefit.session.idleon completion. Without filtering, users would get notification spam.isUnreadflag is a manual marker (Mark as Unread context menu), independent of agent lifecycle. Blue dot only shows when no agent status is active.Known Limitations
These are external limitations of Claude Code / OpenCode hook systems:
Solved limitations:
User interrupt (Ctrl+C)→ Now detected in terminal, clears status immediatelyESC to stop generation→ Now detected in terminal, clears status immediatelyTerminal exit→ Now clears "working" and "permission" status automaticallyDocumented inline in
useAgentHookListener.ts.Commits
a9cd65d4- Base feature: 3-color workspace status indicators6e4a8d0e- Fix: Use StatusIndicator in workspace sidebar649258f7- Fix: Correct OpenCode event types for status indicators60c36400- Fix: Clear pane status on terminal exit56548784- Fix: Clear status on ESC/Ctrl+C to handle agent interrupts252f7f3d- Fix: Use session.status event for OpenCode busy detection1889ad4f- Feat: Add debug logging utility with SUPERSET_DEBUG flagFollow-ups
Summary by CodeRabbit
New Features
Improvements
Documentation
Tests
✏️ Tip: You can customize this high-level summary in your review settings.