Skip to content

refactor: added agentStore for agent related state management. useAgentListeners hook has replaced IPC listener code in App.tsx#341

Merged
reachrazamair merged 6 commits into
mainfrom
code-refactor
Feb 11, 2026
Merged

refactor: added agentStore for agent related state management. useAgentListeners hook has replaced IPC listener code in App.tsx#341
reachrazamair merged 6 commits into
mainfrom
code-refactor

Conversation

@reachrazamair
Copy link
Copy Markdown
Contributor

Renderer State Refactoring: agentStore + App.tsx Extraction

Summary

  • Introduce agentStore (Zustand) to centralize agent detection cache, error recovery actions, and process queue management — replacing scattered App.tsx local state and callbacks
  • Extract useAgentListeners hook (~1,520 lines) from App.tsx, moving all IPC listener setup (onData, onUsage, onStderr, onExit, onAgentError, onThinkingChunk, onToolExecution, etc.) into a dedicated hook under hooks/agent/
  • Extract processQueuedItem (~280 lines) from App.tsx into an agentStore action, decoupling queue processing from the React component tree
  • Move addLogToTab from App.tsx useCallback to a sessionStore action for consistent log management
  • Add defensive fixes: sanitize Electron/IDE env vars from child processes (STRIPPED_ENV_VARS in envBuilder.ts) and add stdin error handler in ChildProcessSpawner.ts to prevent silent EPIPE failures
  • Organize hooks directory: relocate root-level hooks into domain subdirectories (useInlineWizardbatch/, useStatsstats/)
  • Remove dead code: unused imports, stale utility functions, and orphaned graph-data-builder code across 90+ files

Key changes

Area What changed
stores/agentStore.ts New Zustand store (534 lines) — agent detection cache, error recovery, processQueuedItem action
hooks/agent/useAgentListeners.ts New hook (1,582 lines) — all IPC listeners extracted from App.tsx
stores/sessionStore.ts Added addLogToTab action
App.tsx Reduced from ~14,500 → ~12,650 lines (~1,850 lines removed)
envBuilder.ts Strip ELECTRON_RUN_AS_NODE, CLAUDECODE, NODE_ENV, etc. from spawned agent environments
ChildProcessSpawner.ts Stdin error handler for EPIPE resilience
hooks/batch/, hooks/stats/ Hooks relocated from root hooks/ into domain subdirectories
90+ component files Dead import cleanup, unused variable removal

Test coverage

  • agentStore: 82 tests (70 from Phase 1 edge case expansion + 12 for processQueuedItem)
  • useAgentListeners: 29 tests covering all IPC event handlers
  • sessionStore: 8 new tests for addLogToTab
  • Full suite: 18,714 tests passing, 0 TypeScript errors

Test plan

  • npx tsc --noEmit --pretty — 0 errors
  • npm run test — 18,714 tests pass

…etection cache

Create agentStore as an orchestration layer over sessionStore + IPC,
following the tabStore pattern. Extract 5 error recovery callbacks
(clearAgentError, startNewSession, retry, restart, authenticate) from
App.tsx into agentStore actions. App.tsx retains thin wrappers for UI
side-effects (focus input, close modal).

- Create agentStore with agent detection cache (refreshAgents, getAgentConfig)
- Create error recovery actions that compose sessionStore mutations + IPC
- Create killAgent/interruptAgent lifecycle actions
- Wire App.tsx error recovery to agentStore (34 new tests, 18,618 total)
Add comprehensive edge cases for all agentStore actions: retry/restart
order verification, authenticate idempotency, killAgent/interruptAgent
isolation, action stability for all 9 actions, getAgentActions
end-to-end verification, store reset, and concurrent recovery scenarios.
…pp.tsx

Phase 2 — useAgentListeners hook:
- Extract IPC listener useEffect (~1,520 lines) from App.tsx into
  src/renderer/hooks/agent/useAgentListeners.ts
- Covers onData, onUsage, onStderr, onExit, onAgentError, onThinkingChunk,
  onToolExecution, onCommandExit, onSlashCommands, onSessionId, onSshRemote
- 29 tests for listener registration and cleanup

Phase 3 — processQueuedItem + addLogToTab:
- Move processQueuedItem (~280 lines) from App.tsx to agentStore action
- Move addLogToTab from App.tsx useCallback to sessionStore action
- App.tsx delegates via thin wrappers to store actions
- 48 new agentStore tests (82 total), 8 new sessionStore tests (88 total)

Hooks directory cleanup:
- useInlineWizard.ts → hooks/batch/useInlineWizard.ts
- useStats.ts → hooks/stats/useStats.ts
- Update barrel exports and ~90 import paths

