Skip to content

Add Hermes and Pi agent provider support#2748

Open
joeynyc wants to merge 23 commits into
pingdotgg:mainfrom
joeynyc:hermes-agent-provider
Open

Add Hermes and Pi agent provider support#2748
joeynyc wants to merge 23 commits into
pingdotgg:mainfrom
joeynyc:hermes-agent-provider

Conversation

@joeynyc
Copy link
Copy Markdown

@joeynyc joeynyc commented May 18, 2026

Summary

  • add Hermes Agent and Pi Agent provider support through ACP
  • add provider settings setup, diagnostics, update actions, docs, and screenshots
  • harden Pi ACP spawning for packaged macOS PATH behavior
  • keep Pi CLI update notices out of chat output

Verification

  • bun run --cwd apps/server typecheck
  • bun run --cwd apps/web typecheck
  • bun test apps/server/src/provider/Layers/PiAdapter.test.ts apps/server/src/provider/Layers/PiProvider.test.ts apps/server/src/provider/acp/PiAcpSupport.test.ts
  • bun run --cwd apps/web test:browser SettingsPanels.browser.tsx
  • bun run --cwd apps/web build
  • bun run --cwd apps/server build
  • bun run --cwd apps/desktop build
  • bun run --cwd apps/desktop smoke-test
  • real local Pi chat E2E with GPT 5.5 returned PI_E2E_OK before this final filter patch; filter covered by focused unit test

Note

Medium Risk
Adds two new provider drivers that spawn local CLIs via ACP and changes provider/model selection gating logic in the web UI, which could affect session startup and model-picker behavior. Risk is moderated by targeted unit/browser tests and largely additive integration paths.

Overview
Adds Hermes and Pi as first-class providers, including new server ProviderDrivers plus ACP adapters that manage sessions/turns, permissions, attachments, and runtime event streaming.

Implements provider health/status layers for both (binary discovery, version/auth probing, default-model detection, suggested-path hints) and background snapshot enrichment for update advisories; Pi also hardens ACP spawning by injecting PI_ACP_PI_COMMAND and fixing PATH handling, and filters Pi CLI “new version” notices from assistant deltas.

Extends text-generation to use Hermes/Pi ACP runtimes for commit/PR/branch/thread-title generation, registers both drivers in the built-in driver list, and updates the web UI/model picker to only allow selection of selectable instances (enabled + available + ready + has models) while adding provider-specific chat surfaces (chat-surface-hermes / chat-surface-pi) and improved empty/setup messaging; README/docs are updated with setup guidance and screenshots.

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

Note

Add Hermes and Pi as built-in agent provider types with ACP-backed text generation

  • Registers HermesDriver and PiDriver as built-in provider drivers, each backed by a new ACP runtime (makeHermesAcpRuntime / makePiAcpRuntime) that spawns the respective CLI tool and streams chunked responses with a 180s timeout.
  • Adds HermesSettings and PiSettings schemas to server and client settings, including binary path fields, custom models, and patch schemas for partial updates.
  • Implements config file parsing (parseHermesConfigModelDefaults, parsePiConfigModelDefaults) to detect the active model from each provider's YAML/JSON config on disk.
  • Extends the model picker and chat composer to use isSelectableProviderInstance (enabled + available + ready + has models), so Hermes/Pi instances that are installed but unauthenticated appear disabled rather than selectable.
  • Adds provider-specific UI: setup checklists, diagnostics rows, CLI-managed-auth messaging, and branded chat surface themes (chat-surface-hermes, chat-surface-pi).
  • Adds a custom dev-watch script (scripts/dev-watch.mjs) replacing node --watch with a debounced, SIGTERM-graceful file watcher.
  • Risk: resolveSelectableProviderInstance now requires status === 'ready' and at least one model — previously enabled+available was sufficient, which could silently change the auto-selected provider for existing users.

Macroscope summarized 8eff67b.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 18, 2026

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: 6c659f03-31a7-496e-a345-04b1e94b499d

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 18, 2026
Comment thread apps/server/src/provider/Layers/PiProvider.ts
generateBranchName,
generateThreadTitle,
} satisfies TextGenerationShape;
});
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.

