Skip to content

feat: Add GitHub Copilot CLI as first-class agent with multi-model support#805

Closed
scriptease wants to merge 27 commits intoRunMaestro:rcfrom
scriptease:feat/copilot-cli-agent-rc
Closed

feat: Add GitHub Copilot CLI as first-class agent with multi-model support#805
scriptease wants to merge 27 commits intoRunMaestro:rcfrom
scriptease:feat/copilot-cli-agent-rc

Conversation

@scriptease
Copy link
Copy Markdown

@scriptease scriptease commented Apr 12, 2026

Summary

  • Adds GitHub Copilot CLI (copilot) as a fully integrated agent in Maestro, with JSONL output parsing, session storage, error detection, and batch mode support
  • Introduces a multi-model picker powered by the models.dev API, allowing users to seamlessly switch between Anthropic, OpenAI, Google, and xAI models within a single Copilot CLI agent
  • Full integration across all 9 agent registry touchpoints: agent ID, definition, capabilities, metadata, context window, output parser, error patterns, session storage, and wizard/modal tiles

Multi-Model Support

Bildschirmfoto 2026-04-12 um 10 59 50

Copilot CLI uniquely supports switching between models from different providers through a single agent. The model picker fetches available models from the github-copilot provider on models.dev and also reads the user's configured model from ~/.copilot/config.json.

Currently available models (via models.dev):

Anthropic OpenAI Google xAI
claude-opus-4.6 gpt-5.4 gemini-3.1-pro-preview grok-code-fast-1
claude-opus-4.5 gpt-5.4-mini gemini-3-pro-preview
claude-sonnet-4.6 gpt-5.2 gemini-3-flash-preview
claude-sonnet-4.5 gpt-5.2-codex gemini-2.5-pro
claude-sonnet-4 gpt-5.1
claude-opus-41 gpt-5.1-codex
claude-haiku-4.5 gpt-5.1-codex-max
gpt-5.1-codex-mini
gpt-5
gpt-5-mini
gpt-4.1
gpt-4o

Known Issue: Gemini models not yet available

Bildschirmfoto 2026-04-12 um 10 01 43

Gemini models appear in the models.dev catalog and the model picker but are not yet accepted by the Copilot CLI binary. Selecting one produces:

This is tracked upstream at github/copilot-cli#1703. The error is handled gracefully — users see a message directing them to check GitHub Copilot feature settings.