Defensive fixes (main process):
- Strip Electron/IDE env vars from child processes (envBuilder.ts)
- Add stdin error handler in ChildProcessSpawner.ts

Dead code removal across ~60 components (unused React imports, unused
variables, unreachable code paths in graphDataBuilder/DocumentEditor).

App.tsx: 14,500 → 12,650 lines. All 18,714 tests pass.
@claude
Copy link
Copy Markdown

claude Bot commented Feb 11, 2026

Pull Request Review: agentStore + App.tsx Refactoring

Overview

This is an excellent refactoring that significantly improves the codebase architecture. The extraction of ~1,850 lines from App.tsx into dedicated stores and hooks is a major win for maintainability.

Strengths

1. Architecture & Design ✅

  • Strong separation of concerns: Agent lifecycle logic properly moved to agentStore, IPC listeners to useAgentListeners
  • Zustand store pattern: Correctly follows the established tabStore pattern — orchestration actions compose sessionStore mutations with IPC calls
  • State ownership: Smart design keeping session-level state in sessionStore while agentStore handles orchestration
  • Hook extraction: useAgentListeners (~1,582 lines) is well-structured with clear comments explaining design decisions

2. Code Quality ✅

  • Test coverage: Outstanding — 18,714 tests passing with 82 tests for agentStore, 29 for useAgentListeners, 8 for addLogToTab
  • Documentation: Excellent inline comments explaining rationale
  • Type safety: Proper TypeScript usage throughout, interfaces well-defined (ProcessQueuedItemDeps, UseAgentListenersDeps)
  • Error handling: Defensive error handling maintained (try/catch, early returns, safety checks)

3. Critical Defensive Fixes ✅

  • Environment sanitization (envBuilder.ts:59-73): Strips ELECTRON_RUN_AS_NODE, CLAUDECODE, etc. to prevent agent misidentification — this is crucial
  • EPIPE resilience (ChildProcessSpawner.ts:347-363): Stdin error handler prevents silent failures when child exits early
  • Dead code cleanup: 90+ files cleaned up systematically

4. Organization ✅

  • Hook directory structure: hooks/agent/, hooks/batch/, hooks/stats/ — clean domain-based organization
  • Selectors: Proper selector exports (selectAvailableAgents, selectAgentsDetected)
  • Non-React access: getAgentState() and getAgentActions() for service usage

Observations & Recommendations

1. Potential Race Condition in processQueuedItem ⚠️

Location: agentStore.ts:241-243

If tabByItemId is undefined AND getActiveTab() returns undefined (e.g., session has no aiTabs), targetTab is undefined but execution continues. While there is error logging at line 464, the agent spawn at line 308 will use default as tabId.

Recommendation: Add early return after targetTab resolution to prevent spawning with invalid tab ID.

2. SSH Remote Execution Support ✅

Verified: The PR properly passes session.sessionSshRemoteConfig through to window.maestro.process.spawn() at lines 323, 405. This aligns with CLAUDE.md requirements.

3. Custom Agent Configuration ✅

Verified: Custom paths, args, env vars, model, and context window are all properly passed through (lines 318-322, 400-404).

4. Batch Mode Integration ✅

The pauseBatchOnErrorRef and getBatchStateRef integration in useAgentListeners is well-designed. Auto Run batch errors are properly detected and paused.

5. Group Chat Error Delegation ✅

Group chat error handling correctly delegates to setGroupChatError for session IDs matching group-chat pattern.

Minor Suggestions

Testing Edge Cases

While test coverage is excellent, consider adding:

  • Test for processQueuedItem when targetTab is undefined (to verify behavior)
  • Test for environment variable stripping (verify ELECTRON_RUN_AS_NODE is absent in spawned process)

Performance Considerations

  • Batched updates: batchedUpdater for high-frequency log/usage updates is excellent — prevents React render thrashing
  • RAF throttling: thinkingChunkRafIdRef pattern is smart for chunked streaming

Security

  • Environment sanitization: The STRIPPED_ENV_VARS addition is a security improvement — prevents credential leakage from parent process
  • Read-only mode filtering: Properly strips YOLO flags when read-only is active

Breaking Changes

None detected. This is a pure refactoring with equivalent behavior.

Verdict: APPROVE ✅

This PR demonstrates excellent engineering discipline:

  • Surgical extraction of complex logic without behavioral changes
  • Outstanding test coverage (18,714 passing tests)
  • Critical defensive fixes for production reliability
  • Clean architecture following established patterns

The only actionable item is the potential race condition in processQueuedItem (see recommendation above). Otherwise, this is ready to merge.

