diff --git a/docs/plugins.md b/docs/plugins.md index 5362b88..933cb17 100644 --- a/docs/plugins.md +++ b/docs/plugins.md @@ -36,6 +36,7 @@ Pi extensions running under PI WEB's session daemon can ask the user questions w - **Works from hooks, without the prompt queue.** Answers travel over a dedicated session-daemon channel, so a dialog opened inside an in-flight `tool_call` hook parks safely — the agent loop waits for the hook and the run continues with the answer. Consent-gating a tool from a `tool_call` hook is a supported pattern. - **`session_start` dialogs are reachable.** A dialog opened from a `session_start` hook is answerable while the session is still starting, both when creating a session and when opening an existing one; startup completes once the dialog settles. - **Survives browser reloads; first answer wins.** Reloading the browser re-renders open dialogs from the session status. With several tabs on the same session, the first answer settles the dialog and the other tabs re-render the settled card. +- **Settled cards stay until dismissed.** An answered or closed dialog leaves its outcome card in the transcript so the user can see what became of it — answers travel to the extension alone, so the card is the only record of the exchange. The card is browser-local: only a browser that saw the dialog open renders it, and switching sessions or reloading drops it. - **Timeouts.** The extension's own `timeout` option applies, and the daemon adds an unattended-dialog safety valve, `extensionDialogsTimeoutMs` (default 5 minutes, `0` waits forever — see [Extension dialogs](https://pi-web.dev/config#extension-dialogs)). The effective deadline is the sooner of the two. A dialog that closes without an answer resolves with its kind's cancel value: `false` for confirm, `undefined` for select and input. - **Abort and runtime replacement.** Aborting the current run settles a dialog opened during that run immediately, at abort-request time, with its cancel value. Replacing the session runtime (`/reload`, session disposal) settles any still-open dialog the same way; hooks on the new runtime open fresh dialogs. The extension's own `AbortSignal` is honored: aborting it dismisses the dialog and resolves with the cancel value. - **Other UI surfaces are still no-ops.** `ExtensionUIContext` methods beyond the three dialogs (widgets, status, editor, `custom`) remain unimplemented under PI WEB even though `hasUI` is `true`; do not rely on `hasUI` alone to detect them. diff --git a/src/client/src/appState.ts b/src/client/src/appState.ts index 74a6abe..aa56295 100644 --- a/src/client/src/appState.ts +++ b/src/client/src/appState.ts @@ -46,9 +46,10 @@ export interface AppState { pendingDialogs: PendingExtensionDialog[]; /** * Dialogs that closed while their session was selected, kept with the close - * reason and any answer so the card can render its outcome briefly. The wire - * outcome is deliberately small, so only a browser that saw the dialog open - * can show the closed card; deselection and reloads drop these. + * reason and any answer so the settled card can show what became of the + * dialog. The card stays until the user dismisses it. The wire outcome is + * deliberately small, so only a browser that saw the dialog open can show + * the closed card; deselection and reloads drop these. */ closedDialogs: ClosedExtensionDialog[]; /** Thinking levels available for the selected session's current model. */ diff --git a/src/client/src/components/ExtensionDialogCard.ts b/src/client/src/components/ExtensionDialogCard.ts index fb51096..f33a5f8 100644 --- a/src/client/src/components/ExtensionDialogCard.ts +++ b/src/client/src/components/ExtensionDialogCard.ts @@ -76,7 +76,8 @@ export function extensionDialogCountdownText(timeoutAt: string | undefined, nowM * The card owns only browser-local form state (the half-typed input, the * in-flight close flag, the display-only countdown); the daemon remains the * source of truth for whether the dialog is open. Closed mode renders the - * transient outcome for a browser that saw the dialog open. + * settled outcome — a browser-local record that stays until dismissed — for a + * browser that saw the dialog open. */ @customElement("extension-dialog-card") export class ExtensionDialogCard extends LitElement { diff --git a/src/shared/apiTypes.ts b/src/shared/apiTypes.ts index 01285a7..38fe4c6 100644 --- a/src/shared/apiTypes.ts +++ b/src/shared/apiTypes.ts @@ -656,9 +656,9 @@ export interface PendingExtensionDialog { /** * The complete result of a closed extension dialog. Unlike an ask outcome it - * stays small — the dialog itself is not embedded, because a closed dialog - * renders only transiently for browsers that saw it open; reloads rehydrate - * open dialogs from {@link SessionStatus.pendingDialogs} alone. + * stays small — the dialog itself is not embedded, because a settled card is a + * browser-local record that stays until the user dismisses it; reloads + * rehydrate open dialogs from {@link SessionStatus.pendingDialogs} alone. */ export interface ExtensionDialogOutcome { dialogId: string;