Near-identical TextGeneration files duplicate hundreds of lines

Low Severity

HermesTextGeneration.ts and PiTextGeneration.ts are functionally identical — same structure, same logic, same 256-line length — differing only in provider name strings and ACP runtime factory. This full-file duplication also extends to the adapter pair (HermesAdapter.ts/PiAdapter.ts) and the driver pair. A shared, parameterized makeAcpTextGeneration factory accepting the provider name and ACP runtime builder would eliminate hundreds of duplicated lines and ensure bug fixes apply to both providers.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 211f230. Configure here.

Comment thread apps/web/src/components/settings/SettingsPanels.tsx
Comment on lines +58 to +71
function scheduleRestart() {
clearTimeout(restartTimer);
restartTimer = setTimeout(() => {
restartTimer = undefined;
console.log("Restarting server...");

const previous = child;
previous.once("exit", () => {
if (!stopping) {
start();
}
});
previous.kill("SIGTERM");
}, 100);
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 scripts/dev-watch.mjs:58

If the child process exits on its own during the 100ms debounce window, the previous.once("exit", ...) listener in scheduleRestart is registered after the 'exit' event already fired. Since ChildProcess emits 'exit' only once, the listener never runs and start() is never called — the server stays dead. The parent process also won't exit because the exit handler at line 24 returns early when restartTimer is truthy. Consider checking previous.killed or previous.exitCode before registering the listener, and ensure the restart proceeds immediately if the process already exited.

  const previous = child;
+  if (previous.exitCode !== null || previous.killed) {
+    if (!stopping) {
+      start();
+    }
+  } else {
    previous.once("exit", () => {
      if (!stopping) {
        start();
      }
    });
    previous.kill("SIGTERM");
+  }
🤖 Copy this AI Prompt to have your agent fix this:
In file apps/server/scripts/dev-watch.mjs around lines 58-71:

If the child process exits on its own during the 100ms debounce window, the `previous.once("exit", ...)` listener in `scheduleRestart` is registered after the `'exit'` event already fired. Since `ChildProcess` emits `'exit'` only once, the listener never runs and `start()` is never called — the server stays dead. The parent process also won't exit because the exit handler at line 24 returns early when `restartTimer` is truthy. Consider checking `previous.killed` or `previous.exitCode` before registering the listener, and ensure the restart proceeds immediately if the process already exited.

Evidence trail:
apps/server/scripts/dev-watch.mjs lines 24-27 (exit handler returns early when `restartTimer` is truthy), lines 58-71 (`scheduleRestart` registers the `once('exit')` listener only after the debounce timeout, which may be after the child already exited). Node.js ChildProcess 'exit' event is emitted once and not replayed for late listeners. `child.kill()` on a dead process is a no-op. Verified at commit REVIEWED_COMMIT.

Comment thread apps/server/src/provider/acp/PiAcpSupport.ts
Comment thread apps/server/src/provider/Layers/PiProvider.ts Outdated
Comment thread apps/server/src/provider/Layers/HermesProvider.ts Outdated
@macroscopeapp
Copy link
Copy Markdown
Contributor

macroscopeapp Bot commented May 18, 2026

Approvability

Verdict: Needs human review

Diff is too large for automated approval analysis. A human reviewer should evaluate this PR.

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

Comment thread apps/server/src/provider/acp/PiAcpSupport.ts
@joeynyc
Copy link
Copy Markdown
Author

joeynyc commented May 18, 2026

Release-readiness follow-up pushed in joeynyc/t3code@d439ab14.

Validated locally:

  • Pi focused tests: 13 passed across Pi ACP support, provider, and adapter tests.
  • Provider settings browser test: 19 passed.
  • Server typecheck: passed.
  • Web typecheck: passed.
  • Windows NSIS build: produced release/T3-Code-0.0.24-x64.exe.
  • macOS arm64 DMG build: produced release/T3-Code-0.0.24-arm64.dmg.
  • DMG mount sanity check: mounted read-only, verified checksum, found T3 Code (Alpha).app, detached cleanly.

Addressed review feedback:

  • Disabled Pi interaction mode toggle because Pi ACP does not currently honor mode switching.
  • Fixed Pi ACP Windows root path handling and avoided emitting an empty PATH for relative commands.
  • Trimmed PI_CODING_AGENT_DIR before use.
  • Hardened Hermes YAML-style inline comment stripping.
  • Fixed provider settings update running state for live update advisories without an outdated candidate, with browser coverage.

Remaining external check: Vercel marketing deploy still fails with Authorization required to deploy, which appears to be a Vercel/GitHub permission gate rather than a code failure. Windows installer was built on macOS but not executed on a Windows host in this environment.

streamEvents,
} satisfies PiAdapterShape;
});
}
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.

Massive code duplication across Hermes and Pi implementations

Low Severity

PiAdapter.ts (~836 lines), PiDriver.ts (~175 lines), and PiTextGeneration.ts (~256 lines) are near-identical copies of their Hermes counterparts, differing only in provider name strings, the ACP runtime factory import, and one extra sanitizePiAssistantTextDelta call in the Pi adapter's ContentDelta handler. Over 1,200 lines of logic are duplicated verbatim between the two providers. A shared parameterized factory (taking provider name, ACP runtime builder, and optional text sanitizer) would eliminate this redundancy and reduce risk of inconsistent future bug fixes.

Additional Locations (2)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit d439ab1. Configure here.

@joeynyc
Copy link
Copy Markdown
Author

joeynyc commented May 19, 2026

Polish follow-up pushed in joeynyc/t3code@8eff67b2.

Changes:

  • Added Hermes/Pi setup checklist rows in provider settings: enabled, CLI/adapter detected, Pi binary, auth, and model visibility.
  • Added Copy diagnostics for Hermes/Pi provider cards.
  • Cleaned copy-to-clipboard toast language for setup commands, diagnostics, and update commands.
  • Made model picker empty states actionable for favorites, search, disabled providers, setup-needed providers, and ready providers with no models.
  • Added browser coverage for the no-model Hermes empty state and unit coverage for the setup checklist logic.
  • Added docs/providers/release-readiness.md and linked it from the README for Mac/Windows packaging validation and screenshot expectations.

Validation:

  • apps/web ProviderInstanceCard unit test: passed.
  • ProviderModelPicker + SettingsPanels browser tests: 46 passed.
  • apps/web typecheck: passed.
  • apps/server typecheck: passed.
  • apps/web production build: passed.

Note: I verified the provider settings polish in the paired in-app browser. A separate headless screenshot pass hit the app's pairing-token gate, so I did not replace the existing README chat screenshots in this commit.

Copy link
Copy Markdown
Contributor

@cursor cursor Bot left a comment

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 2 potential issues.

There are 4 total unresolved issues (including 2 from previous reviews).

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 8eff67b. Configure here.

return Effect.void;
}
return Ref.update(outputRef, (current) => current + content.text);
});
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 update notice not sanitized in text generation

Medium Severity

The sanitizePiAssistantTextDelta function strips Pi CLI update notices from chat output in PiAdapter.ts, but the same sanitization is missing from PiTextGeneration.ts. The handleSessionUpdate callback in text generation accumulates raw agent_message_chunk text without filtering, so if a Pi CLI update notice appears during commit message, PR content, branch name, or thread title generation, it will corrupt the accumulated output and likely break extractJsonObject JSON parsing or produce garbage results.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 8eff67b. Configure here.

}

const changedPath = resolve(srcDir, filename);
if (!changedPath.startsWith(`${srcDir}/`)) {
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.

Dev watch path check fails on Windows

Low Severity

The shouldRestart function checks changedPath.startsWith(${srcDir}/) using a forward slash, but on Windows resolve() produces backslash-separated paths. This causes the guard to always return false on Windows, meaning file changes will never trigger a server restart during development.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 8eff67b. Configure here.

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.

1 participant