Skip to content

Bscholar/pi rpc#2812

Closed
bscholar-tt wants to merge 38 commits into
pingdotgg:mainfrom
bscholar-tt:bscholar/pi-rpc
Closed

Bscholar/pi rpc#2812
bscholar-tt wants to merge 38 commits into
pingdotgg:mainfrom
bscholar-tt:bscholar/pi-rpc

Conversation

@bscholar-tt

@bscholar-tt bscholar-tt commented May 26, 2026

Copy link
Copy Markdown

What Changed

Why

UI Changes

Checklist

  • This PR is small and focused
  • I explained what changed and why
  • I included before/after screenshots for any UI changes
  • I included a video for animation/interaction changes

Note

High Risk
Large new provider adapter and release-pipeline changes affect core agent execution and how CLI/desktop are shipped; remote install now depends on GitHub release assets.

Overview
Adds a first-class Pi Agent provider that runs the pi CLI in RPC mode and maps its JSONL events (turns, tools, streaming text, extension UI prompts) into T3’s provider runtime, plus settings/UI wiring and structured text-generation via Pi.

Fork-oriented release and distribution: CI/release runners move to GitHub-hosted images; the release workflow is trimmed to manual stable releases (no tag push, nightly cron, npm publish, Vercel deploy, Discord, or Windows/signing paths). Releases build macOS/Linux desktop artifacts and attach a t3 CLI .tgz from a new cli pack command instead of publishing to npm. Desktop remote SSH installs t3 from GitHub Release tarball URLs when repository.url points at the fork.

Product defaults and branding: Stage label Alpha → Bscholar across desktop/web; default auto-bootstrap and text-generation provider shift from Codex to Claude. Telemetry defaults to off. Composer/model UI keys trait options by provider instance id and limits the model picker sidebar to ready instances.

Some server tests around OTLP proxying and live CLI-over-running-server are removed or narrowed.

Reviewed by Cursor Bugbot for commit 9678427. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Add Pi Agent provider with RPC adapter, text generation, and settings integration

  • Introduces the piAgent provider driver (PiDriver.ts, PiAdapter.ts) that manages Pi Agent sessions, turns, and UI interactions via the @earendil-works/pi-coding-agent package.
  • Adds PiTextGeneration (PiTextGeneration.ts) that spawns the Pi CLI with a 180s timeout, collects JSON output, and validates it against a schema.
  • Registers piAgent in contracts (model.ts, settings.ts), provider registry, and UI metadata, with default models claude-sonnet-4-6 and claude-haiku-4-5.
  • Switches auto-bootstrap and default text generation from Codex to Claude (claudeAgent/claude-haiku-4-5).
  • Renames the app stage label from Alpha to Bscholar across desktop, web, and contracts.
  • Overhauls the release workflow to require manual dispatch, drop nightly/Windows/macOS-x64 paths, and publish CLI tarballs via GitHub Releases.
  • Risk: default provider change from Codex to Claude affects all fresh installs and auto-bootstrapped configurations.
📊 Macroscope summarized 9678427. 35 files reviewed, 6 issues evaluated, 0 issues filtered, 5 comments posted

🗂️ Filtered Issues

ardenworks and others added 30 commits May 20, 2026 14:02
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
These tests fail due to an undici content-length validation
incompatibility and are unrelated to the desktop app.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Only build macOS arm64 and Linux x64 artifacts.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
No RELEASE_APP_ID/RELEASE_APP_PRIVATE_KEY secrets configured.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Two bugs preventing Pi responses from appearing in chat:

1. sendTurn awaited prompt() which blocks until the entire turn
   finishes, preventing the caller from returning. Changed to
   fire-and-forget via runFork (matching Claude adapter pattern).

2. message_update handler checked for wrong properties (.text/.thinking)
   instead of the actual SDK shape (.type === "text_delta"/.delta).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Without a shell, NVM-managed binaries aren't in PATH on SSH hosts.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