Great work on this refactoring! The codebase is significantly more maintainable, and the defensive fixes will prevent production issues.

Add early return guard in processQueuedItem when both tabByItemId and
getActiveTab return undefined, preventing spawn with invalid tab ID.
@claude
Copy link
Copy Markdown

claude Bot commented Feb 11, 2026

Code Review: PR #341 - Renderer State Refactoring

This is an excellent architectural refactoring that successfully decouples complex agent lifecycle management from App.tsx into well-organized stores and hooks. The code quality is high, test coverage is comprehensive, and the security improvements are well-designed.


📊 Overall Assessment

Recommendation:APPROVE with minor follow-up items

Summary:

  • +6,674 / -3,962 lines across 99 files
  • App.tsx reduced by ~2,084 lines (253 added, 2,337 deleted)
  • Test coverage: 119 new tests (82 agentStore, 29 useAgentListeners, 8 sessionStore)
  • Full suite: 18,714 tests passing, 0 TypeScript errors
  • Code quality: Strong architectural patterns, proper separation of concerns

🎯 Key Achievements

1. agentStore Introduction ✅

The new Zustand store (534 lines) centralizes agent detection cache, error recovery, and queue processing. Follows the established tabStore pattern correctly.

Strengths:

  • Clean separation of concerns with excellent documentation
  • Proper non-React API support
  • Comprehensive error recovery actions
  • Strong test coverage (82 tests with edge case scenarios)

2. useAgentListeners Hook Extraction ✅

Successfully extracts ~1,520 lines of IPC listener logic from App.tsx into a dedicated hook (1,582 lines).

Strengths:

  • Excellent fresh state access pattern
  • Smart RAF throttling for thinking chunks
  • Proper cleanup strategy (all 11 listeners unsubscribed)
  • Well-structured dependency injection
  • Comprehensive test coverage (29 tests)

3. Security Improvements ✅

Environment variable sanitization and defensive stdin error handling.

Strengths:

  • Well-chosen STRIPPED_ENV_VARS list
  • PATH rebuilding prevents pollution
  • Safe tilde expansion
  • Proper SSH_AUTH_SOCK preservation

4. Hooks Reorganization ✅

Logical domain-based directory structure.


🐛 Issues Found

HIGH Priority

1. Session Stuck in Busy State on Error (agentStore.ts:470-476)

Problem: If all tabs are deleted concurrently during queue processing, error handler returns unmodified session, leaving it stuck in busy state.

Impact: Broken session state, user cannot interact with session
Fix: Always reset session state to idle in error path


MEDIUM Priority

2. Tab Deletion Race Condition (agentStore.ts:239-242)

Problem: If item.tabId tab is deleted after queueing but before processing, command executes on wrong tab.

Impact: Commands execute on wrong tabs in edge cases
Fix: Log warning and add explicit abort logic

3. Duplicate Stdin Error Handlers (ChildProcessSpawner.ts:348-365, 444-455)

Problem: Both handlers fire for same error with inconsistent logging
Fix: Consolidate into single handler


LOW Priority

4. Multiple setSessions() Calls (useAgentListeners.ts)
Fix: Batch into single functional update for performance

5. Type Casting to any (useAgentListeners.ts:82, 120, 422)
Fix: Import proper types


💡 Recommendations

Must Fix Before Merge

  1. Fix HIGH priority issue - Session stuck in busy state

Should Fix (Post-Merge OK)

  1. Consolidate duplicate stdin error handlers
  2. Add explicit abort logic for tab deletion race
  3. Remove any type casting

Nice to Have

  1. Batch setSessions() calls
  2. Add JSDoc comments
  3. Document ref requirements

🧪 Test Coverage: Excellent ✅

  • 119 new tests across 3 test files
  • Full suite: 18,714 tests passing
  • Proper mocking patterns
  • Edge case coverage

🔒 Security: Well-Designed ✅

  • Correctly strips Electron/IDE context variables
  • Preserves SSH_AUTH_SOCK and credential variables
  • PATH rebuilding prevents pollution
  • Safe tilde expansion
  • No security vulnerabilities found

📝 Code Quality Score: 8.7/10

Category Score
Architecture 9/10
Code Quality 8.5/10
Test Coverage 9/10
Security 9/10
Documentation 8.5/10
Performance 8/10
Overall 8.7/10

✅ Conclusion

This PR represents high-quality architectural work that significantly improves code organization and maintainability.

The single HIGH priority issue should be fixed before merge (straightforward fix). All other issues are minor polish items for post-merge.

Great work on this refactoring! 🎉

…te handlers, type safety

