Skip to content

feat(agent): embed pi as the engine + add gated plan execution - #27

Merged
gustavohariel merged 1 commit into
mainfrom
feat/embed-pi-gated-execution
Jun 1, 2026
Merged

gustavohariel merged 1 commit into
mainfrom
feat/embed-pi-gated-execution

Conversation

@gustavohariel

Copy link
Copy Markdown
Owner

Migrates funfa's engine from the dual Vercel AI SDK ToolLoopAgent (API-key) + codex CLI subprocess (subscription) setup to a single embedded pi agent (@earendil-works/pi-coding-agent), and adds gated execution so funfa can apply an accepted plan, not just print it. Effect-TS is kept at the core/* + propose boundary.

Part A — embed pi (engine swap)

  • provider.ts → builds pi AuthStorage + ModelRegistry (~/.pi/agent). No-auth detector via ModelRegistry.getAvailable(); FUNFA_PI_MODEL ("<provider>/<model-id>") override; friendly /login-or-provider-key error. Unlocks Claude Pro/Max and ChatGPT subscription OAuth (impossible before).
  • propose.ts → drives pi headlessly via createAgentSession + session.prompt(); submit_plan captures the outcome through a closure. pi retries transients itself, so the duplicate Effect retry is dropped; AgentError classification is kept.
  • prompt.ts → collapses to LOOP_BODY + one PI_BINDING + buildBasePrompt (the two-sided API_BINDING/CODEX_BINDING + drift-guard test are gone). FUNFA_FAKE_HARDWARE is now provider-neutral.
  • Tools → pi defineTool (TypeBox): submit_plan, load_skill (bundled bodies, not disk), fetch_page, web_search. Hardware probes use pi's built-in bash, so run_shell + sandbox.ts are deleted.
  • web_search is backed by Parallel's standalone Search API (PARALLEL_API_KEY) via plain fetch; without the key it returns a clear "use fetch_page" message. The Vercel AI SDK + AI Gateway deps are removed (the gateway exposes search only as a generation-time tool, so there was nothing embeddable).
  • Deleted the codex stack (codex-cli/codex-app-server/codex-protocol) and the vendored codex-app-server-schema/.
  • Build: compile drops --bytecode (bytecode gen fails on pi's dep tree); keeps --minify --sourcemap. Binary ~64 MB → ~71 MB.

Part B — gated execution (new capability)

  • permission.ts — a pi tool_call extension, registered in-embed via the resource loader's extensionFactories (survives bun --compile; no -e filesystem discovery). Gates every apply_command: consent prompt (default deny), pinned trusted-host download allow-list (GitHub release infra for Proton-GE/DXVK/vkd3d), https-only, and --bypass-confirms auto-allows.
  • apply-command.ts — argv-only apply tool (no shell) for PHASE 4 filesystem steps, run via Bun.spawn.
  • PHASE 4 in the skills: apply filesystem high-tier steps (Proton-GE install, etc.) via apply_command; Steam-client state (launch options, Proton-version selection) stays PRINTED for the user to apply in Steam — on the maintainer's targets Steam runs continuously and its config files can't be written safely from the CLI. No pretending to apply Steam state.

Threat model

funfa runs unsandboxed bash composed by a model that ingests untrusted forum/wiki text → a prompt-injection RCE surface. Mitigations in this PR: the permission gate, the download allow-list + https-only, and argv-only apply_command. Residual risk: the read-only bash probe tool is allowed freely; OS-level confinement (bubblewrap) is the next hardening step and is intentionally out of scope. Documented in agent/permission.ts.

Tests

  • Replaced propose.test.ts (was AI-SDK MockLanguageModelV3-specific → now buildUserPrompt units + an opt-in live pi-session smoke gated on FUNFA_PI_SMOKE=1 + auth), provider.test.ts (pi auth: parseModelOverride/hasUsableAuth/resolveModelOverride), and prompt.test.ts (buildBasePrompt).
  • New: permission.test.ts (gate blocks without consent / allows with consent / auto-allows under --bypass-confirms / blocks off-allow-list + http downloads) and tools.test.ts (each custom tool's execute + the Bun apply runner).
  • compile.test.ts updated: compiles without --bytecode, asserts the no-auth message + that bundled skill text parsed.
  • Deleted the codex + sandbox tests.

Verification

  • bun run check-types ✓, bun run check (ultracite) ✓
  • cd apps/cli && bun test → 81 pass, 1 skip (opt-in live smoke), 0 fail
  • bun run --filter cli compile succeeds without --bytecode; funfa --help exits 0; no-auth path prints the /login error.

Auth-gated manual E2E (can't run in CI): npx @earendil-works/pi-coding-agent → /login, then funfa "Assassin's Creed Brotherhood" (off-Linux: set FUNFA_FAKE_HARDWARE); craft a plan with a Proton-GE install to exercise the gate.

Out of scope (unchanged from the plan)

Decky frontend + SteamClient apply for launch-options/Proton-version; bubblewrap confinement of apply_command; removing Effect-TS.

Replace the dual Vercel AI SDK ToolLoopAgent (API-key) + codex CLI
subprocess (subscription) engine with a single embedded pi agent
(@earendil-works/pi-coding-agent), and add gated execution so funfa can
apply an accepted plan, not just print it. Effect-TS is kept at the
core/propose boundary.

Engine (Part A):
- provider.ts builds pi AuthStorage + ModelRegistry (~/.pi/agent). No-auth
  detector via getAvailable(); FUNFA_PI_MODEL ("<provider>/<model-id>")
  override. Unlocks Claude Pro/Max + ChatGPT subscription OAuth.
- propose.ts drives pi headlessly via createAgentSession + session.prompt;
  submit_plan captures the outcome through a closure. pi handles transient
  retries, so the duplicate Effect retry is dropped.
- prompt.ts collapses to LOOP_BODY + one PI_BINDING (the API/CODEX two-sided
  binding + drift guard are gone). FUNFA_FAKE_HARDWARE is now provider-neutral.
- Custom tools are pi defineTools (TypeBox): submit_plan, load_skill (bundled
  bodies), fetch_page, web_search. Hardware probes use pi's built-in bash, so
  run_shell + sandbox.ts are deleted. web_search is backed by Parallel's
  Search API (PARALLEL_API_KEY), degrading to fetch_page when unset; the AI
  SDK + AI Gateway deps are removed.
- Delete the codex stack (codex-cli/app-server/protocol) and vendored schema.

Gated execution (Part B):
- permission.ts: a pi tool_call extension (registered in-embed via the
  resource loader's extensionFactories, so it survives bun --compile) that
  gates every apply_command — consent prompt (default deny), download
  allow-list (trusted GitHub release hosts), https-only, --bypass-confirms
  auto-allows.
- apply-command.ts: argv-only apply tool (no shell) for PHASE 4 filesystem
  steps; runs via Bun.spawn.
- funfa/plan SKILLs gain PHASE 4: apply filesystem high-tier steps via
  apply_command; Steam-client state (launch options, Proton version) stays
  PRINTED for the user to apply in Steam, since Steam runs continuously and
  its config cannot be written safely from the CLI.

Build: compile drops --bytecode (bytecode gen fails on pi's dep tree);
keeps --minify --sourcemap. Binary ~64MB -> ~71MB.

Threat model: funfa runs unsandboxed bash composed by a model that ingests
untrusted forum/wiki text -> a prompt-injection RCE surface. Mitigations
here: the gate, the download allow-list + https-only, argv-only
apply_command. Residual risk: the read-only bash probe tool is allowed
freely; OS-level confinement (bubblewrap) is the next hardening step and is
out of scope. Documented in agent/permission.ts.

Tests: replace propose/provider/prompt tests; add permission.test.ts +
tools.test.ts; compile.test.ts asserts no-bytecode build + the no-auth
message + bundled-skill reachability. Delete codex/sandbox tests.

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.

@gustavohariel
gustavohariel merged commit cb6103f into main Jun 1, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant