aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorRhys <105699450+rhjoh@users.noreply.github.com>2026-08-03 10:44:44 +1000
committerGitHub <noreply@github.com>2026-08-03 03:44:44 +0300
commit5eab32da81b403c2a277222ab97417a8bf90c35f (patch)
tree73cfbad865e949e48ea93b4bd58d482b2806e447 /src
parent10eb0bc744165ae91dfce9fd731fe170410b34ec (diff)
fix(pi): restrict state reporting to tui sessions (#2159)
Co-authored-by: Can Celik <ogulcancelik@gmail.com>
Diffstat (limited to 'src')
-rw-r--r--src/integration/assets/herdr-agent-state.test.ts25
-rw-r--r--src/integration/assets/pi/herdr-agent-state.ts6
-rw-r--r--src/integration/mod.rs2
-rw-r--r--src/integration/tests.rs2
4 files changed, 31 insertions, 4 deletions
diff --git a/src/integration/assets/herdr-agent-state.test.ts b/src/integration/assets/herdr-agent-state.test.ts
index f54332ce..94081e6d 100644
--- a/src/integration/assets/herdr-agent-state.test.ts
+++ b/src/integration/assets/herdr-agent-state.test.ts
@@ -171,6 +171,7 @@ for (const integration of integrations) {
{ reason: "startup" },
{
hasUI: true,
+ mode: "tui",
isIdle: () => true,
sessionManager: {
getSessionFile: () => undefined,
@@ -197,6 +198,7 @@ for (const integration of integrations) {
{ reason: "reload" },
{
hasUI: true,
+ mode: "tui",
isIdle: () => false,
sessionManager: {
getSessionFile: () => undefined,
@@ -268,6 +270,25 @@ test("Pi reports idle only after the agent settles", async () => {
expect(requestStates(requests)).toEqual(["idle", "working", "idle"]);
});
+test("Pi ignores RPC sessions even when UI APIs are available", async () => {
+ const requests = await startRecordingServer("pi-rpc");
+ const { handlers, pi } = createExtensionHarness();
+ const { default: install } = await importFresh("./pi/herdr-agent-state.ts");
+ install(pi);
+
+ const context = {
+ ...piContext(() => true),
+ hasUI: true,
+ mode: "rpc",
+ };
+ await handlers.get("session_start")?.({ reason: "startup" }, context);
+ handlers.get("agent_start")?.({}, context);
+ handlers.get("agent_settled")?.({}, context);
+ await Bun.sleep(25);
+
+ expect(requests).toEqual([]);
+});
+
test("Pi settlement preserves explicit blocked-state precedence", async () => {
const requests = await startRecordingServer("pi-settled-blocked");
const { eventHandlers, handlers, pi } = createExtensionHarness();
@@ -307,6 +328,7 @@ test("Pi reports the session replacement source", async () => {
{ reason: "new" },
{
hasUI: true,
+ mode: "tui",
isIdle: () => true,
sessionManager: {
getSessionFile: () => "/tmp/pi-new.jsonl",
@@ -370,6 +392,7 @@ test("Pi waits for a replacement session report before publishing state", async
{ reason: "new" },
{
hasUI: true,
+ mode: "tui",
isIdle: () => false,
sessionManager: {
getSessionFile: () => "/tmp/pi-new.jsonl",
@@ -489,6 +512,7 @@ test("Pi retries working state after an unanswered socket attempt", async () =>
{ reason: "startup" },
{
hasUI: true,
+ mode: "tui",
isIdle: () => false,
sessionManager: {
getSessionFile: () => undefined,
@@ -524,6 +548,7 @@ function completionHandlers(handlers: Map<string, Handler>): string[] {
function piContext(isIdle: () => boolean) {
return {
hasUI: true,
+ mode: "tui",
isIdle,
sessionManager: {
getSessionFile: () => undefined,
diff --git a/src/integration/assets/pi/herdr-agent-state.ts b/src/integration/assets/pi/herdr-agent-state.ts
index 2e79c7c6..67bc4188 100644
--- a/src/integration/assets/pi/herdr-agent-state.ts
+++ b/src/integration/assets/pi/herdr-agent-state.ts
@@ -2,7 +2,7 @@
// managed by herdr; reinstalling or updating the integration overwrites this file.
// add custom hooks/plugins beside this file instead of editing it.
// HERDR_INTEGRATION_ID=pi
-// HERDR_INTEGRATION_VERSION=7
+// HERDR_INTEGRATION_VERSION=8
// @ts-nocheck
import net from "node:net";
@@ -223,7 +223,9 @@ export default function (pi) {
});
pi.on("session_start", async (event, ctx) => {
- if (ctx?.hasUI !== true) {
+ // TUI only: RPC/JSON/print modes are headless (no PTY herdr can display),
+ // and RPC still reports hasUI=true, so mode is the reliable gate.
+ if (ctx?.mode !== "tui") {
return;
}
rootSession = true;
diff --git a/src/integration/mod.rs b/src/integration/mod.rs
index 9fc1886e..8be54c84 100644
--- a/src/integration/mod.rs
+++ b/src/integration/mod.rs
@@ -23,7 +23,7 @@ pub(crate) use types::{IntegrationRecommendation, IntegrationStatus, Integration
const PI_EXTENSION_INSTALL_NAME: &str = "herdr-agent-state.ts";
const PI_EXTENSION_ASSET: &str = include_str!("assets/pi/herdr-agent-state.ts");
-const PI_INTEGRATION_VERSION: u32 = 7;
+const PI_INTEGRATION_VERSION: u32 = 8;
const OMP_EXTENSION_INSTALL_NAME: &str = "herdr-omp-agent-state.ts";
const OMP_EXTENSION_ASSET: &str = include_str!("assets/omp/herdr-agent-state.ts");
const OMP_INTEGRATION_VERSION: u32 = 8;
diff --git a/src/integration/tests.rs b/src/integration/tests.rs
index 7abf7be2..6827dae7 100644
--- a/src/integration/tests.rs
+++ b/src/integration/tests.rs
@@ -2665,7 +2665,7 @@ fn bundled_integration_asset_versions_match_expected_versions() {
fn bundled_integration_assets_report_session_refs() {
assert!(PI_EXTENSION_ASSET.contains("agent_session_path"));
assert!(PI_EXTENSION_ASSET.contains("agent_session_id"));
- assert!(PI_EXTENSION_ASSET.contains("ctx?.hasUI !== true"));
+ assert!(PI_EXTENSION_ASSET.contains("ctx?.mode !== \"tui\""));
assert!(PI_EXTENSION_ASSET.contains("pane.report_agent_session"));
assert!(PI_EXTENSION_ASSET.contains("pane.report_agent\""));
assert!(PI_EXTENSION_ASSET.contains("pi.on(\"agent_start\""));