- Always reset session to idle in processQueuedItem error path, even when no active tab exists
- Abort queue processing when target tab was deleted after queueing instead of executing on wrong tab
- Remove duplicate stdin error handler in ChildProcessSpawner (consolidate to single handler)
- Replace `any` type casts in useAgentListeners deps with proper types (Toast, HistoryEntryInput, GlobalStats, Group)
@claude
Copy link
Copy Markdown

claude Bot commented Feb 11, 2026

Code Review: Agent Store Refactoring

This is an excellent architectural refactoring that significantly improves the codebase organization. The PR successfully extracts agent-related state management from App.tsx into dedicated stores and hooks following established patterns. Here's my detailed feedback:


✅ Strengths

1. Architectural Improvements

  • Excellent separation of concerns: Moving 1,850+ lines out of App.tsx into agentStore and useAgentListeners makes the codebase more maintainable
  • Follows existing patterns: The agentStore correctly follows the tabStore pattern (orchestration over sessionStore + IPC, not owning session-level state)
  • Clear documentation: Both new files have excellent header comments explaining responsibilities and design decisions

2. Code Quality

  • Well-structured store: Clear separation between state (availableAgents, agentsDetected) and actions (error recovery, lifecycle management)
  • Type safety: Strong TypeScript types with clear interfaces (AgentStoreState, AgentStoreActions, ProcessQueuedItemDeps)
  • Defensive programming: Good error handling throughout (try-catch blocks, undefined checks, console.error for debugging)

3. Testing

  • Comprehensive coverage: 18,714 tests passing with 82 tests for agentStore alone
  • Edge case coverage: Tests cover error recovery, queue processing, and agent detection caching
  • 29 tests for useAgentListeners: Good coverage for the IPC listener extraction

4. Performance Considerations

  • RAF throttling preserved: The thinkingChunk buffering with requestAnimationFrame is correctly maintained in useAgentListeners
  • Batched updates: The batchedUpdater pattern for high-frequency operations (logs, usage stats) is properly used
  • Dead code removal: 90+ files cleaned up of unused imports and variables

5. Security Improvement (envBuilder.ts)

  • Critical fix: Stripping ELECTRON_RUN_AS_NODE, CLAUDECODE, NODE_ENV, etc. prevents agents from inheriting IDE/Electron context
  • Well-documented: Clear comments explain why each variable is stripped and the problems they cause
  • Follows defensive security: Prevents auth failures and API path misconfigurations

🔍 Issues & Concerns

1. Critical: SSH Remote Execution Awareness

Location: agentStore.ts:processQueuedItem (lines 232-507)

The processQueuedItem function spawns agent processes but does NOT explicitly wrap with SSH when session.sessionSshRemoteConfig?.enabled is true. According to CLAUDE.md:

IMPORTANT: When implementing any feature that spawns agent processes... you MUST support SSH remote execution.

Current approach (line 338):
The code passes sessionSshRemoteConfig to window.maestro.process.spawn, which suggests SSH handling may be done at the IPC layer.

Verification needed:

  • Does window.maestro.process.spawn handle SSH wrapping internally via sessionSshRemoteConfig?
  • Or should this use wrapSpawnWithSsh() from src/main/utils/ssh-spawn-wrapper.ts?

Impact: If SSH is not handled by the spawn API, queue processing will always execute locally even when configured for SSH remote, breaking user expectations.

Recommendation: Verify with the team that sessionSshRemoteConfig parameter is sufficient, or add explicit SSH wrapping.


2. Potential Bug: Tab Deletion Race Condition

Location: agentStore.ts:processQueuedItem (lines 240-260)

Good defensive check for deleted tabs, but if the queue has multiple items and we abort here, those remaining items are silently left in the queue.

Recommendation: Consider clearing the execution queue on abort:

return {
  ...s,
  state: 'idle' as SessionState,
  busySource: undefined,
  thinkingStartTime: undefined,
  executionQueue: [], // Clear queue since target tab was deleted
};

Or add a comment explaining why preserving the queue is intentional.


3. Code Smell: Large Action Function

Location: agentStore.ts:processQueuedItem (lines 232-507)

This single action is ~280 lines with deeply nested logic for tab resolution, agent detection, read-only filtering, message/command handling, template substitution, and error handling.

Impact: Hard to test individual pieces, hard to modify without introducing bugs.

Recommendation: Extract sub-functions (not blocking for this PR):

  • resolveTargetTab(session, item)
  • buildSpawnConfig(session, targetTab, agent, item, deps)
  • handleMessageItem(session, targetTab, item, config)
  • handleCommandItem(session, targetTab, item, config, deps)

