Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion packages/coding-agent/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

### Added

- Added `ui_prompt_start` and `ui_prompt_end` extension events so host integrations can distinguish active agent work from waiting on user-facing `ctx.ui` prompts ([#5329](https://github.com/earendil-works/pi/issues/5329)).
- Added transcript usage notices for compaction and branch summaries when cache miss notices are enabled.
- Added RPC `clear_queue` to retrieve and remove queued steering and follow-up messages ([#8432](https://github.com/earendil-works/pi/issues/8432)).
- Added environment variables and advanced settings for overriding auto-detected terminal hyperlink, image, and truecolor capabilities ([#8665](https://github.com/earendil-works/pi/issues/8665)).

Expand Down Expand Up @@ -32,7 +34,11 @@
- Added an optional `powershell` tool for Windows, configurable through `defaultTools` and the SDK. See [PowerShell Tool](docs/windows.md#powershell-tool).
- Added a `/thinking` selector and searchable default choices to the model and thinking selectors; Ctrl+S saves the selected model as the global default. See [Models and Thinking](docs/keybindings.md#models-and-thinking).
- Added optional routing session IDs to exported compaction summary helpers so callers can preserve provider routing without enabling prompt cache writes.
- Added transcript usage notices for compaction and branch summaries when cache miss notices are enabled.
- Added



usage notices for compaction and branch summaries when cache miss notices are enabled.
- Added `session_compact_failed` extension events so compaction failures and aborts expose their reason, retry state, source, and error message to handlers ([#8175](https://github.com/earendil-works/pi/issues/8175)).
- Added inherited provider-neutral `toolChoice` support to simple stream requests.
- Added inherited automatic Anthropic server-side refusal fallback for supported first-party models, including returned-model usage pricing ([#8017](https://github.com/earendil-works/pi/issues/8017)).
Expand Down
18 changes: 18 additions & 0 deletions packages/coding-agent/docs/extensions.md
Original file line number Diff line number Diff line change
Expand Up @@ -580,6 +580,24 @@ pi.on("agent_settled", async (_event, ctx) => {
});
```

#### ui_prompt_start / ui_prompt_end

Notification-only lifecycle events for blocking user-facing extension UI prompts. They fire around `ctx.ui.select()`, `ctx.ui.confirm()`, `ctx.ui.input()`, `ctx.ui.editor()`, and `ctx.ui.custom()` so host/status integrations can report "waiting for user" instead of just "running".

Nested or overlapping prompts are coalesced into one outer waiting span. Handlers are invoked best-effort and are not awaited before showing or closing the prompt.

```typescript
pi.on("ui_prompt_start", async (event, ctx) => {
// event.reason === "ui_prompt"
// event.kind: "select" | "confirm" | "input" | "editor" | "custom"
// event.title: prompt title when available
});

pi.on("ui_prompt_end", async (event, ctx) => {
// Pi is no longer waiting on that UI prompt span.
});
```

#### turn_start / turn_end

Fired for each turn (one LLM response + tool calls).
Expand Down
3 changes: 3 additions & 0 deletions packages/coding-agent/src/core/extensions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,9 @@ export type {
TreePreparation,
TurnEndEvent,
TurnStartEvent,
UIPromptEndEvent,
UIPromptKind,
UIPromptStartEvent,
// Events - User Bash
UserBashEvent,
UserBashEventResult,
Expand Down
52 changes: 51 additions & 1 deletion packages/coding-agent/src/core/extensions/runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ import type {
ToolCallEventResult,
ToolResultEvent,
ToolResultEventResult,
UIPromptKind,
UserBashEvent,
UserBashEventResult,
} from "./types.ts";
Expand Down Expand Up @@ -295,6 +296,8 @@ export class ExtensionRunner {
private shortcutDiagnostics: ResourceDiagnostic[] = [];
private commandDiagnostics: ResourceDiagnostic[] = [];
private staleMessage: string | undefined;
private uiPromptDepth = 0;
private activeUIPrompt: { kind: UIPromptKind; title?: string } | undefined;

constructor(
extensions: Extension[],
Expand Down Expand Up @@ -431,10 +434,57 @@ export class ExtensionRunner {
}

setUIContext(uiContext?: ExtensionUIContext, mode: ExtensionMode = "print"): void {
this.uiContext = uiContext ?? noOpUIContext;
this.uiContext = uiContext ? this.wrapUIPromptContext(uiContext) : noOpUIContext;
this.mode = mode;
}

private wrapUIPromptContext(ui: ExtensionUIContext): ExtensionUIContext {
return {
...ui,
select: (title, options, opts) => this.withUIPrompt("select", title, () => ui.select(title, options, opts)),
confirm: (title, message, opts) => this.withUIPrompt("confirm", title, () => ui.confirm(title, message, opts)),
input: (title, placeholder, opts) =>
this.withUIPrompt("input", title, () => ui.input(title, placeholder, opts)),
editor: (title, prefill) => this.withUIPrompt("editor", title, () => ui.editor(title, prefill)),
custom: (factory, options) => this.withUIPrompt("custom", undefined, () => ui.custom(factory, options)),
};
}

private withUIPrompt<T>(kind: UIPromptKind, title: string | undefined, run: () => Promise<T>): Promise<T> {
const outerPrompt = this.uiPromptDepth++ === 0;
if (outerPrompt) {
this.activeUIPrompt = { kind, title };
this.emitUIPromptEvent({ type: "ui_prompt_start", reason: "ui_prompt", kind, ...(title ? { title } : {}) });
}

const finish = () => {
if (--this.uiPromptDepth > 0) return;
this.uiPromptDepth = 0;

const prompt = this.activeUIPrompt ?? { kind, title };
this.activeUIPrompt = undefined;
this.emitUIPromptEvent({
type: "ui_prompt_end",
reason: "ui_prompt",
kind: prompt.kind,
...(prompt.title ? { title: prompt.title } : {}),
});
};

try {
return run().finally(finish);
} catch (err) {
finish();
throw err;
}
}

private emitUIPromptEvent(event: Extract<RunnerEmitEvent, { type: "ui_prompt_start" | "ui_prompt_end" }>): void {
queueMicrotask(() => {
void this.emit(event);
});
}

getUIContext(): ExtensionUIContext {
return this.uiContext;
}
Expand Down
22 changes: 22 additions & 0 deletions packages/coding-agent/src/core/extensions/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -742,6 +742,24 @@ export interface AgentSettledEvent {
type: "agent_settled";
}

export type UIPromptKind = "select" | "confirm" | "input" | "editor" | "custom";

/** Fired when Pi starts waiting on a blocking user-facing extension UI prompt. */
export interface UIPromptStartEvent {
type: "ui_prompt_start";
reason: "ui_prompt";
kind: UIPromptKind;
title?: string;
}

/** Fired when Pi is no longer waiting on a blocking user-facing extension UI prompt. */
export interface UIPromptEndEvent {
type: "ui_prompt_end";
reason: "ui_prompt";
kind: UIPromptKind;
title?: string;
}

/** Fired at the start of each turn */
export interface TurnStartEvent {
type: "turn_start";
Expand Down Expand Up @@ -1077,6 +1095,8 @@ export type ExtensionEvent =
| AgentStartEvent
| AgentEndEvent
| AgentSettledEvent
| UIPromptStartEvent
| UIPromptEndEvent
| TurnStartEvent
| TurnEndEvent
| MessageStartEvent
Expand Down Expand Up @@ -1263,6 +1283,8 @@ export interface ExtensionAPI {
on(event: "agent_start", handler: ExtensionHandler<AgentStartEvent>): void;
on(event: "agent_end", handler: ExtensionHandler<AgentEndEvent>): void;
on(event: "agent_settled", handler: ExtensionHandler<AgentSettledEvent>): void;
on(event: "ui_prompt_start", handler: ExtensionHandler<UIPromptStartEvent>): void;
on(event: "ui_prompt_end", handler: ExtensionHandler<UIPromptEndEvent>): void;
on(event: "turn_start", handler: ExtensionHandler<TurnStartEvent>): void;
on(event: "turn_end", handler: ExtensionHandler<TurnEndEvent>): void;
on(event: "message_start", handler: ExtensionHandler<MessageStartEvent>): void;
Expand Down
3 changes: 3 additions & 0 deletions packages/coding-agent/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,9 @@ export type {
ToolResultEvent,
TurnEndEvent,
TurnStartEvent,
UIPromptEndEvent,
UIPromptKind,
UIPromptStartEvent,
UserBashEvent,
UserBashEventResult,
WidgetPlacement,
Expand Down