From a8da26508757bc710dc083a6a216b6bd47f29d7d Mon Sep 17 00:00:00 2001 From: netcatty Date: Sun, 13 Sep 2026 15:47:07 +0800 Subject: [PATCH 1/2] Add Pi UI-prompt events so the pane reports waiting-for-user MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The shared Pi / Oh My Pi bridge only subscribes to four lifecycle events, so a pane running Pi never shows "waiting for you" while a dialog is open — `ask_user_question`, permission gates and any other extension prompt all run under the hood with the status dot still reading "working". tty7 already has the vocabulary for this (`question-asked`, `permission-request`), and `AgentEventKind::QuestionAsked` / `PermissionRequest` already map to `AgentStatus::Waiting` in `cli_agent.rs`. The Pi extension seam to feed them is `pi.on("ui_prompt_start")`, which fires around every blocking user-facing prompt with `event.kind` telling select / confirm / input / editor / custom apart. Two handlers, each guarded on its own so an Oh My Pi fork that does not expose the hook loses only that event rather than the whole bridge: - `ui_prompt_start` → `permission-request` for `kind === "confirm"` (a permission or destructive-action gate), `question-asked` otherwise. - `ui_prompt_end` → `prompt-submit` so the status returns to working once the dialog closes. Without it the pane would stay on "waiting" until the next stop, which is wrong for the model's continued work after an answer. Deliberately not included: `tool-complete`. The Pi bridge emits with `spawnSync`, so one event per tool call would block the extension host for the duration of a process spawn on every read/grep/edit. The test that asserts the bridge's subscriptions now covers both new event names. Verified: the `format!` template still compiles and renders both new handlers, and the bridge contains every string the test asserts. --- crates/tty7-core/src/core/agent_hooks.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/crates/tty7-core/src/core/agent_hooks.rs b/crates/tty7-core/src/core/agent_hooks.rs index e8865107..97cac9a6 100644 --- a/crates/tty7-core/src/core/agent_hooks.rs +++ b/crates/tty7-core/src/core/agent_hooks.rs @@ -1449,6 +1449,21 @@ export default function (pi: ExtensionAPI) {{ try {{ pi.on("session_start", (_event, ctx) => emit("session-start", ctx)); }} catch {{}} + // Pi-only: Oh My Pi may not expose the UI-prompt hooks, so each one is + // guarded on its own — a fork that rejects the event name must not take the + // rest of the bridge down with it. Without these the pane never reports + // "waiting for you" while a dialog is open, and the event vocabulary already + // carries question-asked / permission-request for exactly that. + try {{ + pi.on("ui_prompt_start", (event, ctx) => {{ + emit(event.kind === "confirm" ? "permission-request" : "question-asked", ctx); + }}); + }} catch {{}} + // The dialog closed, so the agent is working again. Without this the pane + // would stay on "waiting" until the next stop. + try {{ + pi.on("ui_prompt_end", (_event, ctx) => emit("prompt-submit", ctx)); + }} catch {{}} }} "# )) @@ -2205,6 +2220,8 @@ mod tests { "agent_start", "agent_end", "session_shutdown", + "ui_prompt_start", + "ui_prompt_end", ] { assert!( bridge.contains(&format!(r#"pi.on("{event}""#)), From 2de26bbf35e6d5c053a639bd099c7c224ea5265e Mon Sep 17 00:00:00 2001 From: l0ng-ai <24760907+l0ng-ai@users.noreply.github.com> Date: Mon, 14 Sep 2026 18:31:45 +0800 Subject: [PATCH 2/2] fix(pi): restore the pre-prompt status when a UI prompt closes ui_prompt_end sent prompt-submit unconditionally, but Pi also opens prompts while idle (/model, a command's select). Closing one left a finished or fresh pane reading "working" with no agent_end to clear it. Remember the last turn event and re-emit that instead. --- crates/tty7-core/src/core/agent_hooks.rs | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/crates/tty7-core/src/core/agent_hooks.rs b/crates/tty7-core/src/core/agent_hooks.rs index 97cac9a6..7d1aee08 100644 --- a/crates/tty7-core/src/core/agent_hooks.rs +++ b/crates/tty7-core/src/core/agent_hooks.rs @@ -1440,8 +1440,11 @@ export default function (pi: ExtensionAPI) {{ // Extension load = the agent is running in this pane. No context here yet, // so the id rides on session_start instead. emit("session-start"); - pi.on("agent_start", (_event, ctx) => emit("prompt-submit", ctx)); - pi.on("agent_end", (_event, ctx) => emit("stop", ctx)); + // What the pane showed before a UI prompt put it on "waiting", so closing + // the prompt can put it back. + let turn = "session-start"; + pi.on("agent_start", (_event, ctx) => emit((turn = "prompt-submit"), ctx)); + pi.on("agent_end", (_event, ctx) => emit((turn = "stop"), ctx)); pi.on("session_shutdown", (_event, ctx) => emit("session-end", ctx)); // Last, and guarded: the three above already worked, so a Pi build that // rejects this event name must not take them — or the whole extension — @@ -1459,10 +1462,12 @@ export default function (pi: ExtensionAPI) {{ emit(event.kind === "confirm" ? "permission-request" : "question-asked", ctx); }}); }} catch {{}} - // The dialog closed, so the agent is working again. Without this the pane - // would stay on "waiting" until the next stop. + // The dialog closed: restore what it interrupted. Not a blanket + // prompt-submit — Pi also prompts while idle (/model, a command's select), + // and that would leave a finished pane reading "working" with no turn to + // ever end it. try {{ - pi.on("ui_prompt_end", (_event, ctx) => emit("prompt-submit", ctx)); + pi.on("ui_prompt_end", (_event, ctx) => emit(turn, ctx)); }} catch {{}} }} "# @@ -2228,6 +2233,10 @@ mod tests { "{slug} bridge subscribes to {event}" ); } + assert!( + bridge.contains(r#"pi.on("ui_prompt_end", (_event, ctx) => emit(turn, ctx))"#), + "{slug} restores the pre-prompt status rather than forcing working" + ); } assert!( pi_extension_ts(&target, HookAgent::Claude).is_none(),