4. Minor: Inconsistent Error Logging

Location: agentStore.ts (multiple)

Some functions log errors to console, others throw, some do both. Examples:

  • Line 179: console.error + swallow
  • Line 217: try-catch with empty catch (swallow)
  • Line 467: console.error + add log entry to UI

Recommendation: Standardize error handling strategy per function type:

  • Recovery actions (clearError, retry): Log + swallow ✅
  • Orchestration actions (processQueuedItem): Log + UI feedback ✅
  • Lifecycle actions (killAgent): Swallow (process may not exist) ✅

Current behavior is actually reasonable - this is a minor note for future consistency.


5. Documentation Gap: ProcessQueuedItemDeps

Location: agentStore.ts (lines 111-119)

The dependency object is well-typed but lacks usage documentation. Why are these passed as dependencies instead of being fetched inside the action?

Recommendation: Add a comment explaining:

/**
 * Dependencies passed from App.tsx for processQueuedItem.
 * These come from hooks/state that cannot be imported at module level:
 * - conductorProfile: From useConductor hook (changes at runtime)
 * - custom/speckit/openspec commands: From settings hooks (dynamic config)
 */

🎯 Recommendations

Must Verify Before Merge

  1. ✅ Confirm SSH remote execution support in processQueuedItem (verify sessionSshRemoteConfig parameter is sufficient)

Should Fix Before Merge

  1. ✅ Clear execution queue when aborting on deleted tab, or document why preservation is intentional
  2. ✅ Add JSDoc explaining ProcessQueuedItemDeps design decision

Nice to Have (Follow-up PR)

  1. Extract sub-functions from processQueuedItem for better testability
  2. Consider moving processQueuedItem to a separate orchestration service if it grows larger

📊 Performance & Security

Performance: ✅ Excellent

  • Batched updates preserved for high-frequency operations
  • RAF throttling maintained for thinking chunks
  • Dead code removal reduces bundle size
  • No unnecessary re-renders introduced

Security: ✅ Improved

  • Environment variable stripping prevents auth failures (ELECTRON_RUN_AS_NODE, CLAUDECODE, etc.)
  • Defensive checks prevent undefined access
  • Error recovery actions properly clear sensitive state
  • Read-only mode correctly filters out dangerous flags

🧪 Test Coverage

Current: 18,714 tests passing, 0 TypeScript errors

Test breakdown:

  • agentStore: 82 tests (70 edge cases + 12 for processQueuedItem)
  • useAgentListeners: 29 tests covering all IPC handlers
  • sessionStore: 8 new tests for addLogToTab

Recommendation: Add integration test for SSH remote execution path once behavior is verified.


✅ Overall Assessment

This is a high-quality refactoring that significantly improves the codebase architecture. The extraction follows established patterns, maintains excellent test coverage, and includes important security fixes.

Key achievements:

  • ✅ Reduced App.tsx complexity by ~1,850 lines
  • ✅ Introduced clear separation of concerns (agentStore for orchestration, useAgentListeners for IPC)
  • ✅ Maintained 100% test pass rate with comprehensive new tests
  • ✅ Fixed environment variable leakage security issue
  • ✅ Followed existing codebase patterns (tabStore model)
  • ✅ Excellent documentation in code comments

Areas for attention:

  • ⚠️ SSH remote execution needs verification (likely fine, but confirm with team)
  • ⚠️ Minor: Queue cleanup on tab deletion race condition
  • ⚠️ Minor: Documentation for ProcessQueuedItemDeps design

Approval Status: ✅ Approve with minor comments

The concerns raised are either verification items (SSH handling) or minor improvements that can be addressed in follow-up PRs. The comprehensive testing (18,714 tests passing) gives confidence that the refactoring is solid.

Excellent work! This refactoring will make future feature development much easier and the codebase significantly more maintainable. 🎉


📝 Additional Notes

For future refactoring PRs:

  1. This PR sets a great example of how to extract large components - clear commit history, comprehensive tests, and maintained functionality
  2. The pattern of extracting IPC listeners into dedicated hooks (useAgentListeners) could be applied to other large useEffects in the codebase
  3. The agentStore model (orchestration over domain stores + IPC) is a solid pattern worth documenting for other contributors

Specific files to watch:

  • src/renderer/stores/agentStore.ts:processQueuedItem - if this grows beyond 300 lines, consider splitting
  • src/renderer/hooks/agent/useAgentListeners.ts - already large at 1,582 lines, but that's the nature of listener registration; extraction to multiple hooks might fragment the logic

@reachrazamair reachrazamair merged commit 473cff1 into main Feb 11, 2026
2 checks passed
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