Contributors

  • @dwizzzle — Original Copilot CLI agent implementation (#701)
  • @adamcongdon — Error patterns and launcher flag suggestions (from 6fff670)
  • @jSydorowicz21 — Thorough PR review that caught stderr suppression blast radius, SSH storage gap, and stale comments

What's included

Core integration:

  • Agent definition with copilot binary, --output-format json JSONL output, --allow-all batch mode, stdin prompt delivery
  • JSONL output parser handling assistant.message_delta, assistant.message, result, session.error events
  • Session storage reading from ~/.copilot/session-state/<uuid>/ (workspace.yaml + events.jsonl)
  • Comprehensive error patterns: auth, rate limiting, network, token exhaustion, model availability, session not found
  • 665-line test suite for the output parser

Model picker:

  • Fetches models from models.dev API (github-copilot provider)
  • Falls back to user's ~/.copilot/config.json model
  • Searchable dropdown in agent config panel

Bug fixes from review:

  • Narrowed stderr suppression to copilot-cli only (was affecting OpenCode and Factory Droid)
  • Added SSH remote session storage warnings with Phase 2 deferral
  • Fixed tab naming spawn missing sendPromptViaStdinRaw flag
  • Added copilot-cli to SUPPORTED_AGENTS in NewInstanceModal

Test plan

  • Create new Copilot CLI agent via wizard and NewInstanceModal
  • Verify model picker loads models from models.dev
  • Switch between Claude, GPT, and Grok models mid-conversation
  • Verify Gemini model selection shows actionable error message
  • Verify tab auto-naming works for Copilot CLI sessions
  • Verify session storage lists and resumes sessions
  • Verify OpenCode/Factory Droid stderr is not suppressed (regression check)

Summary by CodeRabbit

Release Notes

  • New Features

    • Added GitHub Copilot CLI as a new supported agent option.
    • Integrated session storage and resumption capabilities for Copilot CLI.
    • Implemented automatic model discovery for available Copilot CLI models.
    • Updated agent selection interface to include Copilot CLI.
  • Documentation

    • Added comprehensive Copilot usage guidelines and engineering standards.
    • Added Phase 2 planning documentation for future Copilot CLI enhancements.

dwizzzle and others added 18 commits April 12, 2026 08:41
Add copilot-cli agent support with full JSONL output parsing, session
storage browsing, error detection, and UI integration.

Agent definition:
- Binary: copilot, batch mode via -p flag
- JSON output: --output-format json (JSONL)
- Session resume: --resume SESSION-ID
- YOLO mode: --allow-all
- Model selection: --model flag

Output parser (verified against actual CLI output):
- 11 event types: session lifecycle, streaming deltas, tool execution,
  assistant messages, and result with session ID
- Accumulates outputTokens from assistant.message events

Session storage:
- Reads ~/.copilot/session-state/<uuid>/workspace.yaml for metadata
- Parses events.jsonl for message history
- Supports pagination, search, and project filtering

Also includes:
- Error patterns (auth, rate limit, network, token exhaustion)
- UI: added to SUPPORTED_AGENTS and wizard agent tiles
- copilot-instructions.md for Copilot sessions in this repo

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
41 tests covering all 11 JSONL event types verified against actual
copilot CLI output. Includes:

- Session lifecycle: mcp_server_status_changed, mcp_servers_loaded, tools_updated
- Conversation: user.message, assistant.turn_start/end, message_delta, message
- Tool execution: tool.execution_start, tool.execution_complete
- Completion: result with sessionId and usage
- Error detection: auth, rate limit, network, exit codes
- End-to-end: full session simulations (simple + tool use)
- Edge cases: empty deltas, long output truncation, token accumulation

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ssues

On Windows, long prompts passed as PowerShell CLI args get garbled due
to escaping issues with special characters. Changed copilot-cli to use
'-p -' (read from stdin) and sendPromptViaStdinRaw=true.

Also added sendPromptViaStdinRaw as an agent definition field so other
agents can opt into stdin-based prompt delivery.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
When an output parser is registered (JSONL agents like copilot-cli, codex,
opencode, factory-droid), non-JSON lines from stdout are now suppressed
instead of being displayed to the user as raw text. This prevents
PowerShell profile banners and MCP server startup messages from cluttering
the agent output.

Only agents WITHOUT an output parser (terminal, legacy mode) continue to
emit raw non-JSON lines, which is correct for terminal sessions.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The promptArgs function was being skipped when sendPromptViaStdinRaw=true
because the spawner's promptViaStdin guard prevents adding prompt args.
But '-p -' isn't prompt text — it's a flag telling copilot to read stdin.
Moved to batchModeArgs so it's always present in batch mode.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
copilot -p - means 'prompt is the literal dash character', not 'read
from stdin'. When stdin is piped (sendPromptViaStdinRaw), copilot reads
it automatically without -p. Removed -p - from batchModeArgs.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot CLI outputs MCP server startup messages, PowerShell profile
banners, and initialization noise to stderr. These were being displayed
via the onStderr renderer handler. Now suppressed for all JSONL agents
with output parsers (except Codex which has special stderr handling).

Error detection still runs first, so real errors are captured.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Two fixes for the raw JSONL display issue:

1. detectErrorFromParsed now handles session.error events (Copilot CLI
   format: data.message, data.errorType). Previously, session.error
   wasn't caught because it has no top-level 'error' field, causing
   the error to fall through to detectErrorFromExit which dumps the
   full stdoutBuffer in raw.

2. detectErrorFromExit no longer includes stderr/stdout in raw — for
   JSONL agents the stdoutBuffer contains all parsed JSON events which
   is noise, not useful error context.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The root cause of raw JSONL display: copilot-cli's --output-format json
args didn't match the isStreamJsonMode heuristic check, so the process
was treated as batch-JSON (single JSON blob) instead of streaming JSONL.
On exit, handleBatchModeExit tried to JSON.parse the entire buffer,
failed, and dumped the raw content to the display.

Fixed by adding outputParser presence as a signal for stream-json mode.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
P1: Reset accumulatedOutputTokens on result event (singleton parser
was carrying tokens across sessions). Report tokens unconditionally
even when result event has no usage field.

P2: Add UUID validation to session storage public methods to prevent
path traversal. Update Phase 2 parity matrix to reflect shipped
features. Derive GRID_ROWS from AGENT_TILES.length.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Fetch available models from models.dev API (github-copilot provider)
for the copilot-cli agent. Falls back to user configured model from
~/.copilot/config.json if the API is unavailable.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…SSH, fix stale comment

1. StderrHandler: narrow stderr suppression to copilot-cli only instead of
   all agents with output parsers. The previous condition (outputParser &&
   toolType !== 'codex') silently dropped stderr for OpenCode and Factory
   Droid, which is a behavioral change to shipped agents.

2. CopilotCliSessionStorage: add logger.warn() when sshConfig is provided,
   since SSH remote session storage is not yet implemented. Previously
   silently fell back to local sessions. Added SSH remote storage as
   explicit Phase 2 deferred item in the plan doc.

3. capabilities.ts: update stale comment that said session storage was
   deferred to Phase 2 — it shipped in Phase 1.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Merge additional error patterns from @adamcongdon parallel Copilot CLI
implementation:

- auth_expired: invalid token, please login patterns
- rate_limited: HTTP 429 status code pattern
- network_error: ECONNRESET/ENOTFOUND, request timeout patterns
- token_exhaustion: context exceeded, too many tokens alternates
- agent_crashed: fatal/unexpected/internal/unhandled error pattern
- session_not_found: new category for session not found / invalid session

Also add suggested launcher flags (--no-auto-update, --no-ask-user) to
Phase 2 plan doc, credited to @adamcongdon.

Co-Authored-By: Adam Congdon <automating.adam@gmail.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copilot CLI was grayed out with 'Coming Soon' in the Create New Agent
modal because it was missing from the SUPPORTED_AGENTS array. The wizard
AgentSelectionScreen already had it marked as supported.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…pawn

The tab naming handler built its own spawn config but omitted the agent
definition's sendPromptViaStdinRaw and noPromptSeparator flags. For
copilot-cli (which requires stdin prompt delivery), this caused the
prompt to be appended as a positional CLI argument with '--' separator,
triggering 'error: too many arguments. Expected 0 arguments but got 1.'
The error was then silently suppressed by the copilot-cli stderr filter.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 12, 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
📝 Walkthrough

Walkthrough

This PR adds comprehensive support for GitHub Copilot CLI as a new agent type in Maestro, including agent configuration, JSONL output parsing, session storage, error handling, and UI integration across the codebase.

Changes

Cohort / File(s) Summary
Documentation
.github/copilot-instructions.md, docs/Copilot-CLI-Phase2-Plan.md
Added Copilot usage guidelines and engineering standards, plus a Phase 2 planning document defining feature parity requirements for the Copilot CLI agent.
Agent Configuration
src/shared/agentIds.ts, src/shared/agentMetadata.ts, src/shared/agentConstants.ts, src/main/agents/definitions.ts, src/main/agents/capabilities.ts
Extended agent registry to include 'copilot-cli' with display name, BETA classification, 200k context window, capability flags (JSON output, session ID, streaming, distinct results), and CLI configuration (stdin-raw prompts, batch args, JSON output args, session resume, model args).
Output Parsing & Error Handling
src/main/parsers/copilot-cli-output-parser.ts, src/main/parsers/error-patterns.ts, src/main/parsers/index.ts
Implemented new CopilotCliOutputParser to normalize Copilot CLI JSONL events into normalized ParsedEvents; added COPILOT_CLI_ERROR_PATTERNS for error detection (auth expiration, rate limiting, network/token/crash/session errors); registered parser in parser registry.
Process Management
src/main/process-manager/types.ts, src/main/process-manager/spawners/ChildProcessSpawner.ts, src/main/process-manager/handlers/StderrHandler.ts, src/main/process-manager/handlers/StdoutHandler.ts, src/main/ipc/handlers/process.ts, src/main/ipc/handlers/tabNaming.ts
Added copilotAccumulatedTokens field to ManagedProcess; refactored stream-JSON mode detection to account for output parser presence; updated stderr suppression for benign Copilot CLI initialization patterns; enhanced stdout handling for per-event token accumulation and usage event synthesis; threaded sendPromptViaStdinRaw and noPromptSeparator config through spawn IPC handlers.
Model Discovery
src/main/agents/detector.ts
Added copilot-cli model discovery via https://models.dev/api.json fetch with 3s timeout, extracting GitHub Copilot model names and merging user-configured model from ~/.copilot/config.json.
Session Storage
src/main/storage/copilot-cli-session-storage.ts, src/main/storage/index.ts
Implemented CopilotCliSessionStorage to access local Copilot CLI sessions under ~/.copilot/session-state/, providing session listing (with metadata, timestamps, message counts), message reading with pagination, and searchable message extraction; registered in storage initialization.
UI Integration
src/renderer/components/NewInstanceModal/types.ts, src/renderer/components/Wizard/screens/AgentSelectionScreen.tsx
Added 'copilot-cli' to SUPPORTED_AGENTS; added new agent tile for Copilot CLI with name, brand color, and description; refactored grid navigation to compute grid rows dynamically from tile count.
Tests
src/__tests__/main/agents/detector.test.ts, src/__tests__/main/parsers/copilot-cli-output-parser.test.ts, src/__tests__/main/parsers/index.test.ts, src/__tests__/main/process-manager/spawners/ChildProcessSpawner.test.ts
Updated detector tests to derive expected agent count from AGENT_IDS.length and assert copilot-cli detection; added comprehensive parser test suite (728 lines) covering lifecycle events, streaming, tool use, token handling, error detection, and end-to-end sessions; updated parser registry count expectations; adjusted spawner mock to handle new toolType parameter.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Renderer
    participant Main
    participant ChildProcess as Child Process
    participant Parser as Output Parser
    participant Storage as Session Storage

    User->>Renderer: Select Copilot CLI agent & send prompt
    Renderer->>Main: spawn IPC (sendPromptViaStdinRaw: true)
    Main->>Main: Load copilot-cli agent config<br/>(definitions, capabilities)
    Main->>ChildProcess: Spawn 'copilot --allow-all --output-format json'
    Main->>ChildProcess: Write prompt to stdin
    ChildProcess->>ChildProcess: Process prompt, emit JSONL events
    ChildProcess-->>Main: stdout (JSONL lines)
    Main->>Parser: parseJsonLine(line)
    Parser->>Parser: transformMessage(type) to normalized event
    Parser-->>Main: ParsedEvent (text/tool_use/usage/system)
    Main->>Main: Accumulate outputTokens per session
    Main-->>Renderer: Emit normalized events
    Main->>Storage: readSessionMessages(projectPath, sessionId)
    Storage->>Storage: Parse events.jsonl from<br/>~/.copilot/session-state/
    Storage-->>Main: SessionMessage[] with pagination
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

approved


🐰 A Copilot hops into Maestro's fold,
With JSONL streams of AI bold,
Sessions stored and tokens tracked,
Errors caught, no feature lacks,
New agent shines, the story's told! ✨

🚥 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 accurately and specifically summarizes the main change: adding GitHub Copilot CLI as a first-class agent with multi-model support, which aligns with the comprehensive integration across the codebase documented in the pull request.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% 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

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.

Copy link
Copy Markdown

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

🧹 Nitpick comments (2)
src/__tests__/main/agents/detector.test.ts (1)

325-326: Avoid hardcoded agent counts in tests.

Line 326 and Line 973 hardcode 9, which will break on every future agent addition/removal. Prefer deriving from the canonical ID list (AGENT_IDS.length) and keep the explicit toContain('copilot-cli') assertions.

Also applies to: 972-973

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

In `@src/__tests__/main/agents/detector.test.ts` around lines 325 - 326, Replace
the hardcoded agent count assertions in the detector tests with a dynamic check
based on the canonical AGENT_IDS list: instead of expect(agents.length).toBe(9)
use expect(agents.length).toBe(AGENT_IDS.length) (or equivalent), while
retaining explicit inclusion checks like
expect(agents).toContain('copilot-cli'); locate the assertions around the tests
referencing agents (e.g., the block using the agents variable and the
toContain('copilot-cli') check) and update both occurrences so the test no
longer hardcodes numeric counts but derives the expected length from AGENT_IDS.
src/__tests__/main/parsers/index.test.ts (1)

52-57: Consolidate parser-count expectation to a single source in this test file.

These assertions repeat the 5 literal in several places. Consider using one shared expected parser list/count constant so future parser additions only require one edit.

Also applies to: 62-67, 76-76

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

In `@src/__tests__/main/parsers/index.test.ts` around lines 52 - 57, Replace
hard-coded literal counts in this test file with a single shared constant (e.g.,
EXPECTED_PARSERS or EXPECTED_PARSER_COUNT) defined near the top of the file and
reference it in the tests that call initializeOutputParsers() and
getAllOutputParsers(); update the assertions in the tests that currently use 5
(the one checking parsers.length and the other occurrences around lines 62–67
and 76) to use that shared constant so adding/removing parsers only requires
updating one value and keep tests in sync.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/copilot-instructions.md:
- Line 68: Update the documentation reference that currently points to the
renderer shim `src/renderer/constants/themes.ts` to instead point to the
canonical shared module `src/shared/themes.ts` (and continue to reference
`src/shared/theme-types.ts` for the `ThemeId` type); locate the doc line listing
"Themes" and replace the path so contributors are directed to
`src/shared/themes.ts` as the source-of-truth and `src/shared/theme-types.ts`
for `ThemeId`, avoiding edits to the re-export shim
`src/renderer/constants/themes.ts`.

In `@docs/Copilot-CLI-Phase2-Plan.md`:
- Around line 21-23: Summary: The docs instruct running Copilot with -p but the
PR sends prompts via raw stdin; update the doc to use the same stdin transport
so captured JSONL matches what the integration emits. Change the guidance in
docs/Copilot-CLI-Phase2-Plan.md to instruct piping the prompt into Copilot (use
the stdin transport) when running `copilot --output-format json --allow-all`,
capture that output, and re-run the event shape investigation; also mention
re-validating the parser at src/main/parsers/copilot-cli-output-parser.ts
against the piped stdin JSONL output so the heuristic matching is refined to the
actual schema emitted by the stdin path.

In `@src/main/agents/detector.ts`:
- Around line 365-402: The fetch to 'https://models.dev/api.json' in the
function/section that returns models for agentId should be bounded with an
AbortController and short timeout: create an AbortController, pass its signal
into fetch, set a timer (e.g., 2–3s) to call controller.abort(), and clear the
timer on successful response; handle the abort error path the same as other
fetch failures so the code falls back to reading ~/.copilot/config.json and
continues to return models; update the try/catch around fetch (and any nested
await response.json()) to respect the abort and avoid waiting indefinitely.

In `@src/main/ipc/handlers/process.ts`:
- Line 645: The line that sets sendPromptViaStdinRaw in the IPC payload is
overwriting the per-request value from ...config with the agent default; change
the assignment so it prefers the request-level flag first and falls back to
agent.sendPromptViaStdinRaw only if config.sendPromptViaStdinRaw is undefined
(i.e., use config.sendPromptViaStdinRaw ?? agent?.sendPromptViaStdinRaw). Update
the object property where sendPromptViaStdinRaw is set (near the
sendPromptViaStdinRaw: agent?.sendPromptViaStdinRaw entry) to use that
nullish-coalescing pattern so IPC callers can force raw-stdin per request while
still honoring the agent default when absent.

In `@src/main/parsers/copilot-cli-output-parser.ts`:
- Around line 283-289: The default branch in transformMessage() treats errors by
checking msg.error only, causing session.error payloads (which hold the message
at data.message) to be rendered as "Unknown error"; update transformMessage() to
explicitly detect msg.type === 'session.error' (or
msg.type?.includes('session.error')) and pass msg.data (or msg.data.message)
into the same extractErrorText() logic (or reuse detectErrorFromParsed()'s
extraction) so the parsed event's text matches detectErrorFromParsed(); ensure
the returned object for error events sets type: 'error', text:
extractErrorText(...) and raw: msg just like other error paths.

In `@src/main/parsers/error-patterns.ts`:
- Around line 953-955: The existing error-pattern entry with pattern
/copilot.*command not found/i should be broadened to also match reversed
phrasing like "command not found: copilot"; update the regex for that entry (the
object with keys pattern/message/recoverable) to a case-insensitive pattern that
matches both "copilot ... command not found" and "command not found ... copilot"
(for example by alternating the two forms or using a non-capturing group),
leaving the message and recoverable fields unchanged.

In `@src/main/process-manager/handlers/StderrHandler.ts`:
- Around line 110-119: The current early return in StderrHandler.ts for toolType
=== 'copilot-cli' silently swallows all stderr; change this so we only suppress
when the stderr is empty or matches known benign patterns (use the existing
outputParser or a small benignRegex list to detect those cases) and otherwise do
not return—log the stderr as warning/error with sessionId and cleanedStderr and
allow normal error handling to continue (so upstream detection or exceptions can
surface to Sentry). Locate the conditional that references toolType,
outputParser, logger and cleanedStderr and replace the unconditional return with
a guarded suppression check (isBenign via outputParser or regex), otherwise log
and proceed.

In `@src/main/process-manager/handlers/StdoutHandler.ts`:
- Around line 241-245: Change the suppression log in StdoutHandler.ts so it uses
a lower-severity level and stops including raw content previews: replace the
logger.info call inside the non-JSON suppression branch (the call that currently
passes sessionId, toolType: managedProcess.toolType, and linePreview:
line.substring(0, 80)) with a debug-level log (e.g., logger.debug) and remove
the linePreview field; if you need visibility, log a generic marker or a
rate-limited counter instead but do not emit any portion of the suppressed line.

In `@src/main/process-manager/spawners/ChildProcessSpawner.ts`:
- Around line 340-351: The isStreamJsonMode flag in ChildProcessSpawner should
not be set solely because getOutputParser returned a parser; remove the
unconditional `!!outputParser` check and instead detect streaming JSON only when
the parser explicitly indicates it supports streaming (e.g., check a property
like outputParser?.supportsStreaming or outputParser?.isStreamJsonMode) or when
command args/config require JSON; update the isStreamJsonMode expression to use
that explicit property (or add such a property to the parser interface if
missing) so non-JSON parser-enabled agents are not forced into stream-json
handling.

In `@src/main/storage/copilot-cli-session-storage.ts`:
- Around line 246-326: readSessionMessages and getSessionPath currently accept
any valid sessionId because sessions are stored globally; update both to
re-parse the session's workspace metadata (workspace.yaml or equivalent stored
alongside events.jsonl in the directory returned by
getCopilotSessionDir()/sessionId) and verify the stored cwd/project path matches
the provided _projectPath before returning data. In readSessionMessages
(function readSessionMessages) after validating isValidSessionId(sessionId) and
computing sessionDir/eventsPath, load and parse the workspace metadata (e.g.,
workspace.yaml) and if its cwd does not equal the incoming _projectPath then
return { messages: [], total: 0, hasMore: false }; similarly in getSessionPath
(function getSessionPath) perform the same workspace metadata check and return
null if the session's cwd differs. Keep the existing SSH warning behavior
unchanged.

---

Nitpick comments:
In `@src/__tests__/main/agents/detector.test.ts`:
- Around line 325-326: Replace the hardcoded agent count assertions in the
detector tests with a dynamic check based on the canonical AGENT_IDS list:
instead of expect(agents.length).toBe(9) use
expect(agents.length).toBe(AGENT_IDS.length) (or equivalent), while retaining
explicit inclusion checks like expect(agents).toContain('copilot-cli'); locate
the assertions around the tests referencing agents (e.g., the block using the
agents variable and the toContain('copilot-cli') check) and update both
occurrences so the test no longer hardcodes numeric counts but derives the
expected length from AGENT_IDS.

In `@src/__tests__/main/parsers/index.test.ts`:
- Around line 52-57: Replace hard-coded literal counts in this test file with a
single shared constant (e.g., EXPECTED_PARSERS or EXPECTED_PARSER_COUNT) defined
near the top of the file and reference it in the tests that call
initializeOutputParsers() and getAllOutputParsers(); update the assertions in
the tests that currently use 5 (the one checking parsers.length and the other
occurrences around lines 62–67 and 76) to use that shared constant so
adding/removing parsers only requires updating one value and keep tests in sync.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: c9f4aa4b-9d17-49b9-bf38-e9af329b7fd7

📥 Commits

Reviewing files that changed from the base of the PR and between 3d52b3f and f7ab6d3.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (24)
  • .github/copilot-instructions.md
  • docs/Copilot-CLI-Phase2-Plan.md
  • src/__tests__/main/agents/detector.test.ts
  • src/__tests__/main/parsers/copilot-cli-output-parser.test.ts
  • src/__tests__/main/parsers/index.test.ts
  • src/__tests__/main/process-manager/spawners/ChildProcessSpawner.test.ts
  • src/main/agents/capabilities.ts
  • src/main/agents/definitions.ts
  • src/main/agents/detector.ts
  • src/main/ipc/handlers/process.ts
  • src/main/ipc/handlers/tabNaming.ts
  • src/main/parsers/copilot-cli-output-parser.ts
  • src/main/parsers/error-patterns.ts
  • src/main/parsers/index.ts
  • src/main/process-manager/handlers/StderrHandler.ts
  • src/main/process-manager/handlers/StdoutHandler.ts
  • src/main/process-manager/spawners/ChildProcessSpawner.ts
  • src/main/storage/copilot-cli-session-storage.ts
  • src/main/storage/index.ts
  • src/renderer/components/NewInstanceModal/types.ts
  • src/renderer/components/Wizard/screens/AgentSelectionScreen.tsx
  • src/shared/agentConstants.ts
  • src/shared/agentIds.ts
  • src/shared/agentMetadata.ts

Comment thread .github/copilot-instructions.md Outdated
Comment thread docs/Copilot-CLI-Phase2-Plan.md Outdated
Comment thread src/main/agents/detector.ts
Comment thread src/main/ipc/handlers/process.ts Outdated
Comment thread src/main/parsers/copilot-cli-output-parser.ts
Comment thread src/main/parsers/error-patterns.ts Outdated
Comment thread src/main/process-manager/handlers/StderrHandler.ts
Comment thread src/main/process-manager/handlers/StdoutHandler.ts Outdated
Comment thread src/main/process-manager/spawners/ChildProcessSpawner.ts Outdated
Comment thread src/main/storage/copilot-cli-session-storage.ts
@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Apr 12, 2026

Greptile Summary

This PR adds GitHub Copilot CLI as a fully integrated first-class agent across all 9 registry touchpoints (agent ID, definition, capabilities, metadata, context window, output parser, error patterns, session storage, wizard/modal tiles), and introduces a multi-model picker backed by the models.dev API. The integration is thorough and well-tested; prior P1 findings (singleton token-counter leak, cwd-less session bleed) were addressed in preceding commits.

One P1 remains: four // DEBUG:-tagged console.log calls were left in StdoutHandler.ts (lines 299–303, 321–323, 497–502, 511–514). They fire on every usage event for all agents, logging session IDs and token counts — these must be removed before merge.

Confidence Score: 4/5

Near-merge-ready; one debug console.log cleanup required before shipping to production

All prior P1 findings (singleton token leak, cwd-less session bleed in listSessions) have been resolved. The integration is complete across all 9 touchpoints and the test suite is comprehensive (665-line parser tests). The single remaining P1 is the four debug console.log statements in StdoutHandler.ts that fire in production for every agent on every usage event — a mechanical cleanup required before merge.

src/main/process-manager/handlers/StdoutHandler.ts — four debug console.log statements must be removed

Important Files Changed

Filename Overview
src/main/parsers/copilot-cli-output-parser.ts New JSONL parser for Copilot CLI; handles assistant.message_delta, assistant.message (tool-only / text+tool / text-only), result, and error events; singleton token-counter leak addressed by accumulating per-session on ManagedProcess
src/main/storage/copilot-cli-session-storage.ts Session storage reading ~/.copilot/session-state/; listSessions correctly excludes cwd-less sessions, but readSessionMessages and getSessionPath still allow cwd-less sessions through, inconsistent with the fail-closed intent
src/main/process-manager/handlers/StdoutHandler.ts Four debug console.log statements marked // DEBUG: left in the hot usage-event path; fire for all agents on every turn, logging session IDs and token counts; must be removed before merge
src/main/agents/definitions.ts Copilot CLI agent definition added with correct batchModeArgs, jsonOutputArgs, resumeArgs, modelArgs, sendPromptViaStdinRaw, noPromptSeparator; consistent with existing agent patterns
src/main/agents/capabilities.ts Copilot CLI capabilities well-documented; Phase 1/2 split clearly explained; supportsResultMessages correctly enabled; usesJsonLineOutput set true
src/main/agents/detector.ts runModelDiscovery copilot-cli case fetches from models.dev with 3s AbortController timeout and falls back to ~/.copilot/config.json; approach is consistent with other agents
src/main/process-manager/handlers/StderrHandler.ts copilot-cli stderr suppression narrowed to known benign patterns (MCP startup, stdin prompt banner); non-benign stderr logged as warning and allowed through; OpenCode/Factory Droid unaffected
src/main/parsers/error-patterns.ts Comprehensive copilot-cli error patterns added for auth, rate-limit, network, token-exhaustion, model-availability, session-not-found; recoverable flags set appropriately

Sequence Diagram

sequenceDiagram
    participant UI as Renderer UI
    participant IPC as IPC (agents:getModels)
    participant Det as AgentDetector
    participant API as models.dev API
    participant FS as ~/.copilot/config.json
    participant Spawn as ChildProcessSpawner
    participant Parser as CopilotCliOutputParser
    participant Store as CopilotCliSessionStorage

    UI->>IPC: getModels('copilot-cli')
    IPC->>Det: discoverModels('copilot-cli')
    Det->>API: GET /api.json (3s timeout)
    API-->>Det: github-copilot provider models
    Det->>FS: readFileSync config.json (prepend current model)
    Det-->>UI: sorted model list

    UI->>Spawn: spawn(copilot --output-format json --allow-all)
    Note over Spawn: isStreamJsonMode=true (--output-format json)
    Spawn->>Spawn: sendPromptViaStdinRaw → write prompt to stdin
    Spawn-->>Parser: JSONL lines via stdout

    Parser-->>Parser: assistant.message_delta → text (isPartial)
    Parser-->>Parser: assistant.message (text only) → result
    Parser-->>Parser: result event → usage (+ inject copilotAccumulatedTokens)

    UI->>Store: listSessions(projectPath)
    Store->>FS: readdir ~/.copilot/session-state/
    Store->>FS: read workspace.yaml per UUID dir
    Note over Store: skip if meta.cwd absent or ≠ projectPath
    Store-->>UI: AgentSessionInfo[]
Loading

Reviews (3): Last reviewed commit: "fix: detect --output-format json as stre..." | Re-trigger Greptile

Comment thread src/main/parsers/copilot-cli-output-parser.ts Outdated
Comment thread src/main/storage/copilot-cli-session-storage.ts Outdated
Comment thread src/main/parsers/copilot-cli-output-parser.ts
noreply and others added 2 commits April 12, 2026 10:44
Inline fixes:
- copilot-instructions.md: point Themes to src/shared/themes.ts (canonical)
- Phase2 plan: use stdin transport (echo | copilot) to match integration
- detector.ts: add AbortController with 3s timeout on models.dev fetch
- process.ts: prefer per-request sendPromptViaStdinRaw over agent default
- copilot-cli-output-parser.ts: handle session.error data.message payload
- error-patterns.ts: broaden 'command not found' regex to match both orderings
- StderrHandler.ts: replace blanket suppression with benign-pattern matching
- StdoutHandler.ts: downgrade suppression log to debug, remove content preview
- ChildProcessSpawner.ts: remove !!outputParser from isStreamJsonMode check
- copilot-cli-session-storage.ts: verify projectPath in readSessionMessages
  and getSessionPath against workspace.yaml cwd

Nitpicks:
- detector.test.ts: use AGENT_IDS.length instead of hardcoded 9
- parsers/index.test.ts: extract EXPECTED_PARSER_COUNT constant

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ession filter

P1: CopilotCliOutputParser.accumulatedOutputTokens is instance state on a
singleton. If a session is killed or errors before emitting a result event,
the residual count inflates usage stats for the next session. Fix: add
resetState() as an optional method on AgentOutputParser interface, implement
it in CopilotCliOutputParser, and call it from ExitHandler on every process
exit.

P2: listSessions skipped the project-path filter when workspace.yaml had no
cwd field, causing workspace-less sessions to appear in every project's
session browser. Fix: skip sessions without a cwd instead of including them.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@scriptease
Copy link
Copy Markdown
Author

@greptile please review

Copy link
Copy Markdown

@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 (1)
src/main/storage/copilot-cli-session-storage.ts (1)

264-278: ⚠️ Potential issue | 🟠 Major

Fail closed when the session’s workspace metadata can’t be verified.

Lines 277-278 and 358-360 reopen access whenever workspace.yaml is missing or unreadable. Because Copilot stores sessions globally, that turns a known UUID from another repo back into a readable/openable session. Return an empty result / null on metadata read or parse failure when projectPath is provided.

Also applies to: 345-360

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

In `@src/main/storage/copilot-cli-session-storage.ts` around lines 264 - 278, The
catch that currently swallows errors when reading/parsing workspace.yaml should
fail closed when a projectPath is provided: inside the block that verifies
"session belongs to the requested project" (the code that builds workspacePath,
reads via fs.readFile and calls parseWorkspaceYaml), replace the empty catch
handler with logic that returns an empty result (or null) when projectPath is
truthy so unreadable/missing workspace.yaml prevents access; apply the same
change to the duplicate verification block later (the other catch around
parseWorkspaceYaml at lines ~345-360) so both checks return empty/null on
read/parse failure instead of allowing access.
🧹 Nitpick comments (1)
src/__tests__/main/agents/detector.test.ts (1)

973-974: Keep this assertion derived from AGENT_IDS.

This test hardcodes 9 right after the suite was updated to use AGENT_IDS.length, so the next agent addition will break it again. Reuse the shared constant here too.

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

In `@src/__tests__/main/agents/detector.test.ts` around lines 973 - 974, The test
assertion currently hardcodes the expected agent count as 9; change it to use
the shared AGENT_IDS constant so it stays correct when agents are added (replace
expect(result.length).toBe(9) with
expect(result.length).toBe(AGENT_IDS.length)), ensuring AGENT_IDS is imported or
referenced in the test scope and leaving the rest of the test (e.g., the result
variable and surrounding assertions) unchanged.
🤖 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/Copilot-CLI-Phase2-Plan.md`:
- Around line 49-55: Update the checklist to match the actual BaseSessionStorage
contract: remove mentions of implementing searchSessions() and getGlobalStats(),
and instead require implementing getSessionPath(projectPath, sessionId),
deleteMessagePair(projectPath, sessionId, messageId) and
getSearchableMessages(projectPath, sessionId) in addition to
listSessions(projectPath, options) and readSessionMessages(projectPath,
sessionId, options); keep the step to register the concrete storage in
initializeSessionStorages() so the new implementation (class extending
BaseSessionStorage) is discoverable.

In `@src/main/storage/copilot-cli-session-storage.ts`:
- Around line 152-169: The code currently lowercases resolved paths (e.g.,
normalizedProjectPath and normalizedCwd) which causes incorrect matches on
case-sensitive platforms; change comparisons to use path.resolve(...) verbatim
and perform case-folding only when running on Windows (process.platform ===
'win32') so Windows uses .toLowerCase() for both sides while other platforms
compare the exact resolved strings; apply the same Windows-only case-insensitive
logic to the other path comparisons noted (the checks around the symbols used at
the other locations).

---

Duplicate comments:
In `@src/main/storage/copilot-cli-session-storage.ts`:
- Around line 264-278: The catch that currently swallows errors when
reading/parsing workspace.yaml should fail closed when a projectPath is
provided: inside the block that verifies "session belongs to the requested
project" (the code that builds workspacePath, reads via fs.readFile and calls
parseWorkspaceYaml), replace the empty catch handler with logic that returns an
empty result (or null) when projectPath is truthy so unreadable/missing
workspace.yaml prevents access; apply the same change to the duplicate
verification block later (the other catch around parseWorkspaceYaml at lines
~345-360) so both checks return empty/null on read/parse failure instead of
allowing access.

---

Nitpick comments:
In `@src/__tests__/main/agents/detector.test.ts`:
- Around line 973-974: The test assertion currently hardcodes the expected agent
count as 9; change it to use the shared AGENT_IDS constant so it stays correct
when agents are added (replace expect(result.length).toBe(9) with
expect(result.length).toBe(AGENT_IDS.length)), ensuring AGENT_IDS is imported or
referenced in the test scope and leaving the rest of the test (e.g., the result
variable and surrounding assertions) unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 85d97464-e97b-4e2e-ba82-88aeea1b2a99

📥 Commits

Reviewing files that changed from the base of the PR and between f7ab6d3 and cc3d2f8.

📒 Files selected for processing (12)
  • .github/copilot-instructions.md
  • docs/Copilot-CLI-Phase2-Plan.md
  • src/__tests__/main/agents/detector.test.ts
  • src/__tests__/main/parsers/index.test.ts
  • src/main/agents/detector.ts
  • src/main/ipc/handlers/process.ts
  • src/main/parsers/copilot-cli-output-parser.ts
  • src/main/parsers/error-patterns.ts
  • src/main/process-manager/handlers/StderrHandler.ts
  • src/main/process-manager/handlers/StdoutHandler.ts
  • src/main/process-manager/spawners/ChildProcessSpawner.ts
  • src/main/storage/copilot-cli-session-storage.ts
✅ Files skipped from review due to trivial changes (3)
  • src/tests/main/parsers/index.test.ts
  • .github/copilot-instructions.md
  • src/main/parsers/copilot-cli-output-parser.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/main/agents/detector.ts
  • src/main/process-manager/handlers/StderrHandler.ts

Comment thread docs/Copilot-CLI-Phase2-Plan.md Outdated
Comment thread src/main/storage/copilot-cli-session-storage.ts Outdated
…ail-closed storage

- Phase2 plan: update BaseSessionStorage method checklist to match actual
  contract (getSessionPath, deleteMessagePair, getSearchableMessages instead
  of searchSessions/getGlobalStats)
- copilot-cli-session-storage: case-fold paths only on Windows
  (process.platform === 'win32') to fix incorrect matches on case-sensitive
  filesystems; change catch handlers to fail closed (return empty/null) when
  workspace.yaml is unreadable instead of allowing access
- detector.test.ts: replace second hardcoded agent count with AGENT_IDS.length

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@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

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

153-154: ⚠️ Potential issue | 🟠 Major

Make path comparison case-insensitive on Windows only.

Line 153/171/274/356 lowercases both paths on every platform. That can match the wrong project on
case-sensitive filesystems.

Proposed fix
+function normalizePathForCompare(p: string): string {
+	const resolved = path.resolve(p);
+	return process.platform === 'win32' ? resolved.toLowerCase() : resolved;
+}
+
-const normalizedProjectPath = path.resolve(projectPath).toLowerCase();
+const normalizedProjectPath = normalizePathForCompare(projectPath);

-const normalizedCwd = path.resolve(meta.cwd).toLowerCase();
+const normalizedCwd = normalizePathForCompare(meta.cwd);

-if (meta.cwd && path.resolve(meta.cwd).toLowerCase() !== path.resolve(projectPath).toLowerCase()) {
+if (meta.cwd && normalizePathForCompare(meta.cwd) !== normalizePathForCompare(projectPath)) {
 	return { messages: [], total: 0, hasMore: false };
}

-if (meta.cwd && path.resolve(meta.cwd).toLowerCase() !== path.resolve(projectPath).toLowerCase()) {
+if (meta.cwd && normalizePathForCompare(meta.cwd) !== normalizePathForCompare(projectPath)) {
 	return null;
}

Also applies to: 171-173, 273-275, 355-357

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

In `@src/main/storage/copilot-cli-session-storage.ts` around lines 153 - 154,
Current code unconditionally lowercases resolved paths (e.g.,
normalizedProjectPath and normalizedExistingProjectPath) causing incorrect
matches on case-sensitive filesystems; change it to only normalize case on
Windows by checking process.platform === 'win32' (resolve paths with
path.resolve(...) first, then call toLowerCase() only when platform is 'win32')
and apply the same conditional behavior to all places where you currently
lowercase both paths before comparing (the variables used in project session
lookup and removal).

278-280: ⚠️ Potential issue | 🟠 Major

Don’t fail open when project ownership cannot be verified.

Line 278 and Line 360 currently allow session access if workspace.yaml can’t be read. That still
permits cross-project reads when UUID is known.

Proposed fix
-			} catch {
-				// If workspace.yaml can't be read, allow access (session may be incomplete)
-			}
+			} catch {
+				return { messages: [], total: 0, hasMore: false };
+			}
...
-			} catch {
-				// If workspace.yaml can't be read, allow access
-			}
+			} catch {
+				return null;
+			}

Also applies to: 360-362

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

In `@src/main/storage/copilot-cli-session-storage.ts` around lines 278 - 280, The
current catch blocks that swallow errors when reading workspace.yaml (the
try/catch around the ownership verification logic in
copilot-cli-session-storage.ts) "fail open" and allow session access if the file
can’t be read; change those catch blocks to deny access instead: when
reading/parsing workspace.yaml fails, log the error and return a negative auth
result (e.g., return false or throw an authorization error) from the
ownership-check function rather than allowing access. Apply the same fix to both
occurrences (the catch at the shown block and the similar catch around lines
~360-362) so failed reads consistently reject the session.
🤖 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/parsers/copilot-cli-output-parser.ts`:
- Around line 103-112: The parser's mutable field accumulatedOutputTokens and
its resetState() make token counts global and unsafe for concurrent Copilot
sessions; move token accounting out of the singleton parser and instead track
output tokens per-session (e.g., add a token counter on the session/process
abstraction such as ManagedProcess or accept a sessionId/context in parse calls
and key counts by that id), remove or deprecate accumulatedOutputTokens from the
parser, update all references (including places noted around lines ~187 and
~283) to increment/decrement the per-session counter rather than the parser
field, and ensure resetState() clears only parser-local non-session state or
delegates per-session resets to the process/session manager.

In `@src/main/process-manager/handlers/ExitHandler.ts`:
- Around line 201-205: The current ExitHandler calls outputParser.resetState()
unconditionally which can clear shared parser state still used by sibling
sessions; instead, change the logic in ExitHandler so you only call
outputParser.resetState() when no other active sessions are referencing that
parser (e.g., ask a SessionManager/ParserRegistry whether this is the last
session using that parser, or maintain a reference count on the parser and
decrement it here and reset only when count reaches zero). Specifically, update
the exit flow around outputParser.resetState to query
SessionManager.isLastSessionForParser(outputParser) or use
outputParser.decrementRefCount() and call resetState() only when it returns 0,
leaving shared parser instances intact while siblings remain active.

In `@src/main/storage/copilot-cli-session-storage.ts`:
- Around line 176-177: The code uses meta.id directly for sessionId which can be
malformed and later break UUID checks in readSessionMessages/getSessionPath;
update the block around sessionId (where meta.id || entry.name is assigned) to
validate meta.id (e.g., using an existing UUID validator or a small isValidUuid
helper), prefer a valid meta.id, fall back to entry.name only if it's valid, and
if neither is a valid identifier skip adding the session metadata (and
optionally log/debug a warning); ensure readSessionMessages and getSessionPath
will only see validated IDs.

---

Duplicate comments:
In `@src/main/storage/copilot-cli-session-storage.ts`:
- Around line 153-154: Current code unconditionally lowercases resolved paths
(e.g., normalizedProjectPath and normalizedExistingProjectPath) causing
incorrect matches on case-sensitive filesystems; change it to only normalize
case on Windows by checking process.platform === 'win32' (resolve paths with
path.resolve(...) first, then call toLowerCase() only when platform is 'win32')
and apply the same conditional behavior to all places where you currently
lowercase both paths before comparing (the variables used in project session
lookup and removal).
- Around line 278-280: The current catch blocks that swallow errors when reading
workspace.yaml (the try/catch around the ownership verification logic in
copilot-cli-session-storage.ts) "fail open" and allow session access if the file
can’t be read; change those catch blocks to deny access instead: when
reading/parsing workspace.yaml fails, log the error and return a negative auth
result (e.g., return false or throw an authorization error) from the
ownership-check function rather than allowing access. Apply the same fix to both
occurrences (the catch at the shown block and the similar catch around lines
~360-362) so failed reads consistently reject the session.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 23c00f6f-ad1c-40ff-a350-78dc2a03d5ca

📥 Commits

Reviewing files that changed from the base of the PR and between cc3d2f8 and a0dba69.

📒 Files selected for processing (4)
  • src/main/parsers/agent-output-parser.ts
  • src/main/parsers/copilot-cli-output-parser.ts
  • src/main/process-manager/handlers/ExitHandler.ts
  • src/main/storage/copilot-cli-session-storage.ts

Comment thread src/main/parsers/copilot-cli-output-parser.ts Outdated
Comment thread src/main/process-manager/handlers/ExitHandler.ts Outdated
Comment thread src/main/storage/copilot-cli-session-storage.ts Outdated
…date Phase 2 doc

Address CI review findings:

1. Move accumulatedOutputTokens from singleton parser to per-session
   ManagedProcess.copilotAccumulatedTokens. Parser now reports per-event
   tokens via usage field on each assistant.message. StdoutHandler
   accumulates on ManagedProcess and injects the total into the result
   event. Removes resetState() from parser interface and ExitHandler.
   Fixes concurrent session token leak.

2. Validate meta.id with isValidSessionId() in listSessions before using
   it as sessionId. Skips sessions with malformed IDs.

3. Update Phase 2 doc to list actual BaseSessionStorage abstract methods
   (getSessionPath, deleteMessagePair, getSearchableMessages) instead of
   non-existent searchSessions/getGlobalStats.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

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

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

164-181: ⚠️ Potential issue | 🟡 Minor

Validate the chosen sessionId before exposing it.

Line 181 prefers meta.id blindly. That lets listSessions() return IDs that readSessionMessages() and getSessionPath() later reject, and stray non-UUID directories can still leak into the list. Prefer a valid meta.id, fall back to entry.name only if it is valid, otherwise skip the entry.

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

In `@src/main/storage/copilot-cli-session-storage.ts` around lines 164 - 181,
listSessions() currently trusts meta.id or entry.name blindly which can return
IDs that readSessionMessages()/getSessionPath() will later reject; validate the
chosen sessionId (e.g., ensure it's a UUID or the same format your other helpers
accept) before exposing it. In the code around parseWorkspaceYaml(),
normalizePath(), and the block that sets sessionId = meta.id || entry.name,
change logic to prefer meta.id only if it passes validation, otherwise use
entry.name only if entry.name itself is valid, and if neither is valid skip the
directory; ensure the same validation routine is used by readSessionMessages()
and getSessionPath() so listed IDs are always resolvable.
🤖 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/Copilot-CLI-Phase2-Plan.md`:
- Around line 36-56: Update the plan to stop re-proposing already-implemented
session storage and instead list only the remaining gaps: remove the "New:
CopilotCliSessionStorage" and register/registering items and instead note that
CopilotCliSessionStorage and BaseSessionStorage implementations exist; update
the matrix to mark session storage as complete; add concise TODOs for SSH remote
support and follow-up hardening (e.g., SSH-aware
getSessionPath/listSessions/readSessionMessages and deleteMessagePair behavior)
and mention that initializeSessionStorages() should be verified for registration
consistency and supportsSessionStorage flags in capabilities.ts.

In `@src/main/storage/copilot-cli-session-storage.ts`:
- Around line 279-288: The code currently only denies access when meta.cwd
exists and differs; change both places that read parseWorkspaceYaml (the block
using workspacePath, parseWorkspaceYaml, normalizePath and the similar block
around lines 364-373) to require meta.cwd to be present and equal to
projectPath: after parsing yaml, if (!meta.cwd || normalizePath(meta.cwd) !==
normalizePath(projectPath)) return the empty result; this ensures missing or
malformed cwd also fails closed before returning messages or paths.
- Around line 326-328: The pagination logic uses || which treats 0 and negative
values incorrectly; update the code that sets limit and offset (the variables
named limit and offset used to compute paginatedMessages via messages.slice) to
use nullish coalescing (??) and then clamp values: set limit = Math.max(0,
(options?.limit ?? totalMessages)) and offset = Math.max(0, (options?.offset ??
0)) (or equivalent) before slicing so a provided 0 is honored and negatives are
prevented.
- Around line 190-197: firstMessage is incorrectly seeded from summary which
hides the real first user prompt; change the logic so firstMessage is initially
empty/undefined (do not assign summary), iterate events and set firstMessage to
the first user.message content found (String(event.data.content).slice(0,200));
after the loop, if no user message was found then set firstMessage = summary as
a fallback; apply the identical change to the other similar block around the
second occurrence (the block that currently mirrors lines 220-226) so both
places use the same fallback behavior.
- Around line 105-124: The loadSessionEvents function currently swallows all
read errors and returns an empty array; update it to only treat a missing file
as recoverable by catching the error from fs.readFile, check if error.code ===
'ENOENT' and return [] in that case, but re-throw any other errors so they
surface to callers/Sentry; also capture/parses errors when JSON.parse fails
(keep skipping unparseable lines) but ensure the outer try/catch uses the caught
error variable to perform the ENOENT check and rethrow otherwise.

---

Duplicate comments:
In `@src/main/storage/copilot-cli-session-storage.ts`:
- Around line 164-181: listSessions() currently trusts meta.id or entry.name
blindly which can return IDs that readSessionMessages()/getSessionPath() will
later reject; validate the chosen sessionId (e.g., ensure it's a UUID or the
same format your other helpers accept) before exposing it. In the code around
parseWorkspaceYaml(), normalizePath(), and the block that sets sessionId =
meta.id || entry.name, change logic to prefer meta.id only if it passes
validation, otherwise use entry.name only if entry.name itself is valid, and if
neither is valid skip the directory; ensure the same validation routine is used
by readSessionMessages() and getSessionPath() so listed IDs are always
resolvable.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 27434b22-0c3f-4b68-bbb5-952c51a87c27

📥 Commits

Reviewing files that changed from the base of the PR and between a0dba69 and eb86fef.

📒 Files selected for processing (3)
  • docs/Copilot-CLI-Phase2-Plan.md
  • src/__tests__/main/agents/detector.test.ts
  • src/main/storage/copilot-cli-session-storage.ts

Comment thread docs/Copilot-CLI-Phase2-Plan.md Outdated
Comment thread src/main/storage/copilot-cli-session-storage.ts
Comment thread src/main/storage/copilot-cli-session-storage.ts
Comment thread src/main/storage/copilot-cli-session-storage.ts
Comment thread src/main/storage/copilot-cli-session-storage.ts Outdated
Copy link
Copy Markdown

@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 (2)
src/__tests__/main/parsers/copilot-cli-output-parser.test.ts (1)

415-438: Add regression coverage for session.error / data.message payloads.

The suite currently exercises error objects and strings, but not the parser’s other supported error shapes. A small case for session.error and one for { type: 'error', data: { message: ... } } would lock down the special-case extraction path that the parser now depends on.

Also applies to: 546-562

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

In `@src/__tests__/main/parsers/copilot-cli-output-parser.test.ts` around lines
415 - 438, Add two test cases to the 'error events' suite that cover the
parser's other supported error shapes: one that passes a JSON line where a
session object contains an error (e.g., { type: 'error', session: { error: {
message: '...' } } }) and one where the payload is { type: 'error', data: {
message: '...' } }; for each call parser.parseJsonLine(...) and assert
event?.type === 'error' and event?.text equals the message string to lock down
the session.error and data.message extraction paths in parseJsonLine.
src/main/parsers/copilot-cli-output-parser.ts (1)

238-247: Fall back to detailedContent before emitting blank tool output.

The local result shape includes both content and detailedContent, but only content is used here. If Copilot returns a completed tool payload with only detailedContent, the UI will show an empty tool result even though output was available.

Suggested fix
-				let output = result?.content || '';
+				let output = result?.content ?? result?.detailedContent ?? '';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/parsers/copilot-cli-output-parser.ts` around lines 238 - 247, The
parser currently only uses result.content and can emit an empty tool output when
only result.detailedContent is present; update the logic that initializes output
(from msg.data?.result) to prefer result.content but fall back to
result.detailedContent before defaulting to '' (i.e., use result.content ||
result.detailedContent || ''), then apply the existing MAX_TOOL_OUTPUT_LENGTH
truncation on that chosen string; references: msg.data?.result, result, output,
MAX_TOOL_OUTPUT_LENGTH.
🤖 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/parsers/copilot-cli-output-parser.ts`:
- Around line 375-379: The branch that detects structured errors only uses
obj.error, so payloads like {type: 'error', data: {message: '...'}} are missed;
update the error-handling branch in copilot-cli-output-parser (the block setting
parsedJson and errorText) to also consider obj.data?.message as a fallback for
extractErrorText (e.g., call extractErrorText on obj.error first and if falsy
call it on obj.data?.message or use a null-coalescing expression), then keep the
same parsedJson assignment and normalize 'Unknown error' to null so
detectErrorFromParsed() and transformMessage() see the message correctly.

---

Nitpick comments:
In `@src/__tests__/main/parsers/copilot-cli-output-parser.test.ts`:
- Around line 415-438: Add two test cases to the 'error events' suite that cover
the parser's other supported error shapes: one that passes a JSON line where a
session object contains an error (e.g., { type: 'error', session: { error: {
message: '...' } } }) and one where the payload is { type: 'error', data: {
message: '...' } }; for each call parser.parseJsonLine(...) and assert
event?.type === 'error' and event?.text equals the message string to lock down
the session.error and data.message extraction paths in parseJsonLine.

In `@src/main/parsers/copilot-cli-output-parser.ts`:
- Around line 238-247: The parser currently only uses result.content and can
emit an empty tool output when only result.detailedContent is present; update
the logic that initializes output (from msg.data?.result) to prefer
result.content but fall back to result.detailedContent before defaulting to ''
(i.e., use result.content || result.detailedContent || ''), then apply the
existing MAX_TOOL_OUTPUT_LENGTH truncation on that chosen string; references:
msg.data?.result, result, output, MAX_TOOL_OUTPUT_LENGTH.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 87db6243-96be-453c-a710-41878210c858

📥 Commits

Reviewing files that changed from the base of the PR and between eb86fef and d43760e.

📒 Files selected for processing (5)
  • src/__tests__/main/parsers/copilot-cli-output-parser.test.ts
  • src/main/parsers/copilot-cli-output-parser.ts
  • src/main/process-manager/handlers/StdoutHandler.ts
  • src/main/process-manager/types.ts
  • src/main/storage/copilot-cli-session-storage.ts
✅ Files skipped from review due to trivial changes (1)
  • src/main/process-manager/types.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/main/process-manager/handlers/StdoutHandler.ts

Comment thread src/main/parsers/copilot-cli-output-parser.ts
@scriptease
Copy link
Copy Markdown
Author

@greptile what do you think

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Apr 12, 2026

Solid PR overall — comprehensive, well-structured, and the iterative review cycle clearly improved it. Here's my read:

Strengths:

  • Full integration across all 9 registry touchpoints is the right approach — no half-baked agent registrations
  • 665-line test suite for the output parser is good coverage for a complex JSONL protocol
  • Proactive error handling for the Gemini limitation rather than silently failing
  • The models.dev fallback → ~/.copilot/config.json graceful degradation is clean

What the review cycle caught and fixed:

  • The singleton accumulatedOutputTokens leaking across sessions was a real bug — good catch, good fix
  • resetState() wiping shared parser state while sibling sessions were active was subtle and dangerous
  • Blanket stderr suppression was a blast-radius issue; narrowing to known noise patterns is the right call
  • isStreamJsonMode being inferred from parser presence alone was too broad

Remaining concerns I'd flag:

  1. loadSessionEvents still catches all errors and returns [] — ENOENT should be silent, but permission/I/O errors should bubble up to Sentry per your coding guidelines. That CodeRabbit thread is unresolved.
  2. The docs/Copilot-CLI-Phase2-Plan.md still has stale sections re-proposing work that's already landed in this PR (session storage registration). Not a blocker but will confuse whoever picks up Phase 2.
  3. The assistant.message with content + toolRequests being emitted as type: 'text' rather than type: 'result' — the unresolved comment on line 215 of the parser — could cause the final agent turn to be de-prioritized in the UI when tools were involved. Worth a deliberate decision either way.

Verdict: Mergeable if items 1 and 3 are addressed or explicitly deferred with a comment. The core integration is sound and the review cycle did its job.

Tip: You can customize Greptile's behavior for this repo with .greptile/rules and .greptile/config.

noreply and others added 3 commits April 12, 2026 12:16
…in Copilot CLI parser

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… session history

- Revert assistant.message with content+toolRequests back to type: text
  (not result) — these are mid-turn messages before tool execution, emitting
  result prematurely consumes the StdoutHandler result slot
- Replace custom slice(offset, offset+limit) pagination with
  BaseSessionStorage.applyMessagePagination() so session history loads from
  the end (most recent first), matching all other storage implementations

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… suppression

- Add sendPromptViaStdinRaw to GroomingProcessManager interface and spawn
  call so copilot-cli grooming sends prompts via stdin instead of CLI args
  (same class of bug previously fixed for tab naming)
- Narrow MCP stderr suppression from /mcp.*server/i (matches any MCP message)
  to /mcp server .*(start|connect|ready|loaded|initializ)/i so only startup
  noise is suppressed and real MCP failures (timeouts, crashes) flow through

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@scriptease scriptease marked this pull request as draft April 12, 2026 10:47
@scriptease
Copy link
Copy Markdown
Author

Fixed regression

The isStreamJsonMode detection in ChildProcessSpawner.ts:344 checked for --format but '--output-format'.includes('--format') is false, so Copilot CLI's JSONL was buffered as batch output instead of parsed line-by-line.

On exit, JSON.parse() failed on multi-line JSONL → catch block emitted the raw buffer → raw JSON dumped into the chat UI.

Fixed by adding --output-format to the detection (a57dbee4), plus fixing getStdinFlags returning false instead of undefined which blocked sendPromptViaStdinRaw: true via false ?? true (dd46b45b).

@scriptease scriptease marked this pull request as ready for review April 12, 2026 10:59
@scriptease
Copy link
Copy Markdown
Author

@greptile one more

noreply and others added 2 commits April 12, 2026 13:07
… take effect

getStdinFlags returned false (not undefined) on macOS/Linux and for SSH
sessions. Since the IPC handler uses ?? to fall back to the agent definition
default, false blocked the fallback — copilot-cli sendPromptViaStdinRaw:true
was overridden to false, causing too many arguments when the prompt was
appended as a CLI arg instead of sent via stdin.

Now returns undefined for non-applicable platforms so ?? correctly falls
through to agent.sendPromptViaStdinRaw.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…arsing

isStreamJsonMode checked for --format but not --output-format. Since
String.includes is used, --output-format does not match --format as a
substring. Without stream-json mode, copilot-cli JSONL was accumulated
in a batch buffer and dumped as raw JSON on process exit instead of
being parsed line-by-line through the output parser.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@pedramamini
Copy link
Copy Markdown
Collaborator

Hey @scriptease — thanks for this PR. Rather than merging any one of the three concurrent Copilot-CLI PRs in isolation, we rolled a combined integration into a Copilot-RC branch that takes #566 as the base (images, SSH-remote storage, hidden-progress UI, slash commands, Windows path probes, read-only enforcement, group chat moderation) and cherry-picks your models.dev multi-model picker on top — it was the standout feature of this PR and is now wired into the shared ModelTextInput dropdown so it benefits from the same filterable UX as the other agents. Refactored your fetch logic into two helpers (readCopilotConfiguredModel, fetchCopilotModelsFromApi) with a 3s abort timeout.

Agent id/display is copilot-cli / Copilot-CLI. Shipping in v0.16.12-RC shortly. Closing this as superseded by the combined merge; you're credited as a co-author on the merge commit. Big thanks for the model picker — really nice contribution.

@pedramamini
Copy link
Copy Markdown
Collaborator

Superseded by Copilot-RC combined merge. Shipping in v0.16.12-RC.

pedramamini added a commit that referenced this pull request Apr 23, 2026
Combines work from three PRs:
- #566 (nolanmclark) — broad Copilot integration: images, SSH-remote
  session storage, hidden-progress UI, slash commands, Windows path
  probes, read-only CLI enforcement, group chat moderation, usage stats
- #701 (dwizzzle) — original copilot-cli agent implementation and
  JSONL parser foundation (absorbed via #805's lineage)
- #805 (scriptease) — models.dev multi-model picker (cherry-picked as
  per-process helpers), parser refinements

Drops Aider from new-agent flows; removes Qwen3 wizard tile.
Shipping in v0.16.12-RC.

Co-authored-by: Nolan Clark <nolanmclark@gmail.com>
Co-authored-by: David Weston <david@dwizzzle.com>
Co-authored-by: Florian Agsteiner <florian.agsteiner@caperwhite.com>
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.

6 participants