Skip to content

Add GitHub Copilot CLI support#566

Open
nolanmclark wants to merge 13 commits intoRunMaestro:mainfrom
nolanmclark:feat/github-copilot-cli-support
Open

Add GitHub Copilot CLI support#566
nolanmclark wants to merge 13 commits intoRunMaestro:mainfrom
nolanmclark:feat/github-copilot-cli-support

Conversation

@nolanmclark
Copy link

@nolanmclark nolanmclark commented Mar 13, 2026

Summary

This PR adds GitHub Copilot CLI support to Maestro as a new beta agent. It wires Copilot into Maestro’s agent system end-to-end, including agent detection, launch definitions, capability metadata, JSON output parsing, session resume support, and session history loading from ~/.copilot/session-state. It also updates onboarding and UI surfaces so Copilot can be selected and managed alongside the existing agents.

What changed:

  • added copilot as a supported Maestro agent
  • added Copilot agent definitions, capabilities, metadata, icon, and default context window
  • added a dedicated Copilot output parser for --output-format json
  • added Copilot session storage support backed by ~/.copilot/session-state
  • added Copilot-specific error pattern handling
  • updated process handling to support Copilot’s streamed/concatenated JSON events and session ID extraction
  • added path probing for Copilot installs on macOS and Windows
  • updated onboarding / new agent selection UI to include GitHub Copilot
  • updated docs and agent reference tables

Notes:

  • this integration is marked Beta
  • supports interactive use, batch prompts, JSON output, and session resume
  • session history works for both local and SSH-backed environments
  • Copilot-specific limitations are documented in the agent capability/docs updates

Summary by CodeRabbit

  • New Features

    • GitHub Copilot CLI added (Beta): UI tile, icon, display name, resume/JSON/batch/streaming support, model selection, and session browsing/search in the app.
  • Documentation

    • README, installation, contributing, and release notes updated with Copilot CLI requirements and usage guidance.
  • Tests

    • Expanded test coverage for Copilot parsing, session storage, spawning, streaming, CLI args, and related behaviors.

@coderabbitai
Copy link

coderabbitai bot commented Mar 13, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 5cfbb0bf-39e5-4ded-a148-210483a8d751

📥 Commits

Reviewing files that changed from the base of the PR and between e9f7b61 and 763299e.

📒 Files selected for processing (1)
  • src/main/storage/copilot-session-storage.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/main/storage/copilot-session-storage.ts

📝 Walkthrough

Walkthrough

Adds GitHub Copilot CLI support: agent metadata, capabilities, path probing, per-process parser factory, Copilot JSON parser and error patterns, Copilot session storage, PTY/child spawner and stdout buffering/resync changes, remote-fs escaping, renderer UI entries, tests, and docs updates.

Changes

Cohort / File(s) Summary
Docs & Guides
README.md, CONTRIBUTING.md, CLAUDE-AGENTS.md, CLAUDE.md, docs/installation.md, docs/releases.md
Add Copilot to supported-agents docs/tables and requirements; formatting/reflow edits.
Agent Definitions & Metadata
src/main/agents/definitions.ts, src/main/agents/capabilities.ts, src/shared/agentIds.ts, src/shared/agentMetadata.ts, src/shared/agentConstants.ts
Introduce copilot: agent definition (binary/args builders), capability flags, display name, beta flag, and default context window.
Path Probing
src/main/agents/path-prober.ts
Add known Copilot install paths for Windows and Unix-like systems.
Parsers & Error Patterns
src/main/parsers/copilot-output-parser.ts, src/main/parsers/error-patterns.ts, src/main/parsers/index.ts, src/main/parsers/agent-output-parser.ts, src/main/parsers/parser-factory.ts
New CopilotOutputParser and Copilot error patterns; add toolCallId to ParsedEvent; introduce per-process createOutputParser, register/export Copilot parser.
Stdout Handling & Process Types
src/main/process-manager/handlers/StdoutHandler.ts, src/main/process-manager/types.ts
Copilot JSON buffering/splitting, concatenated-JSON handling, oversized-buffer recovery, session-id emission helper, deduplication; add ManagedProcess flags (jsonBufferCorrupted, emittedToolCallIds).
Spawners / PTY
src/main/process-manager/spawners/ChildProcessSpawner.ts, src/main/process-manager/spawners/PtySpawner.ts
Instantiate per-process parser via factory; broaden resume/stream-json flag detection; Windows-shell wrapping for PTY spawns when required.
Session Storage
src/main/storage/copilot-session-storage.ts, src/main/storage/index.ts
Add CopilotSessionStorage (local & SSH-aware discovery, events parsing, pagination/search, directory sizing); register storage in initialization.
Remote FS Utilities
src/main/utils/remote-fs.ts
Add shellEscapeRemotePath and apply across remote-fs functions to correctly handle ~ and $HOME.
Agent Args Helpers
src/main/utils/agent-args.ts
Add parseCustomArgs and hasJsonOutputFlag; gate jsonOutputArgs injection to interactive prompt flows; export applyAgentConfigOverrides and getContextWindowValue.
Renderer / UI
src/renderer/components/..., src/renderer/constants/agentIcons.ts, src/renderer/hooks/agent/useAgentListeners.ts
Add Copilot to supported lists, wizard tile/icon, dashboard display-name mappings; improved agent-error-log cleanup when data resumes.
Tests
src/__tests__/...
Extensive test additions/updates: Copilot parser, StdoutHandler buffering/resync, Pty/Child spawners, CopilotSessionStorage, agent detection/definitions, agent-args, remote-fs expansion, and UI listener recovery.
Small UI/UX mappings
src/renderer/components/NewInstanceModal.tsx, src/renderer/components/Wizard/screens/AgentSelectionScreen.tsx, src/renderer/components/UsageDashboard/*
Display-name, icon and tile additions for copilot; dynamic grid sizing for wizard tiles.

Sequence Diagram

sequenceDiagram
    participant User as User/CLI
    participant Spawner as ChildProcessSpawner
    participant PTY as PtySpawner
    participant Copilot as Copilot CLI
    participant Stdout as StdoutHandler
    participant Parser as CopilotOutputParser
    participant Storage as CopilotSessionStorage
    participant UI as Renderer

    User->>Spawner: invoke `copilot` with flags (e.g. --output-format json, -p)
    Spawner->>Spawner: detect stream-json/resume and create per-process parser
    Spawner->>PTY: spawn process (requiresPty true) / wrap in shell on Windows
    PTY->>Copilot: execute Copilot CLI
    Copilot-->>Stdout: stream concatenated JSON / mixed text
    Stdout->>Stdout: buffer, split JSON, resync on corruption
    Stdout->>Parser: deliver discrete JSON objects
    Parser->>Stdout: emit text_delta, tool_use, result, error events
    Parser->>Storage: persist session events (on result/sessionId)
    UI->>Storage: list/search sessions
    UI->>Spawner: request resume -> Spawner invokes Copilot with --resume=<id>
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Add GitHub Copilot CLI support' accurately and clearly summarizes the main change: adding a new GitHub Copilot CLI agent to the Maestro system as a beta integration.
Docstring Coverage ✅ Passed Docstring coverage is 90.20% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
📝 Coding Plan
  • Generate coding plan for human review comments

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.

@coderabbitai
Copy link

coderabbitai bot commented Mar 13, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@nolanmclark nolanmclark marked this pull request as ready for review March 13, 2026 02:06
@coderabbitai
Copy link

coderabbitai bot commented Mar 13, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link

@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: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
README.md (1)

105-110: ⚠️ Potential issue | 🟡 Minor

Supported-agent statements are now inconsistent in this README.

After adding Copilot in Line 109, the earlier support lists at Line 15 and Line 86 still omit it. That creates contradictory guidance for new users.

📝 Suggested doc sync
-Run multiple agents in parallel with a Linear/Superhuman-level responsive interface. Currently supporting **Claude Code**, **OpenAI Codex**, **OpenCode**, and **Factory Droid** with plans for additional agentic coding tools (Gemini CLI, Qwen3 Coder) based on user demand.
+Run multiple agents in parallel with a Linear/Superhuman-level responsive interface. Currently supporting **Claude Code**, **OpenAI Codex**, **OpenCode**, **Factory Droid**, and **GitHub Copilot CLI (Beta)**, with plans for additional agentic coding tools (Gemini CLI, Qwen3 Coder) based on user demand.

-> **Note**: Maestro supports Claude Code, OpenAI Codex, OpenCode, and Factory Droid. Support for additional agents (Gemini CLI, Qwen3 Coder) may be added in future releases based on community demand.
+> **Note**: Maestro supports Claude Code, OpenAI Codex, OpenCode, Factory Droid, and GitHub Copilot CLI (Beta). Support for additional agents (Gemini CLI, Qwen3 Coder) may be added in future releases based on community demand.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@README.md` around lines 105 - 110, The README has inconsistent "supported
agent" lists—update all occurrences of the supported AI coding agents so they
match and include "GitHub Copilot CLI" alongside "Claude Code", "OpenAI Codex",
and "OpenCode"; search for the agent bullet lists (the "At least one supported
AI coding agent installed and authenticated" section and any other
prerequisite/overview lists) and add the "GitHub Copilot CLI" entry with the
same link text/format as used in the newly added item to keep wording and links
consistent across the README.
src/main/parsers/error-patterns.ts (1)

858-958: ⚠️ Potential issue | 🟠 Major

Mirror Copilot into SSH_ERROR_PATTERNS too.

Registering Copilot here leaves the SSH-specific fallback table above unchanged. On SSH-backed Copilot sessions, bash: copilot: command not found and /path/copilot: No such file or directory will miss the friendly install diagnostic and collapse to the generic crash path.

Suggested follow-up in SSH_ERROR_PATTERNS.agent_crashed
+		{
+			pattern:
+				/bash:.*copilot.*command not found|sh:.*copilot.*command not found|zsh:.*command not found:.*copilot/i,
+			message: 'GitHub Copilot command not found. Ensure Copilot CLI is installed.',
+			recoverable: false,
+		},
 		{
 			// Agent binary missing (executable file not found at path)
 			// More specific pattern: requires path-like structure before the binary name
 			// Matches: "/usr/local/bin/claude: No such file or directory"
 			// Does NOT match: "claude: error: File 'foo.txt': No such file or directory" (normal file errors)
-			pattern: /\/[^\s:]*\/(claude|opencode|codex):\s*No such file or directory/i,
+			pattern: /\/[^\s:]*\/(claude|opencode|codex|copilot):\s*No such file or directory/i,
 			message: 'Agent binary not found at the specified path. Ensure the agent is installed.',
 			recoverable: false,
 		},
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/parsers/error-patterns.ts` around lines 858 - 958, The
Copilot-specific error patterns (COPILOT_ERROR_PATTERNS) were registered only in
patternRegistry and not mirrored into SSH_ERROR_PATTERNS, so SSH-backed Copilot
sessions miss Copilot-specific install/crash diagnostics; update
SSH_ERROR_PATTERNS.agent_crashed to include the same Copilot entries (or
merge/copied relevant patterns from COPILOT_ERROR_PATTERNS.agent_crashed such as
the "no prompt provided.*interactive terminal" and install-related patterns)
and/or add a registration step that maps 'copilot' patterns into the
SSH_ERROR_PATTERNS set, ensuring patternRegistry and SSH_ERROR_PATTERNS both
include the Copilot patterns so messages like "bash: copilot: command not found"
match the friendly install diagnostic.
🧹 Nitpick comments (6)
src/__tests__/main/utils/remote-fs.test.ts (1)

171-183: Good test coverage for the home-relative path expansion feature.

The test correctly verifies the primary use case: ~/... paths are expanded to "$HOME/..." in SSH commands. This aligns well with the Copilot session storage path handling.

Consider adding additional test cases for comprehensive coverage of shellEscapeRemotePath:

🧪 Optional: additional edge case tests
it('expands bare ~ to $HOME', async () => {
	const deps = createMockDeps({
		stdout: 'file.txt\n',
		stderr: '',
		exitCode: 0,
	});

	await readDirRemote('~', baseConfig, deps);

	const call = (deps.execSsh as any).mock.calls[0][1];
	const remoteCommand = call[call.length - 1];
	expect(remoteCommand).toContain('"$HOME"');
});

it('escapes special characters in home-relative paths', async () => {
	const deps = createMockDeps({
		stdout: 'file.txt\n',
		stderr: '',
		exitCode: 0,
	});

	await readDirRemote('~/path$with"special', baseConfig, deps);

	const call = (deps.execSsh as any).mock.calls[0][1];
	const remoteCommand = call[call.length - 1];
	// Should contain escaped special chars within double quotes
	expect(remoteCommand).toMatch(/"\$HOME\/path.*special"/);
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/__tests__/main/utils/remote-fs.test.ts` around lines 171 - 183, The test
currently covers expansion of home-relative paths but we should add two more
unit tests for shellEscapeRemotePath behavior: one to verify a bare "~" is
expanded to "$HOME" when calling readDirRemote, and another to ensure special
characters inside a home-relative path (e.g., ~/path$with"special) are safely
escaped and still wrapped in double quotes in the SSH command; add tests in
src/__tests__/main/utils/remote-fs.test.ts that call readDirRemote with '~' and
with a path containing special chars, then inspect the mocked deps.execSsh call
(as done in the existing test) to assert the remote command contains '"$HOME"'
for the bare-tilde case and a quoted/escaped "$HOME/..." string (use a regex
match) for the special-character case, using the same createMockDeps fixture and
baseConfig so they follow existing test patterns.
src/renderer/components/UsageDashboard/SessionStats.tsx (1)

88-101: Same consolidation opportunity as AgentEfficiencyChart.tsx.

This formatAgentName function duplicates the shared AGENT_DISPLAY_NAMES mapping. Consider importing getAgentDisplayName from src/shared/agentMetadata.ts to keep agent display names in a single location.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/components/UsageDashboard/SessionStats.tsx` around lines 88 -
101, Replace the local formatAgentName mapping with the shared lookup: import
and call getAgentDisplayName (from the shared agent metadata module) inside
formatAgentName (or replace formatAgentName with a thin wrapper around
getAgentDisplayName), remove the duplicated names object, and keep the same
fallback behavior (return the original toolType if getAgentDisplayName returns
undefined). Update any references to formatAgentName to continue working
unchanged.
src/__tests__/main/process-manager/spawners/PtySpawner.test.ts (1)

57-89: Good test coverage for Windows shell wrapping.

The test correctly validates that .cmd files are wrapped in cmd.exe with proper escaping. Consider adding a complementary test case verifying that non-.cmd executables (e.g., direct binary paths) are NOT wrapped in cmd.exe to ensure the needsWindowsShell guard works both ways.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/__tests__/main/process-manager/spawners/PtySpawner.test.ts` around lines
57 - 89, Add a complementary unit test to PtySpawner.test.ts that verifies
non-.cmd executables are NOT wrapped by cmd.exe: create a similar test using
PtySpawner.spawn with a command like 'C:\\Program Files\\git\\bin\\git.exe' (or
another .exe path), requiresPty: true, and assert mockPtySpawn was called with
the original executable path and the original args (not
'C:\\Windows\\System32\\cmd.exe' and not the combined string), and that the cwd
is preserved; place the test alongside the existing "wraps Windows .cmd agents"
test and reference PtySpawner.spawn and mockPtySpawn in the assertions.
src/renderer/components/UsageDashboard/AgentEfficiencyChart.tsx (1)

52-65: Consider using shared getAgentDisplayName from agentMetadata.ts.

This local formatAgentName function duplicates the AGENT_DISPLAY_NAMES mapping from src/shared/agentMetadata.ts. The same pattern exists in SessionStats.tsx. Consider importing getAgentDisplayName from the shared module to maintain a single source of truth for agent display names.

♻️ Suggested refactor
+import { getAgentDisplayName } from '../../../shared/agentMetadata';
 
-/**
- * Format agent type display name
- */
-function formatAgentName(agent: string): string {
-	const names: Record<string, string> = {
-		'claude-code': 'Claude Code',
-		opencode: 'OpenCode',
-		'openai-codex': 'OpenAI Codex',
-		codex: 'Codex',
-		'gemini-cli': 'Gemini CLI',
-		'qwen3-coder': 'Qwen3 Coder',
-		'factory-droid': 'Factory Droid',
-		copilot: 'GitHub Copilot',
-		terminal: 'Terminal',
-	};
-	return names[agent] || agent;
-}
+// Use getAgentDisplayName directly from shared module

Then replace formatAgentName(agent.agent) calls with getAgentDisplayName(agent.agent).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/components/UsageDashboard/AgentEfficiencyChart.tsx` around lines
52 - 65, formatAgentName duplicates the AGENT_DISPLAY_NAMES mapping; replace
this local mapper with the shared function by importing getAgentDisplayName from
src/shared/agentMetadata.ts and use getAgentDisplayName(agent) wherever
formatAgentName is called (and likewise update SessionStats.tsx uses). Remove
the local formatAgentName function and its names map, update imports to include
getAgentDisplayName, and ensure calls like formatAgentName(agent.agent) become
getAgentDisplayName(agent.agent) so the app uses a single source of truth.
src/main/agents/path-prober.ts (1)

432-444: Remove duplicated Unix probe path for Copilot.

homebrew('copilot') already includes /usr/local/bin/copilot, so the explicit entry is redundant.

♻️ Proposed cleanup
 		copilot: [
 			// Homebrew installation (primary method on macOS)
 			...homebrew('copilot'),
-			// GitHub CLI installation
-			'/usr/local/bin/copilot',
 			path.join(home, '.local', 'bin', 'copilot'),
 			// npm global
 			...npmGlobal('copilot'),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/agents/path-prober.ts` around lines 432 - 444, The copilot probe
array contains a redundant explicit path '/usr/local/bin/copilot' because
homebrew('copilot') already expands to that; remove the literal
'/usr/local/bin/copilot' entry from the copilot array and leave the spread of
homebrew('copilot'), npmGlobal('copilot'), nodeVersionManagers('copilot'), and
other entries (path.join(home, '.local', 'bin', 'copilot') and path.join(home,
'bin', 'copilot')) unchanged so probes remain comprehensive without duplication.
src/main/storage/copilot-session-storage.ts (1)

249-259: Consider consistent parameter passing for clarity.

The path helpers work correctly, but there's an inconsistency: the SSH branch explicitly passes sshConfig to getSessionDir while the local branch omits it. Since getSessionDir handles both cases via ternary, this is functionally correct but could be clearer.

🔧 Optional: Consistent parameter passing
 private getWorkspacePath(sessionId: string, sshConfig?: SshRemoteConfig): string {
 	return sshConfig
 		? path.posix.join(this.getSessionDir(sessionId, sshConfig), 'workspace.yaml')
-		: path.join(this.getSessionDir(sessionId), 'workspace.yaml');
+		: path.join(this.getSessionDir(sessionId, sshConfig), 'workspace.yaml');
 }

 private getEventsPath(sessionId: string, sshConfig?: SshRemoteConfig): string {
 	return sshConfig
 		? path.posix.join(this.getSessionDir(sessionId, sshConfig), 'events.jsonl')
-		: path.join(this.getSessionDir(sessionId), 'events.jsonl');
+		: path.join(this.getSessionDir(sessionId, sshConfig), 'events.jsonl');
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/storage/copilot-session-storage.ts` around lines 249 - 259, Both
helpers getWorkspacePath and getEventsPath should call getSessionDir with the
same arguments in both branches for clarity; change the local-branch calls to
pass the optional sshConfig as well (i.e., call getSessionDir(sessionId,
sshConfig) in both the sshConfig ? and : branches) so getSessionDir usage is
consistent across the functions (references: getWorkspacePath, getEventsPath,
getSessionDir, SshRemoteConfig).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/releases.md`:
- Line 20: Several section headings in docs/releases.md (for example the "Major
0.15.x Additions" heading) were promoted to top-level (#) and flattened the
document hierarchy; revert these headings to the proper subordinate level (use
##, ###, etc. to match surrounding headings) so intra-section headings align
with the document's existing structure. Locate each offending heading text
(e.g., "Major 0.15.x Additions" and the other headings listed in the review) and
replace the leading single '#' with the correct number of '#' characters to
match neighboring sections and restore the navigation/TOC structure.
- Line 101: Fix the user-facing typos/branding in the sentence starting "🗄️
Document Graphs. Launch from file preview or from the FIle tree panel…" by
normalizing capitalization and tightening wording — e.g., change to "Document
graphs: launch from the file preview or file tree panel and explore
relationships between Markdown documents that link to other documents and
external URLs." Replace the incorrect "FIle" casing and ensure consistent
lowercase/branding for "file preview" and "file tree panel", and apply the same
corrections to the other identical occurrences of this phrasing elsewhere in the
document.

In `@src/main/parsers/copilot-output-parser.ts`:
- Line 76: The parser currently keeps mutable state in the singleton field
"toolNames" on CopilotOutputParser, causing cross-session collisions; move this
state into the per-process session (e.g., ManagedProcess) and stop storing it on
the parser singleton. Remove the private toolNames field from
CopilotOutputParser and instead add a session-scoped Map on ManagedProcess (or
accept a session-scoped map/object), update the parser API (methods that
reference toolNames) to take a session or toolNames map parameter (or add a
setSessionToolNames(sessionId|map) method), and change usages in
ChildProcessSpawner.getOutputParser('copilot') and any caller sites to pass the
ManagedProcess/session instance or its tool map so each Copilot run uses its own
toolNames map keyed by toolCallId. Ensure no other mutable parser state remains
shared across processes.
- Around line 285-317: The current logic in the error parsing block (inside
copilot-output-parser.ts) fabricates an errorText when only an exitCode exists,
causing detectErrorFromExit() in StdoutHandler to be bypassed; change the
behavior in the function using extractErrorText, getErrorPatterns, and
matchErrorPattern so that if no actual textual error is present (i.e.,
extractErrorText(msg.error) and extractErrorText(msg.data?.error) both return
falsy) you return null instead of synthesizing `Copilot exited with code
${msg.exitCode}`, thereby preserving the !errorEmitted guard and allowing
detectErrorFromExit() to run its stderr/stdout-based classification
(auth_expired, session_expired, rate_limited, etc.).

In `@src/main/storage/copilot-session-storage.ts`:
- Around line 384-386: The code falls back to new Date().toISOString() for
timestamp which can falsely set creation to "now"; change the fallback so
timestamp = metadata.created_at || metadata.updated_at || workspace file mtime
(read via fs.statSync/stat and use mtime.toISOString()) or a neutral default
(e.g., null or epoch) instead of current time; update the related
variables/logic around timestamp and modifiedAt in copilot-session-storage.ts
(refer to projectRoot, timestamp, modifiedAt, metadata, projectPath) so
created_at prefers existing updated_at or file mtime before using any
non-deterministic current time.

In `@src/main/utils/agent-args.ts`:
- Around line 69-73: The overlap check for jsonOutputArgs in the block that
references agent.jsonOutputArgs, options.prompt and finalArgs.some(...) is too
permissive because it treats tokens independently and can falsely suppress
multi-token flags; change the check to detect whole-sequence occurrences instead
of any-token matches (e.g., implement or call a containsSequence(sequence,
array) helper that checks for contiguous subarray equality between
agent.jsonOutputArgs and finalArgs) and use that in place of the current
finalArgs.some(...) test so multi-token flags like ["--output-format","json"]
are only considered duplicates when the exact sequence exists in finalArgs.

---

Outside diff comments:
In `@README.md`:
- Around line 105-110: The README has inconsistent "supported agent"
lists—update all occurrences of the supported AI coding agents so they match and
include "GitHub Copilot CLI" alongside "Claude Code", "OpenAI Codex", and
"OpenCode"; search for the agent bullet lists (the "At least one supported AI
coding agent installed and authenticated" section and any other
prerequisite/overview lists) and add the "GitHub Copilot CLI" entry with the
same link text/format as used in the newly added item to keep wording and links
consistent across the README.

In `@src/main/parsers/error-patterns.ts`:
- Around line 858-958: The Copilot-specific error patterns
(COPILOT_ERROR_PATTERNS) were registered only in patternRegistry and not
mirrored into SSH_ERROR_PATTERNS, so SSH-backed Copilot sessions miss
Copilot-specific install/crash diagnostics; update
SSH_ERROR_PATTERNS.agent_crashed to include the same Copilot entries (or
merge/copied relevant patterns from COPILOT_ERROR_PATTERNS.agent_crashed such as
the "no prompt provided.*interactive terminal" and install-related patterns)
and/or add a registration step that maps 'copilot' patterns into the
SSH_ERROR_PATTERNS set, ensuring patternRegistry and SSH_ERROR_PATTERNS both
include the Copilot patterns so messages like "bash: copilot: command not found"
match the friendly install diagnostic.

---

Nitpick comments:
In `@src/__tests__/main/process-manager/spawners/PtySpawner.test.ts`:
- Around line 57-89: Add a complementary unit test to PtySpawner.test.ts that
verifies non-.cmd executables are NOT wrapped by cmd.exe: create a similar test
using PtySpawner.spawn with a command like 'C:\\Program
Files\\git\\bin\\git.exe' (or another .exe path), requiresPty: true, and assert
mockPtySpawn was called with the original executable path and the original args
(not 'C:\\Windows\\System32\\cmd.exe' and not the combined string), and that the
cwd is preserved; place the test alongside the existing "wraps Windows .cmd
agents" test and reference PtySpawner.spawn and mockPtySpawn in the assertions.

In `@src/__tests__/main/utils/remote-fs.test.ts`:
- Around line 171-183: The test currently covers expansion of home-relative
paths but we should add two more unit tests for shellEscapeRemotePath behavior:
one to verify a bare "~" is expanded to "$HOME" when calling readDirRemote, and
another to ensure special characters inside a home-relative path (e.g.,
~/path$with"special) are safely escaped and still wrapped in double quotes in
the SSH command; add tests in src/__tests__/main/utils/remote-fs.test.ts that
call readDirRemote with '~' and with a path containing special chars, then
inspect the mocked deps.execSsh call (as done in the existing test) to assert
the remote command contains '"$HOME"' for the bare-tilde case and a
quoted/escaped "$HOME/..." string (use a regex match) for the special-character
case, using the same createMockDeps fixture and baseConfig so they follow
existing test patterns.

In `@src/main/agents/path-prober.ts`:
- Around line 432-444: The copilot probe array contains a redundant explicit
path '/usr/local/bin/copilot' because homebrew('copilot') already expands to
that; remove the literal '/usr/local/bin/copilot' entry from the copilot array
and leave the spread of homebrew('copilot'), npmGlobal('copilot'),
nodeVersionManagers('copilot'), and other entries (path.join(home, '.local',
'bin', 'copilot') and path.join(home, 'bin', 'copilot')) unchanged so probes
remain comprehensive without duplication.

In `@src/main/storage/copilot-session-storage.ts`:
- Around line 249-259: Both helpers getWorkspacePath and getEventsPath should
call getSessionDir with the same arguments in both branches for clarity; change
the local-branch calls to pass the optional sshConfig as well (i.e., call
getSessionDir(sessionId, sshConfig) in both the sshConfig ? and : branches) so
getSessionDir usage is consistent across the functions (references:
getWorkspacePath, getEventsPath, getSessionDir, SshRemoteConfig).

In `@src/renderer/components/UsageDashboard/AgentEfficiencyChart.tsx`:
- Around line 52-65: formatAgentName duplicates the AGENT_DISPLAY_NAMES mapping;
replace this local mapper with the shared function by importing
getAgentDisplayName from src/shared/agentMetadata.ts and use
getAgentDisplayName(agent) wherever formatAgentName is called (and likewise
update SessionStats.tsx uses). Remove the local formatAgentName function and its
names map, update imports to include getAgentDisplayName, and ensure calls like
formatAgentName(agent.agent) become getAgentDisplayName(agent.agent) so the app
uses a single source of truth.

In `@src/renderer/components/UsageDashboard/SessionStats.tsx`:
- Around line 88-101: Replace the local formatAgentName mapping with the shared
lookup: import and call getAgentDisplayName (from the shared agent metadata
module) inside formatAgentName (or replace formatAgentName with a thin wrapper
around getAgentDisplayName), remove the duplicated names object, and keep the
same fallback behavior (return the original toolType if getAgentDisplayName
returns undefined). Update any references to formatAgentName to continue working
unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d2c06fcc-e9fc-417c-8839-1610ea0af7c9

📥 Commits

Reviewing files that changed from the base of the PR and between 9ff6839 and f405f9d.

📒 Files selected for processing (44)
  • CLAUDE-AGENTS.md
  • CLAUDE.md
  • CONTRIBUTING.md
  • README.md
  • docs/installation.md
  • docs/releases.md
  • src/__tests__/main/agents/definitions.test.ts
  • src/__tests__/main/agents/detector.test.ts
  • src/__tests__/main/agents/session-storage.test.ts
  • src/__tests__/main/parsers/copilot-output-parser.test.ts
  • src/__tests__/main/parsers/index.test.ts
  • src/__tests__/main/process-manager/handlers/StdoutHandler.test.ts
  • src/__tests__/main/process-manager/spawners/ChildProcessSpawner.test.ts
  • src/__tests__/main/process-manager/spawners/PtySpawner.test.ts
  • src/__tests__/main/utils/agent-args.test.ts
  • src/__tests__/main/utils/remote-fs.test.ts
  • src/__tests__/renderer/hooks/useAgentListeners.test.ts
  • src/__tests__/shared/agentIds.test.ts
  • src/__tests__/shared/agentMetadata.test.ts
  • src/main/agents/capabilities.ts
  • src/main/agents/definitions.ts
  • src/main/agents/path-prober.ts
  • src/main/parsers/agent-output-parser.ts
  • src/main/parsers/copilot-output-parser.ts
  • src/main/parsers/error-patterns.ts
  • src/main/parsers/index.ts
  • src/main/process-manager/handlers/StdoutHandler.ts
  • src/main/process-manager/spawners/ChildProcessSpawner.ts
  • src/main/process-manager/spawners/PtySpawner.ts
  • src/main/process-manager/types.ts
  • src/main/storage/copilot-session-storage.ts
  • src/main/storage/index.ts
  • src/main/utils/agent-args.ts
  • src/main/utils/remote-fs.ts
  • src/renderer/components/NewInstanceModal.tsx
  • src/renderer/components/UsageDashboard/AgentEfficiencyChart.tsx
  • src/renderer/components/UsageDashboard/LongestAutoRunsTable.tsx
  • src/renderer/components/UsageDashboard/SessionStats.tsx
  • src/renderer/components/Wizard/screens/AgentSelectionScreen.tsx
  • src/renderer/constants/agentIcons.ts
  • src/renderer/hooks/agent/useAgentListeners.ts
  • src/shared/agentConstants.ts
  • src/shared/agentIds.ts
  • src/shared/agentMetadata.ts

@greptile-apps
Copy link

greptile-apps bot commented Mar 13, 2026

Greptile Summary

This PR wires GitHub Copilot CLI into Maestro end-to-end: agent definition, PTY-based spawning, a dedicated output parser for Copilot's concatenated JSON event stream, per-process parser instantiation (preventing cross-session toolNames state leakage), session storage backed by ~/.copilot/session-state, remote-fs ~/ path expansion for SSH support, and full UI registration. Previous review threads (empty final_answer result detection, Windows path correction, normalizeYamlScalar unescaping, jsonOutputArgs interactive-mode guard) have all been addressed.

Key findings from this pass:

  • extractConcatenatedJsonObjects does not handle \uXXXX escape sequences (StdoutHandler.ts). The one-character isEscaped skip correctly covers \", \\, \n etc., but Unicode escapes like \u0022 leave three unguarded characters — if the final hex digit happens to be " or }, string/depth tracking goes out of sync. Impact is low with today's Copilot output (which uses \" rather than \u0022), but the tokenizer is not spec-compliant.
  • batchModeArgs unconditionally includes --allow-all-tools, meaning every batch Copilot session auto-approves tool calls whether or not the user enabled YOLO mode. This is a deliberate workaround for Copilot's lack of an interactive approval prompt in non-TTY batch mode, but it creates a silent security-posture difference from all other supported agents and makes yoloModeArgs redundant (resulting in a duplicate --allow-all-tools flag for YOLO + batch runs).
  • The Copilot icon (✈️) in agentIcons.ts is unrelated to GitHub Copilot's branding and appears to be a placeholder.

Confidence Score: 4/5

  • Safe to merge for beta use; two minor correctness issues should be tracked but neither blocks real-world Copilot sessions under typical output.
  • The integration is comprehensive and well-tested (45 files, ~300 new test assertions covering parsing, session storage, spawning, streaming, and args). All threads from the first review pass are addressed. The two remaining findings — a \uXXXX gap in the custom JSON tokenizer (low practical impact) and auto-approved tools in all batch runs (by design but undocumented) — do not cause data loss or crashes under current Copilot CLI output. The icon placeholder is purely cosmetic.
  • src/main/process-manager/handlers/StdoutHandler.ts (JSON tokenizer edge case) and src/main/agents/definitions.ts (implicit tool auto-approval in batch mode).

Important Files Changed

Filename Overview
src/main/parsers/copilot-output-parser.ts New Copilot JSON parser — correctly maps all event types, tracks tool names across async start/complete events, fixes the previously-reported empty final_answer gap, and delegates per-process state isolation to the new factory.
src/main/process-manager/handlers/StdoutHandler.ts Adds a character-level JSON object splitter for Copilot's concatenated-JSON stdout stream. The splitter's escape-sequence handling is incomplete: it advances only one character after a backslash, so \uXXXX sequences whose last hex digit is a quote character would prematurely terminate string-tracking and corrupt brace-depth counting.
src/main/storage/copilot-session-storage.ts Well-structured session storage backed by ~/.copilot/session-state. YAML parser, path normalizer, and remote-fs integration all look solid. normalizeYamlScalar now handles " and \ unescaping following the previous review thread.
src/main/agents/definitions.ts Copilot definition correctly wires PTY requirement, batch/interactive modes, resume args, and config options. batchModeArgs includes --allow-all-tools, meaning all batch runs (not just YOLO mode) auto-approve every tool call — a security-posture difference from other agents.
src/main/parsers/parser-factory.ts New factory that creates per-process parser instances, correctly preventing shared mutable state (toolNames map) across concurrent Copilot sessions.
src/main/utils/remote-fs.ts New shellEscapeRemotePath function correctly expands ~/... and $HOME/... paths into "$HOME/..." for remote SSH execution, unblocking Copilot's ~/.copilot session-state directory over SSH.
src/main/process-manager/spawners/PtySpawner.ts Adds Windows .cmd wrapping via cmd.exe /d /s /c for PTY-launched agents — required because node-pty cannot directly execute .cmd batch files on Windows.
src/renderer/constants/agentIcons.ts Copilot icon assigned as ✈️ (airplane), which does not match GitHub Copilot branding and appears to be a placeholder.

Sequence Diagram

sequenceDiagram
    participant U as User / Maestro UI
    participant SP as ChildProcessSpawner / PtySpawner
    participant CP as copilot CLI (PTY)
    participant SH as StdoutHandler
    participant PA as CopilotOutputParser
    participant SS as CopilotSessionStorage

    U->>SP: spawn(config { requiresPty, batchModeArgs, jsonOutputArgs })
    SP->>SP: createOutputParser('copilot') → fresh instance
    SP->>CP: PTY launch: copilot -p "…" --output-format json --allow-all-tools --silent

    loop Streamed JSON events (concatenated, no newlines)
        CP-->>SH: stdout chunk (1..N JSON objects)
        SH->>SH: extractConcatenatedJsonObjects(buffer)
        loop each complete JSON object
            SH->>PA: parseJsonObject(parsed)
            PA-->>SH: ParsedEvent (text / tool_use / result / system)
            SH->>SH: emitSessionIdIfNeeded (from result.sessionId)
            SH->>U: emit tool-execution / data / session-id events
        end
        SH->>SH: buffer remainder for next chunk
    end

    CP-->>SH: assistant.message { phase:"final_answer" }
    PA-->>SH: ParsedEvent { type:"result", text, raw.data.phase:"final_answer" }
    SH->>SH: isResultMessage → true → resultEmitted=true
    SH->>U: emit data(finalText)

    CP-->>SH: { type:"result", sessionId, exitCode:0 }
    SH->>SH: extractCopilotSessionId → emit session-id once

    U->>SS: listSessions(projectPath)
    SS->>SS: readdir ~/.copilot/session-state/
    SS->>SS: parseWorkspaceMetadata(workspace.yaml)
    SS->>SS: matchesProject(metadata, projectPath)
    SS->>SS: parseEvents(events.jsonl)
    SS-->>U: AgentSessionInfo[]
Loading

Comments Outside Diff (2)

  1. src/main/process-manager/handlers/StdoutHandler.ts, line 2858-2874 (link)

    \uXXXX escape sequences can corrupt JSON object boundary detection

    The isEscaped flag advances only one character past a backslash. Standard single-character escapes (\", \\, \n, \t, etc.) are handled correctly. However, a \uXXXX Unicode escape contains four subsequent hex digits, so only the first digit is consumed as the "escaped" character — the remaining three are treated as ordinary string content.

    The failure case is when a Unicode escape spells out a structurally significant character such as a double-quote (\u0022) or a closing brace (\u007D):

    {"type":"text","data":{"content":"say \u0022hi\u0022"}}
    

    Processing \u0022:

    1. \isEscaped = true
    2. uisEscaped was true → isEscaped = false, continue
    3. 0, 0, 2 → plain string chars, continue
    4. 2 → plain string char, continue

    Then " after 2 terminates the string prematurely. Now inString = false and the subsequent hi\u0022 content is counted outside the string, causing wrong { / } depth tallies. Depending on what follows, this can either produce a prematurely-sliced JSON fragment that JSON.parse then rejects or silently concatenate two adjacent events.

    A targeted fix is to consume all four hex digits when u follows the backslash while inside a string:

    if (isEscaped) {
        isEscaped = false;
        // Consume the 4 hex digits of a \uXXXX sequence so none are
        // misinterpreted as structural characters.
        if (char === 'u') {
            i += 4; // skip the 4 hex digits
        }
        continue;
    }

    In practice, Copilot's own JSON encoder almost certainly emits \" rather than \u0022 for embedded double-quotes, so real-world impact is low. But the current code is not a correct general JSON tokenizer, which may matter if a future agent or a file-read tool result contains non-ASCII content that gets \uXXXX-encoded.

  2. src/main/agents/definitions.ts, line 2134-2141 (link)

    batchModeArgs auto-approves all tools for every batch session, not just YOLO mode

    batchModeArgs includes --allow-all-tools, so every batch-mode Copilot invocation (e.g., a regular Auto Run prompt) automatically gains permission to run arbitrary shell commands without user confirmation — regardless of whether the user has explicitly opted into YOLO mode.

    This differs from every other supported agent (Claude Code, Codex, OpenCode, Factory Droid) where tool auto-approval is gated behind explicit YOLO-mode enablement. A user who turns off YOLO mode in Maestro settings expecting a more cautious run would still receive --allow-all-tools for Copilot.

    The comment "Non-interactive mode requires tool auto-approval" suggests this was intentional (Copilot's interactive /approve prompt cannot fire in batch mode), but the trade-off is not surfaced to the user and is inconsistent with other agents.

    A few options:

    1. Keep the current behavior but document it clearly in the capability metadata or UI tooltip, noting that Copilot batch mode always requires --allow-all-tools.
    2. Default to --allow-all-tools only when YOLO mode is enabled, and emit a warning (or fail gracefully) when Copilot batch mode is attempted without it.

    Also note that yoloModeArgs: ['--allow-all-tools'] is now redundant because batchModeArgs already injects the flag. In a yolo + batch session the CLI would receive --allow-all-tools --silent --allow-all-tools. While most CLIs tolerate duplicate flags, it's worth confirming Copilot does and removing the duplication.

Last reviewed commit: 763299e

Copy link

@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: 1

🧹 Nitpick comments (1)
src/main/process-manager/handlers/StdoutHandler.ts (1)

505-515: Prune emittedToolCallIds for embedded tool blocks.

Line 532 adds every tool.id into a per-process Set, but this path never removes it; only explicit completed/failed tool_use events do. For agents that only surface embedded tool blocks, the cache grows for the whole session. Clearing it on turn completion/result emission, or using a smaller windowed dedupe cache, would avoid that buildup.

Also applies to: 525-533

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/process-manager/handlers/StdoutHandler.ts` around lines 505 - 515,
The emitted tool-call ID cache (getEmittedToolCallIds(managedProcess)) is only
being added to for running/tool_use with a toolName but not cleared for embedded
tool blocks, causing unbounded growth; update StdoutHandler to prune those IDs
when a turn completes or a result is emitted (e.g., hook into the existing
turn-completion/result-emission path to call
getEmittedToolCallIds(managedProcess).clear() or selectively delete the embedded
tool IDs), and ensure the existing delete branch
(getEmittedToolCallIds(...).delete(...)) still handles explicit completed/failed
events; alternatively implement a small windowed/size-limited dedupe set for
emittedToolCallIds to bound memory.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/main/process-manager/handlers/StdoutHandler.ts`:
- Around line 505-515: The emitted tool-call ID cache
(getEmittedToolCallIds(managedProcess)) is only being added to for
running/tool_use with a toolName but not cleared for embedded tool blocks,
causing unbounded growth; update StdoutHandler to prune those IDs when a turn
completes or a result is emitted (e.g., hook into the existing
turn-completion/result-emission path to call
getEmittedToolCallIds(managedProcess).clear() or selectively delete the embedded
tool IDs), and ensure the existing delete branch
(getEmittedToolCallIds(...).delete(...)) still handles explicit completed/failed
events; alternatively implement a small windowed/size-limited dedupe set for
emittedToolCallIds to bound memory.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 58639db4-84a4-41cd-9289-5c47bdec2557

📥 Commits

Reviewing files that changed from the base of the PR and between f405f9d and fab607d.

📒 Files selected for processing (4)
  • src/__tests__/main/agents/session-storage.test.ts
  • src/__tests__/main/process-manager/handlers/StdoutHandler.test.ts
  • src/main/process-manager/handlers/StdoutHandler.ts
  • src/main/storage/copilot-session-storage.ts

Copy link

@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: 4

♻️ Duplicate comments (1)
src/main/storage/copilot-session-storage.ts (1)

450-451: ⚠️ Potential issue | 🟡 Minor

Avoid non-deterministic new Date() fallback for session creation time.

Line 450 can assign “now” to older sessions missing created_at, which distorts ordering/history metadata.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/storage/copilot-session-storage.ts` around lines 450 - 451, Replace
the non-deterministic fallback new Date().toISOString() when computing
timestamp/modifiedAt: set timestamp = metadata.created_at || metadata.updated_at
|| null (or undefined) and set modifiedAt = metadata.updated_at ||
metadata.created_at || null so ordering relies only on existing metadata fields
(metadata.created_at and metadata.updated_at) instead of generating "now";
update any downstream consumers to handle null/undefined if both fields are
missing.
🧹 Nitpick comments (3)
src/__tests__/main/process-manager/spawners/ChildProcessSpawner.test.ts (1)

54-62: Align the parser mock with the full AgentOutputParser contract.

Line 54–62 omits newer parser methods, which can mask integration breaks if ChildProcessSpawner starts depending on them.

🧪 Suggested mock extension
 vi.mock('../../../../main/parsers', () => ({
 	createOutputParser: vi.fn(() => ({
 		agentId: 'claude-code',
 		parseJsonLine: vi.fn(),
+		parseJsonObject: vi.fn(),
 		extractUsage: vi.fn(),
 		extractSessionId: vi.fn(),
 		extractSlashCommands: vi.fn(),
 		isResultMessage: vi.fn(),
 		detectErrorFromLine: vi.fn(),
+		detectErrorFromParsed: vi.fn(),
+		detectErrorFromExit: vi.fn(),
 	})),
 }));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/__tests__/main/process-manager/spawners/ChildProcessSpawner.test.ts`
around lines 54 - 62, The mock returned by createOutputParser in the test
doesn't implement the full AgentOutputParser contract, which risks masking
regressions in ChildProcessSpawner; update the mock in
ChildProcessSpawner.test.ts so createOutputParser: vi.fn(() => ({ ... }))
includes all current methods used by AgentOutputParser (add any newer methods
such as parseStream/close/reset/getState or other recently added helpers) and
ensure each method is a vi.fn() or appropriate stub so tests exercise the same
API surface as the real AgentOutputParser used by ChildProcessSpawner.
src/main/parsers/parser-factory.ts (1)

21-27: Keep parser registration and parser-constructor sources unified.

Line 21–27 duplicates parser membership already maintained in src/main/parsers/index.ts. Consider deriving both from one shared source to prevent future drift when adding/removing agents.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/parsers/parser-factory.ts` around lines 21 - 27, PARSER_CONSTRUCTORS
currently duplicates the parser membership maintained in
src/main/parsers/index.ts; unify them by making a single source of truth (e.g.,
export a shared registry or factory list from the module that currently exports
the parser implementations) and import that into parser-factory.ts instead of
hardcoding PARSER_CONSTRUCTORS; specifically, replace the hardcoded mapping of
string keys to constructors (the PARSER_CONSTRUCTORS constant that returns
AgentOutputParser instances like ClaudeOutputParser, OpenCodeOutputParser,
CodexOutputParser, FactoryDroidOutputParser, CopilotOutputParser) with a
reference to the shared registry exported from the parsers index so
additions/removals update both places automatically.
src/main/utils/agent-args.ts (1)

60-66: Surface conflicting output-format overrides explicitly.

Line 60–66 silently treats an existing flag key with a different value as “already handled.” Consider logging a warning here so users know JSON parsing may be disabled by conflicting custom args.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/utils/agent-args.ts` around lines 60 - 66, The code silently skips
adding JSON output args when a flag key (flagKey) already exists; update the
block so when flagKey?.startsWith('-') && jsonOutputArgs.length > 1 you check if
haystack already contains flagKey and whether the following value differs from
jsonOutputArgs[1]; if a conflicting value is detected emit a clear warning (use
the project's logger or console.warn) that includes flagKey and the existing
conflicting value before returning haystack.includes(flagKey). Ensure you do not
change the existing return behavior (keep returning haystack.includes(flagKey))
and reference the existing variables flagKey, jsonOutputArgs, and haystack.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/releases.md`:
- Line 22: In the "Maestro Symphony" release blurb update the compound adjective
by changing the phrase "open source projects" to "open-source projects" so the
adjective modifying "projects" is hyphenated; locate the sentence containing
"Browse curated issues from projects with the `runmaestro.ai` label" (the line
starting with "🎶 **Maestro Symphony**") and replace the unhyphenated phrase
accordingly.
- Line 90: Replace the phrase "cross context memory" with the hyphenated
compound adjective "cross-context memory" in the sentence that reads "Agents are
now inherently aware of your activity history as seen in the history panel 📜
(this is built-in cross context memory!)" so it reads "...(this is built-in
cross-context memory!)".

In `@src/main/process-manager/types.ts`:
- Line 73: The overflow/resync paths in StdoutHandler currently reset jsonBuffer
and toggle jsonBufferCorrupted but do not clear the dedupe Set
emittedToolCallIds; update those code paths in StdoutHandler to also reset
emittedToolCallIds (e.g., clear the Set or replace with a new Set) whenever
jsonBuffer is cleared or jsonBufferCorrupted is toggled so stale IDs are removed
and valid tool events are not suppressed.

---

Duplicate comments:
In `@src/main/storage/copilot-session-storage.ts`:
- Around line 450-451: Replace the non-deterministic fallback new
Date().toISOString() when computing timestamp/modifiedAt: set timestamp =
metadata.created_at || metadata.updated_at || null (or undefined) and set
modifiedAt = metadata.updated_at || metadata.created_at || null so ordering
relies only on existing metadata fields (metadata.created_at and
metadata.updated_at) instead of generating "now"; update any downstream
consumers to handle null/undefined if both fields are missing.

---

Nitpick comments:
In `@src/__tests__/main/process-manager/spawners/ChildProcessSpawner.test.ts`:
- Around line 54-62: The mock returned by createOutputParser in the test doesn't
implement the full AgentOutputParser contract, which risks masking regressions
in ChildProcessSpawner; update the mock in ChildProcessSpawner.test.ts so
createOutputParser: vi.fn(() => ({ ... })) includes all current methods used by
AgentOutputParser (add any newer methods such as
parseStream/close/reset/getState or other recently added helpers) and ensure
each method is a vi.fn() or appropriate stub so tests exercise the same API
surface as the real AgentOutputParser used by ChildProcessSpawner.

In `@src/main/parsers/parser-factory.ts`:
- Around line 21-27: PARSER_CONSTRUCTORS currently duplicates the parser
membership maintained in src/main/parsers/index.ts; unify them by making a
single source of truth (e.g., export a shared registry or factory list from the
module that currently exports the parser implementations) and import that into
parser-factory.ts instead of hardcoding PARSER_CONSTRUCTORS; specifically,
replace the hardcoded mapping of string keys to constructors (the
PARSER_CONSTRUCTORS constant that returns AgentOutputParser instances like
ClaudeOutputParser, OpenCodeOutputParser, CodexOutputParser,
FactoryDroidOutputParser, CopilotOutputParser) with a reference to the shared
registry exported from the parsers index so additions/removals update both
places automatically.

In `@src/main/utils/agent-args.ts`:
- Around line 60-66: The code silently skips adding JSON output args when a flag
key (flagKey) already exists; update the block so when flagKey?.startsWith('-')
&& jsonOutputArgs.length > 1 you check if haystack already contains flagKey and
whether the following value differs from jsonOutputArgs[1]; if a conflicting
value is detected emit a clear warning (use the project's logger or
console.warn) that includes flagKey and the existing conflicting value before
returning haystack.includes(flagKey). Ensure you do not change the existing
return behavior (keep returning haystack.includes(flagKey)) and reference the
existing variables flagKey, jsonOutputArgs, and haystack.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: bb155574-0c18-4323-bbda-85481f877666

📥 Commits

Reviewing files that changed from the base of the PR and between fab607d and 9247928.

📒 Files selected for processing (12)
  • docs/releases.md
  • src/__tests__/main/process-manager/handlers/StdoutHandler.test.ts
  • src/__tests__/main/process-manager/spawners/ChildProcessSpawner.test.ts
  • src/__tests__/main/utils/agent-args.test.ts
  • src/main/parsers/copilot-output-parser.ts
  • src/main/parsers/index.ts
  • src/main/parsers/parser-factory.ts
  • src/main/process-manager/handlers/StdoutHandler.ts
  • src/main/process-manager/spawners/ChildProcessSpawner.ts
  • src/main/process-manager/types.ts
  • src/main/storage/copilot-session-storage.ts
  • src/main/utils/agent-args.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/main/process-manager/handlers/StdoutHandler.ts

Copy link

@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

♻️ Duplicate comments (2)
docs/releases.md (2)

170-182: ⚠️ Potential issue | 🟡 Minor

Restore heading hierarchy under v0.12.x.

Line 170, Line 174, Line 178, and Line 182 use ##, which flattens section structure under the existing ## v0.12.x heading. These should be subordinate headings.

✏️ Suggested fix
-## Show Thinking
+### Show Thinking

-## GitHub Spec-Kit Integration
+### GitHub Spec-Kit Integration

-## Context Management Tools
+### Context Management Tools

-## Changes Specific to v0.12.3:
+### Changes Specific to v0.12.3:
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/releases.md` around lines 170 - 182, The release notes use top-level
"##" headings for "Show Thinking", "GitHub Spec-Kit Integration", "Context
Management Tools", and "Changes Specific to v0.12.3" which flattens them under
the existing "## v0.12.x" section; update those headings to subordinate headings
(e.g., change the "##" tokens for the lines containing "Show Thinking", "GitHub
Spec-Kit Integration", "Context Management Tools", and "Changes Specific to
v0.12.3" to "###" so they correctly nest under the v0.12.x heading and restore
proper hierarchy).

176-176: ⚠️ Potential issue | 🟡 Minor

Fix typo in feature reference (“Wortrees” → “Worktrees”).

Line 176 contains a typo that makes the sentence look unpolished in user-facing docs.

✏️ Suggested fix
-... which thanks to Wortrees from v0.11.x allows us to run in parallel!
+... which thanks to Worktrees from v0.11.x allows us to run in parallel!
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/releases.md` at line 176, Replace the typo "Wortrees" with "Worktrees"
in the release note sentence that mentions the GitHub Spec-Kit commands and
overrides `/speckit-implement` (the sentence that currently reads "...which
thanks to Wortrees from v0.11.x allows us to run in parallel!"); update the text
to "Worktrees" so the feature name is spelled correctly and matches the
Worktrees feature reference.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/releases.md`:
- Line 266: Replace the non-hyphenated term "prerelease" with the hyphenated
"pre-release" in the release notes entry that currently reads "Enhanced update
checker to filter prerelease tags like -rc, -beta 🚀" and ensure every other
occurrence of "prerelease" in the document matches the "pre-release" spelling so
the document uses a single consistent variant.
- Line 180: In the release notes sentence containing "You will received
(configurable) warnings at 60% and 80% context consumption with a hint to
compact," fix the grammar by replacing "will received" with "will receive" so
the sentence reads "You will receive (configurable) warnings at 60% and 80%
context consumption with a hint to compact."

---

Duplicate comments:
In `@docs/releases.md`:
- Around line 170-182: The release notes use top-level "##" headings for "Show
Thinking", "GitHub Spec-Kit Integration", "Context Management Tools", and
"Changes Specific to v0.12.3" which flattens them under the existing "##
v0.12.x" section; update those headings to subordinate headings (e.g., change
the "##" tokens for the lines containing "Show Thinking", "GitHub Spec-Kit
Integration", "Context Management Tools", and "Changes Specific to v0.12.3" to
"###" so they correctly nest under the v0.12.x heading and restore proper
hierarchy).
- Line 176: Replace the typo "Wortrees" with "Worktrees" in the release note
sentence that mentions the GitHub Spec-Kit commands and overrides
`/speckit-implement` (the sentence that currently reads "...which thanks to
Wortrees from v0.11.x allows us to run in parallel!"); update the text to
"Worktrees" so the feature name is spelled correctly and matches the Worktrees
feature reference.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b8528c65-8f97-4c6a-b074-4f9ee45da3b7

📥 Commits

Reviewing files that changed from the base of the PR and between 9247928 and b3dd122.

📒 Files selected for processing (3)
  • docs/releases.md
  • src/__tests__/main/process-manager/handlers/StdoutHandler.test.ts
  • src/main/process-manager/handlers/StdoutHandler.ts

Copy link

@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: 1

♻️ Duplicate comments (1)
docs/releases.md (1)

170-178: ⚠️ Potential issue | 🟡 Minor

Demote nested v0.12 subsection headings to the correct level.

Line 170, Line 174, and Line 178 are ## headings inside an existing ## section (v0.12.x), which flattens hierarchy and can break docs navigation. These should be ### to stay nested correctly.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/releases.md` around lines 170 - 178, The three subsection headings "Show
Thinking", "GitHub Spec-Kit Integration", and "Context Management Tools" are
using "##" but should be nested under the surrounding v0.12.x section; update
each of these headings to "###" so they remain correctly nested in the v0.12.x
section and preserve docs navigation and TOC generation.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/releases.md`:
- Line 176: Replace the typo "Wortrees" with "Worktrees" in the release note
text that currently reads "thanks to Wortrees from v0.11.x" so it becomes
"thanks to Worktrees from v0.11.x"; update the exact string in the release notes
entry (the sentence containing "/speckit-implement" and the "thanks to
Wortrees..." fragment) to correct the user-facing text.

---

Duplicate comments:
In `@docs/releases.md`:
- Around line 170-178: The three subsection headings "Show Thinking", "GitHub
Spec-Kit Integration", and "Context Management Tools" are using "##" but should
be nested under the surrounding v0.12.x section; update each of these headings
to "###" so they remain correctly nested in the v0.12.x section and preserve
docs navigation and TOC generation.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: da756e5e-0c9f-4940-9c1b-52b1eabfbc32

📥 Commits

Reviewing files that changed from the base of the PR and between b3dd122 and 542f187.

📒 Files selected for processing (1)
  • docs/releases.md

@coderabbitai
Copy link

coderabbitai bot commented Mar 13, 2026

✅ Actions performed

Reviews resumed.

@coderabbitai
Copy link

coderabbitai bot commented Mar 13, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@nolanmclark nolanmclark marked this pull request as draft March 13, 2026 03:39
@nolanmclark nolanmclark marked this pull request as ready for review March 13, 2026 03:39
Copy link

@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: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
src/main/utils/agent-args.ts (1)

197-206: ⚠️ Potential issue | 🟠 Major

Re-run JSON-output conflict checks after custom args are appended.

buildAgentArgs() injects agent.jsonOutputArgs before src/main/ipc/handlers/process.ts calls applyAgentConfigOverrides(). A user override like --output-format text lands here after the earlier hasJsonOutputFlag() pass, so the final argv can still contain conflicting output-format flags and break batch-mode JSON parsing.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/utils/agent-args.ts` around lines 197 - 206, After appending
parsedCustomArgs to finalArgs in buildAgentArgs (using effectiveCustomArgs,
parseCustomArgs, and finalArgs), re-run the existing JSON-output conflict
detection (the same logic used by hasJsonOutputFlag / agent.jsonOutputArgs
validation) to detect any new conflicting output-format flags introduced by
session custom args; if a conflict is found, resolve it consistently (either
remove the conflicting flag, prefer the later session override, or throw a clear
error) and ensure applyAgentConfigOverrides sees the corrected argv so
batch-mode JSON parsing won’t be broken.
src/main/process-manager/handlers/StdoutHandler.ts (2)

366-384: ⚠️ Potential issue | 🟠 Major

Use agent-specific SSH re-auth guidance here.

This branch still tells users to run claude login for every remote auth_expired error. Copilot can now hit it too, so non-Claude sessions will get the wrong recovery command.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/process-manager/handlers/StdoutHandler.ts` around lines 366 - 384,
The auth_expired error handler currently always suggests `claude login`; update
the logic in the block that checks agentError.type === 'auth_expired' (inside
the stdout handling where parsed, toolType,
outputParser.detectErrorFromParsed/detectErrorFromLine and
managedProcess.sshRemoteHost are used) to choose the correct re-auth command
based on the agent/tool type (use the existing toolType or a managedProcess
property if available). Replace the hardcoded `claude login` message with a
conditional that picks the appropriate guidance per tool (e.g., for Claude keep
`claude login`, for Copilot provide the Copilot-specific re-auth steps),
ensuring the message still includes the remote host when
managedProcess.sshRemoteHost is set and sets agentError.message accordingly.

548-564: ⚠️ Potential issue | 🟠 Major

Don't reopen completed Copilot tools from toolUseBlocks.

CopilotOutputParser.parseAssistantMessage() attaches toolUseBlocks to final_answer result events. By the time this loop runs, the matching tool.execution_complete has already cleared the ID from emittedToolCallIds, so the final answer re-emits the tool as a fresh running execution and leaves it hanging in the UI.

🔧 Minimal guard
-		if (event.toolUseBlocks?.length) {
+		if (event.type === 'text' && event.toolUseBlocks?.length) {
 			for (const tool of event.toolUseBlocks) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/process-manager/handlers/StdoutHandler.ts` around lines 548 - 564,
The loop over event.toolUseBlocks can re-emit tools that were already completed;
add a guard at the top of the loop (in StdoutHandler.ts where
event.toolUseBlocks, getEmittedToolCallIds(managedProcess),
emittedToolCallIds.has(tool.id), emittedToolCallIds.add(tool.id) and
this.emitter.emit('tool-execution'...) are used) to skip any tool block that
represents a completed execution — e.g. check for a completion indicator on the
tool (tool.execution_complete === true or tool.state?.status === 'completed' or
tool.completed === true) and continue without adding the id or emitting; only
add the id and emit when the tool is not marked completed.
♻️ Duplicate comments (1)
docs/releases.md (1)

170-178: ⚠️ Potential issue | 🟡 Minor

Fix heading level under v0.12.x section

At Line 170, Line 174, and Line 178, these subsection headings use ## even though they are nested under ## v0.12.x. This flattens the document hierarchy and hurts TOC/navigation.

✏️ Suggested fix
-## Show Thinking
+### Show Thinking

-## GitHub Spec-Kit Integration
+### GitHub Spec-Kit Integration

-## Context Management Tools
+### Context Management Tools
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/releases.md` around lines 170 - 178, The three subsection headings "Show
Thinking", "GitHub Spec-Kit Integration", and "Context Management Tools" are
using top-level level-2 headings (##) under the existing "v0.12.x" section which
flattens the TOC; change each of those headings to level-3 (###) so they are
correctly nested under the "v0.12.x" section (update the lines containing those
exact heading texts).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/releases.md`:
- Around line 240-242: The release note bullet "Add Sentry crashing reporting
monitoring with opt-out 🐛" contains a wording typo; update that sentence to
clear, grammatical user-facing text such as "Add Sentry crash reporting
(opt-out) 🐛" or "Add Sentry crash reporting with opt-out 🐛" so the phrasing
around Sentry and opt-out is correct; locate and replace the exact string "Add
Sentry crashing reporting monitoring with opt-out 🐛" in the releases.md
content.

In `@src/main/storage/copilot-session-storage.ts`:
- Around line 151-169: The matching is currently case-sensitive so Windows paths
like "C:\Repo" vs "c:\repo" won't match; update normalizePath/matchesProject to
case-fold Windows-style paths only: detect Windows paths (e.g., presence of a
drive letter followed by ':' or backslashes) after normalizing separators and
trailing slashes, then call .toLowerCase() on normalizedProject,
metadata.git_root and metadata.cwd when a Windows-style path is detected, but
leave POSIX/SSH paths case-sensitive; ensure matchesProject still compares
git_root, cwd, and cwd.startsWith(`${normalizedProject}/`) using the possibly
lowercased values.
- Around line 499-503: The catch block that currently returns null for every
exception when loading Copilot session metadata (referencing sessionId and
logger.debug in copilot-session-storage.ts) must distinguish expected "not
found" or permission/read-recoverable errors from unexpected failures: catch
only file-not-found/readable errors (e.g., ENOENT/EPERM or use an existing
isNotFound/isFsError helper) and return null there, but for all other exceptions
call the Sentry/reporting utility (e.g., Sentry.captureException or the
project's reportError helper) with the error and contextual info, then rethrow
or return a structured error instead of swallowing it; update the catch in the
loadSessionMetadata/loadCopilotSessionMetadata function accordingly so
unexpected issues are surfaced.

---

Outside diff comments:
In `@src/main/process-manager/handlers/StdoutHandler.ts`:
- Around line 366-384: The auth_expired error handler currently always suggests
`claude login`; update the logic in the block that checks agentError.type ===
'auth_expired' (inside the stdout handling where parsed, toolType,
outputParser.detectErrorFromParsed/detectErrorFromLine and
managedProcess.sshRemoteHost are used) to choose the correct re-auth command
based on the agent/tool type (use the existing toolType or a managedProcess
property if available). Replace the hardcoded `claude login` message with a
conditional that picks the appropriate guidance per tool (e.g., for Claude keep
`claude login`, for Copilot provide the Copilot-specific re-auth steps),
ensuring the message still includes the remote host when
managedProcess.sshRemoteHost is set and sets agentError.message accordingly.
- Around line 548-564: The loop over event.toolUseBlocks can re-emit tools that
were already completed; add a guard at the top of the loop (in StdoutHandler.ts
where event.toolUseBlocks, getEmittedToolCallIds(managedProcess),
emittedToolCallIds.has(tool.id), emittedToolCallIds.add(tool.id) and
this.emitter.emit('tool-execution'...) are used) to skip any tool block that
represents a completed execution — e.g. check for a completion indicator on the
tool (tool.execution_complete === true or tool.state?.status === 'completed' or
tool.completed === true) and continue without adding the id or emitting; only
add the id and emit when the tool is not marked completed.

In `@src/main/utils/agent-args.ts`:
- Around line 197-206: After appending parsedCustomArgs to finalArgs in
buildAgentArgs (using effectiveCustomArgs, parseCustomArgs, and finalArgs),
re-run the existing JSON-output conflict detection (the same logic used by
hasJsonOutputFlag / agent.jsonOutputArgs validation) to detect any new
conflicting output-format flags introduced by session custom args; if a conflict
is found, resolve it consistently (either remove the conflicting flag, prefer
the later session override, or throw a clear error) and ensure
applyAgentConfigOverrides sees the corrected argv so batch-mode JSON parsing
won’t be broken.

---

Duplicate comments:
In `@docs/releases.md`:
- Around line 170-178: The three subsection headings "Show Thinking", "GitHub
Spec-Kit Integration", and "Context Management Tools" are using top-level
level-2 headings (##) under the existing "v0.12.x" section which flattens the
TOC; change each of those headings to level-3 (###) so they are correctly nested
under the "v0.12.x" section (update the lines containing those exact heading
texts).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: fdefe50e-6d6c-4ac2-9afd-e4b49d997ae3

📥 Commits

Reviewing files that changed from the base of the PR and between b3dd122 and 1752c2f.

📒 Files selected for processing (5)
  • docs/releases.md
  • src/main/parsers/copilot-output-parser.ts
  • src/main/process-manager/handlers/StdoutHandler.ts
  • src/main/storage/copilot-session-storage.ts
  • src/main/utils/agent-args.ts

@nolanmclark
Copy link
Author

Fixed the "outside diff range" comment about empty final_answer falling back to accumulated commentary as result text.

The streamedText fallback on line 608 now skips Copilot — for Copilot, streamedText holds transient commentary that was already emitted as partial text, so falling back to it would duplicate content in the UI. Only non-Copilot agents (where streamedText accumulates the final answer) use this fallback.

Copy link

@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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/main/process-manager/handlers/StdoutHandler.ts (1)

549-565: ⚠️ Potential issue | 🟡 Minor

Tool ID deduplication for Claude Code never cleans up entries.

For OpenCode/Codex (lines 537-539), tool IDs are removed on completed/failed. However, for Claude Code toolUseBlocks (lines 551-557), IDs are added but never removed. Over long sessions with many tool invocations, emittedToolCallIds will grow unbounded.

Consider clearing tool IDs for Claude Code on result emission or implementing a bounded LRU cache if Claude Code doesn't emit explicit completion events.

🛠️ Suggested fix: Clear tool IDs on result emission
 		// Handle result
 		if (
 			managedProcess.toolType !== 'codex' &&
 			outputParser.isResultMessage(event) &&
 			!managedProcess.resultEmitted
 		) {
 			managedProcess.resultEmitted = true;
+			// Clear tool call tracking on turn completion to prevent unbounded growth
+			managedProcess.emittedToolCallIds?.clear();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/process-manager/handlers/StdoutHandler.ts` around lines 549 - 565,
The Claude Code toolUseBlocks path adds tool IDs to emittedToolCallIds (via
getEmittedToolCallIds(managedProcess)) but never removes them, causing unbounded
growth; update the StdoutHandler handling of event.toolUseBlocks (the loop that
calls this.emitter.emit('tool-execution', ...)) to clear the ID after the
corresponding result is emitted (or immediately after emit if Claude never sends
completion), e.g., call emittedToolCallIds.delete(tool.id) at the appropriate
place or replace the emittedToolCallIds store with a bounded/LRU cache
implementation referenced by getEmittedToolCallIds to cap growth.
🧹 Nitpick comments (1)
src/main/process-manager/handlers/StdoutHandler.ts (1)

466-470: Consider using structured logger instead of console.log.

The codebase uses logger.debug for structured logging (e.g., lines 442-450), but these debug statements use console.log. For consistency and production observability, consider converting to logger.debug or removing if these are temporary debugging artifacts.

♻️ Suggested change
-			// DEBUG: Log usage extracted from parser
-			console.log('[StdoutHandler] Usage from parser (line 255 path)', {
-				sessionId,
-				toolType: managedProcess.toolType,
-				parsedUsage: usage,
-			});
+			logger.debug('[ProcessManager] Usage extracted from parser', 'ProcessManager', {
+				sessionId,
+				toolType: managedProcess.toolType,
+				parsedUsage: usage,
+			});

Also applies to lines 488-491, 681-686, 694-698.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/process-manager/handlers/StdoutHandler.ts` around lines 466 - 470,
Replace ad-hoc console.log calls in StdoutHandler (e.g., the debug at "Usage
from parser (line 255 path)" that logs sessionId, managedProcess.toolType,
parsedUsage) with the module's structured logger.debug calls used elsewhere;
ensure you call logger.debug with the same payload keys (sessionId, toolType,
parsedUsage) and a clear message, and update the other similar console.log
occurrences referenced (around the other noted lines) to use logger.debug or
remove them if they were temporary; maintain existing log level and formatting
consistent with the nearby logger.debug statements.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@src/main/process-manager/handlers/StdoutHandler.ts`:
- Around line 549-565: The Claude Code toolUseBlocks path adds tool IDs to
emittedToolCallIds (via getEmittedToolCallIds(managedProcess)) but never removes
them, causing unbounded growth; update the StdoutHandler handling of
event.toolUseBlocks (the loop that calls this.emitter.emit('tool-execution',
...)) to clear the ID after the corresponding result is emitted (or immediately
after emit if Claude never sends completion), e.g., call
emittedToolCallIds.delete(tool.id) at the appropriate place or replace the
emittedToolCallIds store with a bounded/LRU cache implementation referenced by
getEmittedToolCallIds to cap growth.

---

Nitpick comments:
In `@src/main/process-manager/handlers/StdoutHandler.ts`:
- Around line 466-470: Replace ad-hoc console.log calls in StdoutHandler (e.g.,
the debug at "Usage from parser (line 255 path)" that logs sessionId,
managedProcess.toolType, parsedUsage) with the module's structured logger.debug
calls used elsewhere; ensure you call logger.debug with the same payload keys
(sessionId, toolType, parsedUsage) and a clear message, and update the other
similar console.log occurrences referenced (around the other noted lines) to use
logger.debug or remove them if they were temporary; maintain existing log level
and formatting consistent with the nearby logger.debug statements.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 9d03ec43-b1f4-48b6-841c-e16d3a3f7555

📥 Commits

Reviewing files that changed from the base of the PR and between dce62b4 and 0382e8d.

📒 Files selected for processing (1)
  • src/main/process-manager/handlers/StdoutHandler.ts

Copy link

@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 the current code and only fix it if needed.

Inline comments:
In `@src/main/storage/copilot-session-storage.ts`:
- Around line 178-187: The function buildToolUse currently returns an empty
array when all toolRequests are filtered out, which is truthy and leads to
creating meaningless assistant messages; change buildToolUse (and the analogous
implementation at the other occurrence around lines 229-239) to first compute
the mapped result, then return undefined if that result.length === 0, otherwise
return the result (i.e., avoid returning []). Update references to buildToolUse
so callers only see a defined value when there are actual tool entries.
- Around line 287-302: The try-catch in getLocalDirectorySize currently swallows
all errors and returns 0; change it to only return 0 for expected/benign errors
(e.g., ENOENT/NOT_FOUND) and for any other unexpected I/O failures call the
project's Sentry utility (e.g., captureException or reportError) with contextual
details (sessionDir, entryPath, function name) and then rethrow the error (or
propagate it) so failures are observable; apply the same pattern to the
analogous catch blocks at the other locations mentioned (around lines 430-435
and 539-545) so unexpected permission/read/path errors are reported rather than
silently degraded.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6458a1b5-e270-4e64-a4c1-aa9ca212c335

📥 Commits

Reviewing files that changed from the base of the PR and between 0382e8d and d17e042.

📒 Files selected for processing (1)
  • src/main/storage/copilot-session-storage.ts

Copy link

@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: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/main/storage/copilot-session-storage.ts`:
- Around line 543-557: readEventsFile currently swallows all errors; change its
catch to mirror other functions: catch the error, if it represents a
missing/unreadable file (e.g., error.code === 'ENOENT' or equivalent remote "not
found" condition from readRemoteFile) return null, otherwise report the
exception to Sentry (captureException/reporting helper used elsewhere) and
rethrow the error so it bubbles to Sentry/upper layers; keep references to
getEventsPath and readRemoteFile when locating the logic to modify.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 1f029b5a-7496-4a01-9015-5aab3f628c54

📥 Commits

Reviewing files that changed from the base of the PR and between d17e042 and e9f7b61.

📒 Files selected for processing (2)
  • src/main/agents/path-prober.ts
  • src/main/storage/copilot-session-storage.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/main/agents/path-prober.ts

@nolanmclark
Copy link
Author

Good to go! I have a follow up PR that will add some additional enhancements depending on if this is accepted.

@chr1syy
Copy link
Collaborator

chr1syy commented Mar 13, 2026

Whats the enhancement over using OpenCode with Github Copilot?
For myself, i'm using Copilot over OpenCode, with introduction of Copilot we have to deal with another CLI.
What is your follow-up PR introducing?
@pedramamini what is your take on it?

@nolanmclark
Copy link
Author

Whats the enhancement over using OpenCode with Github Copilot? For myself, i'm using Copilot over OpenCode, with introduction of Copilot we have to deal with another CLI. What is your follow-up PR introducing? @pedramamini what is your take on it?

The Github Copilot CLI has its own set of features and capabilities. You should give it a try if you're using copilot with OpenCode! https://github.com/github/copilot-cli

With that maintainability argument, you could say why not run everything through OpenCode instead of Claude Code, Codex CLI, or Gemini CLI. 😄 Github Copilot CLI runs in the same manner as other implemented CLI agents, so it should be modular in nature and not adding any additional overhead. IMO the value of Maestro is bringing together multiple agents, each bringing their own capabilities beyond the model access, and GH CLI falls into that bucket.

@chr1syy
Copy link
Collaborator

chr1syy commented Mar 13, 2026

Didnt mean to sound like i don't want to implement it, you are right to say that with OpenCode we could just implement everything over that. But i remember there was something with GitHub CLI that didn't fit into the way Maestro harnesses other CLI tools. But i can't remember what it exactly it was. Maybe you figured something i did not.

@chr1syy
Copy link
Collaborator

chr1syy commented Mar 13, 2026

@greptileai re-review

@nolanmclark
Copy link
Author

Didnt mean to sound like i don't want to implement it, you are right to say that with OpenCode we could just implement everything over that. But i remember there was something with GitHub CLI that didn't fit into the way Maestro harnesses other CLI tools. But i can't remember what it exactly it was. Maybe you figured something i did not.

No problem, I totally get it! There's been a TON of improvements to Copilot CLI over the last few months, totally could have had gaps initially that have now been filled.

I missed the question on "What is the follow up introducing?". The follow up includes support for referencing images and better UX for insights on what GHCLI is doing while it's working. I want to also add support for the slash commands - which is where some of the additional functionality/features of the CLI are going to become relevant. This PR is really "get the basics in place" and follow up feature PRs will slowly turn on the applicable capabilities. This PR was already getting very large, so I thought having follow-up feature PRs will be easiest to digest.

@chr1syy
Copy link
Collaborator

chr1syy commented Mar 13, 2026

I'll checkout Github CLI and pull the PR :)

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.

2 participants