Skip to content

feat: add Pi Coding Agent plugin#344

Merged
subsy merged 6 commits intosubsy:mainfrom
oneilltomhq:feat/add-pi-agent-plugin
Mar 13, 2026
Merged

feat: add Pi Coding Agent plugin#344
subsy merged 6 commits intosubsy:mainfrom
oneilltomhq:feat/add-pi-agent-plugin

Conversation

@oneilltomhq
Copy link
Contributor

@oneilltomhq oneilltomhq commented Feb 26, 2026

Overview

Adds a new agent plugin for Pi Coding Agent, a minimal, extensible terminal coding agent.

Changes

  • New agent plugin at src/plugins/agents/builtin/pi.ts
  • Plugin registration in index.ts and skill-installer.ts
  • Unit tests with 44 test cases
  • Documentation at website/content/docs/plugins/agents/pi.mdx

Features

  • Non-interactive mode via --print
  • JSONL output for subagent tracing
  • Model selection (--model)
  • Thinking level config (--thinking)
  • File context via @file syntax
  • Prompt via stdin (Windows-safe)

Checklist

  • Test coverage >50%
  • Typecheck passes
  • Lint passes
  • Documentation included
  • ABOUTME header in source files

Summary by CodeRabbit

  • New Features

    • Added Pi coding agent integration with multi-model support, streaming JSONL output, interruptible runs, configurable "thinking" levels, subagent tracing and a Pi agent factory/plugin exposed in the public API.
  • Documentation

    • Added comprehensive Pi agent docs: installation, configuration, usage, options, examples and troubleshooting.
  • Tests

    • Added extensive Pi agent test suite covering metadata, initialization, argument/stdin handling, sandbox requirements and output parsing.
  • Chores

    • Added Pi to agent ID mapping for skill installation.

@vercel
Copy link

vercel bot commented Feb 26, 2026

@oneilltomhq is attempting to deploy a commit to the plgeek Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 26, 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

Adds a new Pi coding agent plugin: implementation and factory, registration and installer mapping, tests, and documentation; includes binary discovery and version probing, non‑interactive JSONL execution and parsing, model/thinking configuration, setup prompts, and sandbox requirements.

Changes

Cohort / File(s) Summary
Plugin Registration
src/plugins/agents/builtin/index.ts
Import createPiAgent, call registry.registerBuiltin(createPiAgent) in registerBuiltinAgents, and re-export createPiAgent and PiAgentPlugin.
Pi Agent Implementation
src/plugins/agents/builtin/pi.ts
New PiAgentPlugin class and createPiAgent factory (default export): config/init, binary discovery and 15s version probe, sandbox hints, setup questions, arg/stdin construction, JSONL stdout parsing into display events, execute/parse flow.
Agent Configuration
src/setup/skill-installer.ts
Add pi: 'pi' to AGENT_ID_MAP for skill-installer agent resolution.
Tests
tests/plugins/pi-agent.test.ts
New comprehensive test suite covering metadata, init paths, validation, setup questions, arg building, stdin handling, and sandbox requirements; includes TestablePiPlugin helper and lifecycle cleanup.
Documentation
website/content/docs/plugins/agents/pi.mdx
New MDX docs: prerequisites, usage examples, config (shorthand/full TOML), modes/models/thinking, JSONL behaviour, subagent tracing, troubleshooting and examples.

Sequence Diagram(s)

sequenceDiagram
  actor User
  participant Registry as "Agent Registry"
  participant PiAgent as "PiAgentPlugin"
  participant PiCLI as "pi (binary)"
  participant Parser as "JSONL Parser"

  User->>Registry: select/run Pi agent
  Registry->>PiAgent: instantiate with config (mode, model, thinking, timeout)
  PiAgent->>PiCLI: discover binary / probe --version
  PiAgent->>PiCLI: run with args (stdin prompt, --print, --mode=json, ...)
  PiCLI-->>PiAgent: stream JSONL stdout
  PiAgent->>Parser: pass JSONL lines
  Parser-->>PiAgent: AgentDisplayEvent(s)
  PiAgent-->>User: emit display events / results
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 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 'feat: add Pi Coding Agent plugin' directly and clearly describes the main change—integrating a new Pi Coding Agent plugin into the codebase.
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
  • 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.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (3)
tests/plugins/pi-agent.test.ts (1)

227-229: Consider adding isReady() check before dispose() in nested afterEach.

The outer afterEach (lines 43-51) defensively checks isReady() before calling dispose(), but this nested afterEach does not. Since some tests in this describe block call dispose() mid-test (e.g., lines 243, 261, 276), the afterEach could attempt to dispose an already-disposed plugin. Consider applying the same defensive pattern for consistency.

♻️ Suggested fix
     afterEach(async () => {
+      try {
+        if (await testablePlugin.isReady()) {
-      await testablePlugin.dispose();
+          await testablePlugin.dispose();
+        }
+      } catch {
+        // Ignore errors from already-disposed plugin
+      }
     });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/plugins/pi-agent.test.ts` around lines 227 - 229, The nested afterEach
should defensively check the plugin's readiness before disposing to avoid
double-dispose errors: wrap the call to testablePlugin.dispose() in an isReady()
check (use testablePlugin.isReady()) inside the nested afterEach so it only
calls testablePlugin.dispose() when the plugin is still ready/active, mirroring
the outer afterEach behavior.
website/content/docs/plugins/agents/pi.mdx (1)

1-4: Consider updating AGENTS.md.

Based on learnings, agent implementations should be documented in AGENTS.md with clear descriptions of purpose, capabilities, and usage. Ensure AGENTS.md is also updated with the Pi agent details to maintain it as the single source of truth for agent documentation.

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

In `@website/content/docs/plugins/agents/pi.mdx` around lines 1 - 4, Add the Pi
Agent to AGENTS.md as the canonical entry: create a section titled "Pi Agent"
that mirrors the content in the pi.mdx page (purpose, capabilities, usage
instructions, configuration options, and example commands/flags), include a
short one-line summary and a "See also" link to the Pi Agent docs
(website/content/docs/plugins/agents/pi.mdx), and ensure the format and headings
match other agent entries in AGENTS.md so it appears consistently in the single
source of truth for agents.
src/plugins/agents/builtin/pi.ts (1)

374-409: Potential issue: original onStdout callback is replaced, not wrapped.

When isJsonMode is true and callbacks are provided, the custom onStdout handler parses JSONL and calls options.onStdout with the processed output. However, if a consumer expects raw stdout data (e.g., for logging), they will only receive the formatted output, not the raw JSONL lines.

This may be intentional for cleaner display, but it differs from some other agent plugins that preserve raw output. If raw output is needed for debugging, consider either:

  1. Documenting this behaviour clearly, or
  2. Providing a separate callback for raw JSONL data

The current implementation is functional, so this is just a note for awareness.

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

In `@src/plugins/agents/builtin/pi.ts` around lines 374 - 409, The current
isJsonMode onStdout handler replaces raw stdout delivery; update the handler so
it preserves raw data by invoking a new/optional callback if present (e.g.,
options.onRawStdout or options.onStdoutRaw) with the original data before doing
JSONL parsing and event processing, while leaving the existing behavior that
calls options.onJsonlMessage, options.onStdoutSegments and options.onStdout
(which receives processed output) intact; modify the onStdout branch around
isJsonMode and the parsing block to first call options.onRawStdout(data) when
present, then continue parsing with parsePiOutputToEvents, JSON.parse lines,
processAgentEventsToSegments and processAgentEvents as now.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@website/content/docs/plugins/agents/pi.mdx`:
- Around line 241-243: The docs show two different install commands (bunx pi-cli
vs npm install -g `@mariozechner/pi-coding-agent`); verify which package name is
correct and make the instructions consistent by updating the troubleshooting
snippet and the prerequisites snippet to use the same package and installer
(e.g., replace `bunx pi-cli` with the canonical install command or replace the
npm global install with the bunx invocation), and ensure any references to the
package name (`pi-cli`, `@mariozechner/pi-coding-agent`) and installer (`bunx`,
`npm install -g`) are identical across the file.

---

Nitpick comments:
In `@src/plugins/agents/builtin/pi.ts`:
- Around line 374-409: The current isJsonMode onStdout handler replaces raw
stdout delivery; update the handler so it preserves raw data by invoking a
new/optional callback if present (e.g., options.onRawStdout or
options.onStdoutRaw) with the original data before doing JSONL parsing and event
processing, while leaving the existing behavior that calls
options.onJsonlMessage, options.onStdoutSegments and options.onStdout (which
receives processed output) intact; modify the onStdout branch around isJsonMode
and the parsing block to first call options.onRawStdout(data) when present, then
continue parsing with parsePiOutputToEvents, JSON.parse lines,
processAgentEventsToSegments and processAgentEvents as now.

In `@tests/plugins/pi-agent.test.ts`:
- Around line 227-229: The nested afterEach should defensively check the
plugin's readiness before disposing to avoid double-dispose errors: wrap the
call to testablePlugin.dispose() in an isReady() check (use
testablePlugin.isReady()) inside the nested afterEach so it only calls
testablePlugin.dispose() when the plugin is still ready/active, mirroring the
outer afterEach behavior.

In `@website/content/docs/plugins/agents/pi.mdx`:
- Around line 1-4: Add the Pi Agent to AGENTS.md as the canonical entry: create
a section titled "Pi Agent" that mirrors the content in the pi.mdx page
(purpose, capabilities, usage instructions, configuration options, and example
commands/flags), include a short one-line summary and a "See also" link to the
Pi Agent docs (website/content/docs/plugins/agents/pi.mdx), and ensure the
format and headings match other agent entries in AGENTS.md so it appears
consistently in the single source of truth for agents.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4477972 and ab6fac8.

📒 Files selected for processing (5)
  • src/plugins/agents/builtin/index.ts
  • src/plugins/agents/builtin/pi.ts
  • src/setup/skill-installer.ts
  • tests/plugins/pi-agent.test.ts
  • website/content/docs/plugins/agents/pi.mdx

oneilltomhq added a commit to oneilltomhq/ralph-tui that referenced this pull request Feb 26, 2026
- Add defensive isReady() check in nested afterEach to avoid double-dispose
- Fix inconsistent install instructions in troubleshooting section
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
tests/plugins/pi-agent.test.ts (2)

242-256: Consider adding the mode: 'text' negative path for buildArgs.

You already verify JSON inclusion; adding the no-tracing text-mode case would better pin expected omission of --mode json.

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

In `@tests/plugins/pi-agent.test.ts` around lines 242 - 256, Add a negative test
that verifies buildArgs does not include "--mode json" for the text/no-tracing
path by creating a TestablePiPlugin, initializing it with mode: 'text' (use
TestablePiPlugin.initialize({ mode: 'text' }) after disposing any prior instance
like dispose()), then call testablePlugin.testBuildArgs('test prompt', ...) or
testBuildArgs('test prompt') and assert that the returned args do NOT contain
'--mode' and do NOT contain 'json'; reference the existing tests that call
testBuildArgs, TestablePiPlugin, initialize and dispose to mirror
setup/teardown.

326-328: Harden teardown in this block to reduce flaky hook failures.

This afterEach is less defensive than the buildArgs suite cleanup. Aligning both patterns will make the tests more resilient.

Proposed patch
     afterEach(async () => {
-      await testablePlugin.dispose();
+      try {
+        if (await testablePlugin.isReady()) {
+          await testablePlugin.dispose();
+        }
+      } catch {
+        // Ignore errors from already-disposed plugin
+      }
     });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/plugins/pi-agent.test.ts` around lines 326 - 328, The teardown
afterEach currently calls await testablePlugin.dispose() directly and should be
hardened like the buildArgs suite: guard against a missing plugin, await
disposal inside a try/catch to swallow and log non-fatal errors, and null out
the reference afterwards; update the afterEach to check if testablePlugin is
truthy, run await testablePlugin.dispose() inside a try/catch (e.g., catch and
optionally console.warn or rethrow only on unexpected errors), and then set
testablePlugin = undefined to ensure subsequent tests start clean.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@tests/plugins/pi-agent.test.ts`:
- Around line 242-256: Add a negative test that verifies buildArgs does not
include "--mode json" for the text/no-tracing path by creating a
TestablePiPlugin, initializing it with mode: 'text' (use
TestablePiPlugin.initialize({ mode: 'text' }) after disposing any prior instance
like dispose()), then call testablePlugin.testBuildArgs('test prompt', ...) or
testBuildArgs('test prompt') and assert that the returned args do NOT contain
'--mode' and do NOT contain 'json'; reference the existing tests that call
testBuildArgs, TestablePiPlugin, initialize and dispose to mirror
setup/teardown.
- Around line 326-328: The teardown afterEach currently calls await
testablePlugin.dispose() directly and should be hardened like the buildArgs
suite: guard against a missing plugin, await disposal inside a try/catch to
swallow and log non-fatal errors, and null out the reference afterwards; update
the afterEach to check if testablePlugin is truthy, run await
testablePlugin.dispose() inside a try/catch (e.g., catch and optionally
console.warn or rethrow only on unexpected errors), and then set testablePlugin
= undefined to ensure subsequent tests start clean.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ab6fac8 and d41647f.

📒 Files selected for processing (2)
  • tests/plugins/pi-agent.test.ts
  • website/content/docs/plugins/agents/pi.mdx
🚧 Files skipped from review as they are similar to previous changes (1)
  • website/content/docs/plugins/agents/pi.mdx

oneilltomhq added a commit to oneilltomhq/ralph-tui that referenced this pull request Feb 26, 2026
- Add defensive isReady() check in nested afterEach to avoid double-dispose
- Add negative test case for mode: text (verifies --mode json is omitted)
- Harden teardown in getStdinInput block for resilient test cleanup
- Fix inconsistent install instructions in troubleshooting section
@oneilltomhq oneilltomhq force-pushed the feat/add-pi-agent-plugin branch from d41647f to df0f3d3 Compare February 26, 2026 22:43
oneilltomhq added a commit to oneilltomhq/ralph-tui that referenced this pull request Feb 26, 2026
- Integrates Pi CLI with Ralph TUI for AI-assisted coding
- Supports JSONL output for subagent tracing and detailed tool call breakdowns
- Includes comprehensive test coverage for all plugin functionality
- Resolves PR subsy#344 review feedback (defensive test teardown, negative test cases, doc fixes)
@oneilltomhq oneilltomhq force-pushed the feat/add-pi-agent-plugin branch from df0f3d3 to 3afe6c8 Compare February 26, 2026 22:45
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/plugins/agents/builtin/pi.ts (1)

119-165: Clear runVersion timeout once the process settles

At Line 160, the timeout is never cleared on normal completion. This leaves a live timer and can call kill() after exit. Please guard single-settlement and clear the timer in both error and close paths.

Proposed hardening
  private runVersion(
    command: string
  ): Promise<{ success: boolean; version?: string; error?: string }> {
    return new Promise((resolve) => {
+      let settled = false;
+      const finish = (result: { success: boolean; version?: string; error?: string }) => {
+        if (settled) return;
+        settled = true;
+        clearTimeout(timeoutId);
+        resolve(result);
+      };

       const useShell = process.platform === 'win32';
       const proc = spawn(useShell ? quoteForWindowsShell(command) : command, ['--version'], {
         stdio: ['ignore', 'pipe', 'pipe'],
         shell: useShell,
       });
@@
       proc.on('error', (error) => {
-        resolve({
+        finish({
           success: false,
           error: `Failed to execute: ${error.message}`,
         });
       });

       proc.on('close', (code) => {
         if (code === 0) {
           const versionMatch = stdout.trim().match(/(\d+\.\d+\.\d+)/);
-          resolve({
+          finish({
             success: true,
             version: versionMatch?.[1],
           });
         } else {
-          resolve({
+          finish({
             success: false,
             error: stderr || `Exited with code ${code}`,
           });
         }
       });

-      setTimeout(() => {
+      const timeoutId = setTimeout(() => {
         proc.kill();
-        resolve({ success: false, error: 'Timeout waiting for --version' });
+        finish({ success: false, error: 'Timeout waiting for --version' });
       }, 15000);
     });
  }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/plugins/agents/builtin/pi.ts` around lines 119 - 165, The promise
returned by the version-check routine can resolve multiple times because the 15s
timeout is never cleared; update the logic in the function that spawns the
process (the block creating proc, its 'error' and 'close' handlers and the
setTimeout) to (1) store the timeout id in a variable, (2) use a single
"settled" boolean to ensure resolve() is called only once, (3)
clearTimeout(timeoutId) in both the proc.on('error', ...) and proc.on('close',
...) paths before resolving, and (4) before the timeout handler calls
proc.kill() and resolve(), check the same "settled" flag so it no-ops if the
process already finished. Reference the proc variable, the 'error' and 'close'
handlers, and the setTimeout/kill logic when making the changes.
🤖 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/plugins/agents/builtin/pi.ts`:
- Around line 374-409: The onStdout handler assumes chunks contain whole JSONL
lines and drops partial JSON objects; fix by adding an incremental buffer that
persists across onStdout invocations for the same execute call (e.g., a closure
or instance field like stdoutJsonlBuffer) and update the callback to prepend the
buffer to the incoming data, split by '\n', keep the last partial line in
stdoutJsonlBuffer (instead of parsing it), and only JSON.parse complete lines to
call options.onJsonlMessage; after that feed the reconstructed full data into
parsePiOutputToEvents/processAgentEventsToSegments/processAgentEvents so
segmented and formatted outputs are not lost.

---

Nitpick comments:
In `@src/plugins/agents/builtin/pi.ts`:
- Around line 119-165: The promise returned by the version-check routine can
resolve multiple times because the 15s timeout is never cleared; update the
logic in the function that spawns the process (the block creating proc, its
'error' and 'close' handlers and the setTimeout) to (1) store the timeout id in
a variable, (2) use a single "settled" boolean to ensure resolve() is called
only once, (3) clearTimeout(timeoutId) in both the proc.on('error', ...) and
proc.on('close', ...) paths before resolving, and (4) before the timeout handler
calls proc.kill() and resolve(), check the same "settled" flag so it no-ops if
the process already finished. Reference the proc variable, the 'error' and
'close' handlers, and the setTimeout/kill logic when making the changes.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between df0f3d3 and 3afe6c8.

📒 Files selected for processing (5)
  • src/plugins/agents/builtin/index.ts
  • src/plugins/agents/builtin/pi.ts
  • src/setup/skill-installer.ts
  • tests/plugins/pi-agent.test.ts
  • website/content/docs/plugins/agents/pi.mdx
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/setup/skill-installer.ts
  • website/content/docs/plugins/agents/pi.mdx

- Integrates Pi CLI with Ralph TUI for AI-assisted coding
- Supports JSONL output for subagent tracing and detailed tool call breakdowns
- Includes comprehensive test coverage for all plugin functionality
- Resolves PR subsy#344 review feedback (defensive test teardown, negative test cases, doc fixes)

Amp-Thread-ID: https://ampcode.com/threads/T-019ca340-073d-7198-94cf-51f1eb646f86
Co-authored-by: Amp <amp@ampcode.com>
@oneilltomhq oneilltomhq force-pushed the feat/add-pi-agent-plugin branch from 3afe6c8 to 4924206 Compare February 28, 2026 08:01
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (3)
tests/plugins/pi-agent.test.ts (2)

115-143: Make the “ignores invalid config” tests assert actual behaviour.

At Line 115 onwards, these tests only check isReady(), so they do not prove invalid values were ignored. Please assert observable outcomes (for example via buildArgs) so these cases protect against config-regression bugs.

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

In `@tests/plugins/pi-agent.test.ts` around lines 115 - 143, The tests currently
call plugin.initialize(...) and only assert plugin.isReady(), which doesn't
verify that invalid config values were ignored; update each test (the ones
calling plugin.initialize with invalid mode/thinking/model/timeout) to also call
the observable API plugin.buildArgs() (or the equivalent public accessor used in
this test suite) and assert its returned args reflect the expected
default/previous valid values (e.g., default mode, default thinking level,
original model string, default timeout) rather than the invalid input; keep the
existing isReady() assertion but add a second assertion comparing
plugin.buildArgs() to the expected args for each case so the test actually
guards against config regressions.

43-51: Consider extracting cleanup into one helper and avoid blanket error swallowing.

The repeated afterEach blocks are near-identical, and the broad catch can hide genuine disposal regressions. A shared disposeIfReady() helper would reduce duplication and keep failures visible.

Also applies to: 227-235, 336-344

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

In `@tests/plugins/pi-agent.test.ts` around lines 43 - 51, Multiple tests repeat
an afterEach that silently swallows disposal errors; extract a shared helper
(e.g., disposeIfReady) that calls plugin.isReady() and then plugin.dispose();
replace the duplicate afterEach blocks to call disposeIfReady(), and avoid a
blanket empty catch—either let errors surface or catch and rethrow/log them (use
processLogger or throw) so genuine disposal failures are visible while still
handling already-disposed state (check isReady before disposing).
src/plugins/agents/builtin/pi.ts (1)

375-385: Parse each JSONL line once and reuse the parsed object.

At Line 377 and Line 384, each line is decoded twice. A single parse path (reuse for both onJsonlMessage and event mapping) would simplify error handling and reduce per-chunk overhead.

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

In `@src/plugins/agents/builtin/pi.ts` around lines 375 - 385, Parse the JSON line
once into a variable (e.g., parsedJson) and reuse it for both callbacks and
event mapping: attempt JSON.parse(trimmed) once, if it succeeds call
options.onJsonlMessage(parsedJson) and then pass the parsed object into
this.parsePiJsonLine; if parse fails, fall back to calling
this.parsePiJsonLine(trimmed) so behavior remains unchanged. Update
parsePiJsonLine to accept either a string or Record<string, unknown> (or add an
overload) and branch inside parsePiJsonLine to handle an already-parsed object
versus a raw string to avoid double parsing and reduce per-chunk overhead,
keeping existing error handling.
🤖 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/plugins/agents/builtin/pi.ts`:
- Around line 295-327: The code currently casts event.assistantMessageEvent and
message blocks without runtime validation; update the handling in the
assistantEvent switch (the assistantEvent variable and its eventType) to first
ensure eventType is a string, then for 'tool_use_start' only push events when
assistantEvent.name is a string and assistantEvent.input is a plain object (or
Record<string, unknown>), and for 'tool_use_end' behave as before; similarly, in
the message_end branch validate that message is an object with content as an
array and that each block is an object with type === 'text' and that block.text
is a string before pushing { type: 'text', content: text }; perform safe type
guards instead of unchecked casts around assistantEvent, event.message,
block.type, and block.text.

---

Nitpick comments:
In `@src/plugins/agents/builtin/pi.ts`:
- Around line 375-385: Parse the JSON line once into a variable (e.g.,
parsedJson) and reuse it for both callbacks and event mapping: attempt
JSON.parse(trimmed) once, if it succeeds call options.onJsonlMessage(parsedJson)
and then pass the parsed object into this.parsePiJsonLine; if parse fails, fall
back to calling this.parsePiJsonLine(trimmed) so behavior remains unchanged.
Update parsePiJsonLine to accept either a string or Record<string, unknown> (or
add an overload) and branch inside parsePiJsonLine to handle an already-parsed
object versus a raw string to avoid double parsing and reduce per-chunk
overhead, keeping existing error handling.

In `@tests/plugins/pi-agent.test.ts`:
- Around line 115-143: The tests currently call plugin.initialize(...) and only
assert plugin.isReady(), which doesn't verify that invalid config values were
ignored; update each test (the ones calling plugin.initialize with invalid
mode/thinking/model/timeout) to also call the observable API plugin.buildArgs()
(or the equivalent public accessor used in this test suite) and assert its
returned args reflect the expected default/previous valid values (e.g., default
mode, default thinking level, original model string, default timeout) rather
than the invalid input; keep the existing isReady() assertion but add a second
assertion comparing plugin.buildArgs() to the expected args for each case so the
test actually guards against config regressions.
- Around line 43-51: Multiple tests repeat an afterEach that silently swallows
disposal errors; extract a shared helper (e.g., disposeIfReady) that calls
plugin.isReady() and then plugin.dispose(); replace the duplicate afterEach
blocks to call disposeIfReady(), and avoid a blanket empty catch—either let
errors surface or catch and rethrow/log them (use processLogger or throw) so
genuine disposal failures are visible while still handling already-disposed
state (check isReady before disposing).

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3afe6c8 and 4924206.

📒 Files selected for processing (5)
  • src/plugins/agents/builtin/index.ts
  • src/plugins/agents/builtin/pi.ts
  • src/setup/skill-installer.ts
  • tests/plugins/pi-agent.test.ts
  • website/content/docs/plugins/agents/pi.mdx
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/setup/skill-installer.ts
  • src/plugins/agents/builtin/index.ts
  • website/content/docs/plugins/agents/pi.mdx

subsy and others added 2 commits March 13, 2026 09:18
Extract parsePiJsonLine as a standalone exported function (matching
the pattern used by parseKimiJsonLine) and add 20 test cases covering
all three JSONL event types: message_update, message_end, turn_end.

This closes the biggest test coverage gap identified during review —
the parser is pure logic with no external dependencies and was previously
untested. Coverage for pi.ts improved from 38% to 55.6%.
@subsy
Copy link
Owner

subsy commented Mar 13, 2026

Review + Fixes Pushed

Nice work on this plugin — it follows all the established patterns well. I've pushed one commit to address the main gap I found during review:

What I fixed

Exported parsePiJsonLine and added 20 parser test cases (9ddde620)

The JSONL parser was the riskiest untested code — it handles 3 event types (message_update, message_end, turn_end) with multiple sub-cases but had zero test coverage. I:

  1. Extracted parsePiJsonLine as a standalone exported function (matching the parseKimiJsonLine pattern with @internal JSDoc tag)
  2. Added 20 tests covering all event types, edge cases (missing fields, empty arrays, unknown types), and invalid input
  3. Coverage for pi.ts improved from 38% → 55.6% lines

What looked good (no changes needed)

  • validateModel accepting empty string — consistent with Cursor, Codex, and GitHub Copilot plugins
  • skill-installer mapping pi'pi' — follows the same pattern as kiro'kiro'
  • Doc links in pi.mdx — both claude.mdx and kiro.mdx exist
  • JSONL buffering in execute() — correctly handles line splitting with incomplete buffer flushing on end
  • Stdin for prompt delivery — good approach to avoid shell escaping issues

All tests pass (65 total, up from 45), typecheck clean, build succeeds.

AI Agent added 2 commits March 13, 2026 14:05
…e tests

- Replace unchecked `as` casts in parsePiJsonLine with proper runtime
  type guards for assistantEvent, eventType, name, input, and content
  blocks. tool_use_start events are now safely skipped when name is not
  a string or input is not a plain object.

- Accept string | Record<string, unknown> in parsePiJsonLine so callers
  can pass pre-parsed objects. processLines now parses JSON once and
  reuses the result for both onJsonlMessage and event mapping.

- Extract shared disposeIfReady helper in tests, replacing three
  duplicate afterEach blocks with blanket empty catches.

- Strengthen invalid-config tests to verify defaults are preserved via
  buildArgs output, not just isReady().

- Add 6 new parser tests for type guard edge cases (non-string name,
  non-object input, array input, non-string eventType, non-string text,
  pre-parsed object input).

71 tests pass, 105 assertions. Coverage for pi.ts: 58.6% lines.
@subsy
Copy link
Owner

subsy commented Mar 13, 2026

Review feedback addressed (9ce4a57)

Merged latest main and pushed fixes for all four findings:

1. Runtime type guards in parsePiJsonLine

Replaced all unchecked as casts with proper runtime validation:

  • assistantEvent is verified as non-null, typeof object, non-array before access
  • eventType must be a string before entering the switch
  • tool_use_start only emits when name is a string AND input is a plain object (not array, not null)
  • message_end content blocks validate type === 'text' and typeof text === 'string' and non-empty before pushing

2. Eliminated double JSON.parse in processLines

processLines now parses each line once into a local parsed variable, reuses it for both onJsonlMessage callback and parsePiJsonLine. Updated parsePiJsonLine signature to accept string | Record<string, unknown> — when given an already-parsed object, it skips the parse step entirely.

3. Strengthened invalid-config tests

Tests for invalid mode, thinking, model, and timeout now use TestablePiPlugin and assert buildArgs() output reflects defaults (e.g., invalid mode still produces --mode json, invalid thinking omits --thinking, etc.) — not just isReady().

4. Extracted disposeIfReady helper

Replaced three duplicate afterEach blocks (with blanket empty catches) with a shared disposeIfReady() function. Errors from dispose() now surface instead of being silently swallowed.

New tests added

6 new tests for type guard edge cases: non-string name, non-object input, array input, non-string eventType, non-string text, and pre-parsed object acceptance.

71 tests, 105 assertions, all passing. Typecheck + build clean.

@codecov
Copy link

codecov bot commented Mar 13, 2026

Codecov Report

❌ Patch coverage is 68.27586% with 138 lines in your changes missing coverage. Please review.
✅ Project coverage is 47.89%. Comparing base (a4ce56c) to head (a31e98f).
⚠️ Report is 7 commits behind head on main.

Files with missing lines Patch % Lines
src/plugins/agents/builtin/pi.ts 67.98% 138 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main     #344      +/-   ##
==========================================
+ Coverage   47.63%   47.89%   +0.26%     
==========================================
  Files         104      105       +1     
  Lines       33923    34358     +435     
==========================================
+ Hits        16160    16457     +297     
- Misses      17763    17901     +138     
Files with missing lines Coverage Δ
src/plugins/agents/builtin/index.ts 94.11% <100.00%> (+0.56%) ⬆️
src/setup/skill-installer.ts 74.31% <100.00%> (+0.14%) ⬆️
src/plugins/agents/builtin/pi.ts 67.98% <67.98%> (ø)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Pi agent docs existed but were unreachable — not listed in the
sidebar navigation alongside the other agent plugins.
@vercel
Copy link

vercel bot commented Mar 13, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
ralph-tui Ready Ready Preview, Comment Mar 13, 2026 2:14pm

Request Review

@subsy subsy merged commit 070344f into subsy:main Mar 13, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants