Add Pi UI-prompt events so the pane reports waiting-for-user

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.
This commit is contained in:
netcatty
2026-09-13 15:47:07 +08:00
parent 66c42e48b7
commit a8da265087
+17
View File
@@ -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}""#)),