These changes were part of the Pi provider integration but were
never staged: Pi added to provider client definitions, removed
from "Coming Soon", and default text generation instance updated.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
SSH auth on some hosts takes 8-11s, and the first HTTP response
through a newly-established tunnel can exceed 1s. The 1s probe
timeout caused every readiness check to fail even after the tunnel
was up, and the 20s total window was too narrow for slow-auth hosts.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
SSH auth on some hosts takes 8-11s, and the first HTTP response
through a newly-established tunnel can exceed 1s. The 1s probe
timeout caused every readiness check to fail even after the tunnel
was up, and the 20s total window was too narrow for slow-auth hosts.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… sidebar

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add `pack` CLI subcommand to create versioned .tgz tarballs
- Update release workflow to build and upload CLI tarballs as release assets
- Add `parseReleaseRepository` to extract owner/repo from URLs or slugs
- Remote t3 runners prefer GitHub Release URLs when fork repository is configured
- Desktop app passes fork repository to SSH tunnel for registry-free installation
So the runtime-derived GitHub Release tarball URLs in
resolveRemoteT3CliPackageSpec resolve to this fork's releases instead of
upstream pingdotgg/t3code.
The previous rename commit (27f1457) updated the legacy user-data dir
name and test fixtures but left the actual stage-label resolver and the
DesktopAppStageLabel contract still emitting "Alpha", so the rendered
app name and DesktopAppIdentity test diverged. Update the contract
literal/schema, the desktop resolver, and the web branding fallback so
non-dev/non-nightly builds advertise themselves as "T3 Code (Bscholar)"
end-to-end.
Pi SDK fires turn_end after each internal LLM call and agent_end once at
the end of the full run. Completing the t3code turn on turn_end fragmented
each Pi response into multiple turns: tool activities disappeared as soon
as a new sub-turn started (the work log filters by latestTurnId), and the
elapsed-time counter reset on every sub-turn boundary.

Making turn_end a no-op and letting agent_end drive completeTurn keeps all
tool activities and text deltas under the single turnId created by sendTurn,
matching how the Claude adapter behaves.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When a Pi thread is continued (e.g. page reload or new server session),
createAgentSession was always creating a blank session with no history.

Now startSession captures the Pi session file path as resumeCursor and
stores it via ProviderSessionDirectory (the same mechanism Claude uses).
On subsequent startSession calls for the same thread, SessionManager.open
is used to load the prior JSONL session file so Pi has full context.
Falls back to a fresh session gracefully if the file is inaccessible.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Tool rows showed only the tool name because item.completed had no detail
or data fields — args were captured at tool_execution_start but never
stored or forwarded to item.completed, which is the event the UI renders.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Support GitHub Release tarball distribution for t3 CLI
adambuchweitz and others added 8 commits May 22, 2026 20:41
Align PiAdapter's classifyToolItemType with ClaudeAdapter so Skill,
Task, MCP, and image tools get proper canonical types instead of
falling through to dynamic_tool_call. Expand summarizePiToolArgs to
extract skill names, prompts, and search patterns for richer detail
strings in the chat timeline.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…pshot

Scan .pi/skills/, .claude/skills/ (project-local) and ~/.pi/agent/skills/
(global) during the PI provider health check. Parse skill frontmatter for
name and description, map to ServerProviderSkill entries, and include them
in the provider snapshot so the chat composer can offer them in the slash
command picker.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… mode

Replaces createAgentSession (npm SDK, in-process) with a spawned `pi --mode rpc`
subprocess. Commands are sent as JSON lines to stdin (prompt, abort, get_state);
AgentSessionEvent objects and RpcResponse objects are read back from stdout.

Per-session lifecycle is managed by a Scope.Closeable; an outgoing Queue drives
the stdin writer fiber; get_state is used after startup to capture the session
file path for the resume cursor.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When Pi emits extension_ui_request events over RPC stdout, route them
through the T3 Code user-input system:
- select / confirm → surface as user-input.requested with structured
  options; respondToUserInput maps answers back to extension_ui_response
- input / editor → auto-cancel (no free-text equivalent in contracts)
- notify / setStatus / setWidget / setTitle / set_editor_text →
  auto-acknowledge so Pi isn't left waiting

On session stop, outstanding requests are cancelled before the process
is killed so Pi doesn't deadlock.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ect and freeform

Pi's RPC fallback uses method:"input" for two cases:
- Multi-select: title encodes options as a numbered list ("Q?\n1. A\n2. B").
  Parse the list, surface as multiSelect:true question; map selected labels
  back to 1-based comma-separated indices for Pi's response protocol.
- Freeform: surface with options:[] so T3's auto-added Other field handles
  free-text entry; answer string passed through as value directly.

editor requests are still cancelled (no useful mapping).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Fire-and-forget methods (notify/setStatus/setWidget/setTitle/set_editor_text)
  don't expect a response — stop sending spurious { value: "" } replies
- editor expects { value: "..." } back like input, not cancellation —
  wire it through the same freeform path
- Expand PendingExtensionUI.method to include "editor"

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented May 26, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 89cb2b1f-0e52-4dfd-83c3-6ac95848c5d1

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ 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.

@github-actions github-actions Bot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:XXL 1,000+ changed lines (additions + deletions). labels May 26, 2026

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 7 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 9678427. Configure here.

const stateDir = path.join(baseDir, isDevelopment ? "dev" : "userdata");
const userDataDirName = isDevelopment ? "t3code-dev" : "t3code";
const legacyUserDataDirName = isDevelopment ? "T3 Code (Dev)" : "T3 Code (Alpha)";
const legacyUserDataDirName = isDevelopment ? "T3 Code (Dev)" : "T3 Code (Bscholar)";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Alpha userData not migrated

High Severity

The legacyUserDataDirName was updated from "T3 Code (Alpha)" to "T3 Code (Bscholar)". This prevents resolveUserDataPath from finding existing user data in "Alpha" directories, causing a fresh user profile to be created and prior settings, SSH config, and Electron data to be lost on upgrade.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 9678427. Configure here.

}
if (modelSelection?.model) {
spawnArgs.push("--model", modelSelection.model);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pi launchArgs never applied

Medium Severity

The PiAdapter's startSession function omits PiSettings.launchArgs when spawning the Pi RPC process. This prevents user-configured arguments from being applied, unlike other drivers that support these settings.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 9678427. Configure here.


yield* context.writeCommand({ type: "prompt", message: promptText }).pipe(
Effect.catchDefect(() => completeTurn(context, "failed", "Failed to send prompt.")),
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pi turns ignore attachments

Medium Severity

The sendTurn implementation only forwards the string input to Pi's prompt RPC, completely ignoring any attachments from ProviderSendTurnInput. This results in Pi receiving incomplete prompts when users include images or files.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 9678427. Configure here.

echo "tag=v$version" >> "$GITHUB_OUTPUT"
echo "name=T3 Code v$version" >> "$GITHUB_OUTPUT"
echo "is_prerelease=false" >> "$GITHUB_OUTPUT"
echo "make_latest=true" >> "$GITHUB_OUTPUT"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Prerelease versions marked latest

Medium Severity

The release_meta step now always sets is_prerelease to false and make_latest to true. This means any version provided with a pre-release suffix (e.g., 1.2.3-beta.1) will be incorrectly published and promoted as the latest stable GitHub release.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 9678427. Configure here.

homePath: "",
customModels: [],
launchArgs: "",
},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Tests enable Pi probing

Low Severity

Several registry tests disable Codex, Claude, Cursor, and OpenCode probes to avoid spawning on CI, but set piAgent.enabled: true, which still runs the Pi --version health check. That breaks the stated isolation and can add flaky PATH-dependent behavior to Codex-focused cases.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 9678427. Configure here.


if (context.turnState) {
yield* completeTurn(context, "completed");
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Inflight Pi turn marked completed

Medium Severity

If sendTurn is called while a Pi turn is still active, the adapter calls completeTurn with state completed before sending the new prompt. The Pi agent may still be running the previous work, so the UI can close a turn prematurely while RPC work continues.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 9678427. Configure here.

release-assets/*.exe
release-assets/*.blockmap
release-assets/*.yml
release-assets/*.tgz

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Mac updater blockmaps not published

Medium Severity

The release job still collects *.blockmap files into artifacts but the GitHub release upload lists no longer include release-assets/*.blockmap. Mac builds that rely on electron-updater blockmaps may fail or fall back when updating from the published release.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 9678427. Configure here.

Comment on lines +838 to +840
const stateResponse = yield* Deferred.await(stateDeferred).pipe(
Effect.timeoutOption(5000),
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟢 Low Layers/PiAdapter.ts:838

The pendingRequests entry for stateReqId is never cleaned up when Deferred.await times out at lines 838-840. If the 5-second timeout fires, the map entry remains orphaned until the session closes, leaking one entry per timed-out session startup. Consider deleting the entry on timeout using Effect.tap or Effect.ensuring.

     const stateResponse = yield* Deferred.await(stateDeferred).pipe(
       Effect.timeoutOption(5000),
+      Effect.tap(() => Effect.sync(() => pendingRequests.delete(stateReqId))),
     );
🤖 Copy this AI Prompt to have your agent fix this:
In file apps/server/src/provider/Layers/PiAdapter.ts around lines 838-840:

The `pendingRequests` entry for `stateReqId` is never cleaned up when `Deferred.await` times out at lines 838-840. If the 5-second timeout fires, the map entry remains orphaned until the session closes, leaking one entry per timed-out session startup. Consider deleting the entry on timeout using `Effect.tap` or `Effect.ensuring`.

Evidence trail:
apps/server/src/provider/Layers/PiAdapter.ts line 835: `pendingRequests.set(stateReqId, stateDeferred)` — entry added. Lines 838-840: `Deferred.await(stateDeferred).pipe(Effect.timeoutOption(5000))` — timeout does not delete entry. Line 598: `context.pendingRequests.delete(msg['id'])` — only deletion path, requires a response to arrive. Lines 613-665 (`stopSessionInternal`): `pendingExtensionUIs` explicitly cleaned at 625-631 with `.clear()`, but no cleanup for `pendingRequests` at all. Line 664: `sessions.delete(context.session.threadId)` removes context reference (GC-eligible), but entry persists in map until that point.

with:
node-version-file: package.json
- name: Build t3 server package
run: bun run build --filter=t3

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟠 High workflows/release.yml:191

bun run build --filter=t3 passes --filter=t3 to the build script instead of interpreting it as a workspace filter. Bun requires top-level flags before subcommands, so the filter is ignored and the build may fail or run against the wrong packages. Use bun --filter=t3 run build to ensure the filter is applied.

Suggested change
run: bun run build --filter=t3
- name: Build t3 server package
run: bun --filter=t3 run build
🤖 Copy this AI Prompt to have your agent fix this:
In file .github/workflows/release.yml around line 191:

`bun run build --filter=t3` passes `--filter=t3` to the build script instead of interpreting it as a workspace filter. Bun requires top-level flags before subcommands, so the filter is ignored and the build may fail or run against the wrong packages. Use `bun --filter=t3 run build` to ensure the filter is applied.

echo "Invalid release version: $raw" >&2
exit 1
fi

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟠 High workflows/release.yml:59

The version validation regex on line 55 accepts pre-release suffixes like -beta.1, but lines 64-65 unconditionally set is_prerelease=false and make_latest=true. When a user dispatches the workflow with version 1.2.3-beta.1, the GitHub Release is published as the latest stable release, directing all users to a pre-release build instead of respecting the pre-release semantics.

🤖 Copy this AI Prompt to have your agent fix this:
In file .github/workflows/release.yml around line 59:

The version validation regex on line 55 accepts pre-release suffixes like `-beta.1`, but lines 64-65 unconditionally set `is_prerelease=false` and `make_latest=true`. When a user dispatches the workflow with version `1.2.3-beta.1`, the GitHub Release is published as the latest stable release, directing all users to a pre-release build instead of respecting the pre-release semantics.

Comment thread apps/web/index.html
rel="stylesheet"
/>
<title>T3 Code (Alpha)</title>
<title>T3 Code (Bscholar)</title>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟠 High web/index.html:92

The <title> was changed to T3 Code (Bscholar), where "Bscholar" appears to be the PR author's username rather than an intentional product stage name. This displays a personal identifier to all users in browser tabs, bookmarks, and search results. Consider reverting to a stage name like (Alpha) or confirming if this change is intentional.

🤖 Copy this AI Prompt to have your agent fix this:
In file apps/web/index.html around line 92:

The `<title>` was changed to `T3 Code (Bscholar)`, where "Bscholar" appears to be the PR author's username rather than an intentional product stage name. This displays a personal identifier to all users in browser tabs, bookmarks, and search results. Consider reverting to a stage name like `(Alpha)` or confirming if this change is intentional.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Medium

providers: Schema.optionalKey(

Adding piAgent to ServerSettings.providers at line 416 without a corresponding entry in ServerSettingsPatch.providers breaks the patch API for Pi settings. When users attempt to modify Pi settings, the request will fail validation because piAgent is missing from the patch struct at lines 505-511. Every other provider has a matching patch schema, so piAgent should have one too.

🤖 Copy this AI Prompt to have your agent fix this:
In file packages/contracts/src/settings.ts around line 505:

Adding `piAgent` to `ServerSettings.providers` at line 416 without a corresponding entry in `ServerSettingsPatch.providers` breaks the patch API for Pi settings. When users attempt to modify Pi settings, the request will fail validation because `piAgent` is missing from the patch struct at lines 505-511. Every other provider has a matching patch schema, so `piAgent` should have one too.

Evidence trail:
packages/contracts/src/settings.ts lines 411-417 (ServerSettings.providers includes piAgent), lines 505-511 (ServerSettingsPatch.providers omits piAgent), lines 461-490 (CodexSettingsPatch, ClaudeSettingsPatch, CursorSettingsPatch, OpenCodeSettingsPatch all defined, no PiSettingsPatch). PI_PLAN.md line 110 and 125 mention PiSettingsPatch only in a planning doc.

@macroscopeapp

macroscopeapp Bot commented May 26, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

4 blocking correctness issues found. This PR introduces a new Pi Agent provider integration alongside major CI/CD workflow changes, branding modifications, and default behavior changes. Multiple high-severity unresolved comments identify potential data loss, broken release semantics, and questionable branding changes that require human assessment.

You can customize Macroscope's approvability policy. Learn more.

bscholar-tt added a commit to bscholar-tt/t3code that referenced this pull request May 26, 2026
Release workflow:
- Detect pre-release versions (containing '-') and avoid promoting them
  as the latest stable GitHub release. Feeds through a release_channel
  output to resolve-previous-release-tag.ts.
- Use 'bun --filter=t3 run build' so the workspace filter actually
  applies (bun requires top-level flags before subcommands).
- Include release-assets/*.blockmap in the published files so
  electron-updater delta updates keep working on macOS.

Pi provider:
- Apply PiSettings.launchArgs to the spawned 'pi --mode rpc' process,
  matching the ClaudeAdapter parseCliArgs pattern.
- Forward ProviderSendTurnInput.attachments to Pi's prompt RPC as
  inline base64 ImageContent so image turns no longer drop the
  attached files.
- When sendTurn is called while a turn is still active, send an
  abort to Pi first and mark the prior turn as 'interrupted'
  (not 'completed') so the UI doesn't claim a settled result.
- Clean up the get_state pendingRequests entry on timeout via
  Effect.ensuring to prevent per-startup leaks.

Contracts:
- Add PiSettingsPatch and wire it into ServerSettingsPatch.providers
  so the patch API actually accepts Pi settings updates.

Tests:
- Disable piAgent in the two Codex-focused ProviderRegistry tests
  that were spawning a real 'pi --version' probe and breaking the
  stated CI isolation.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL 1,000+ changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants