Expose the Pi-compatible tui, rpc, json, or print host mode to every extension context and cover mode transitions in the runner regression suite.
Fixes#8419
- Replace the obsolete agent dashboard control center with a new fullscreen agents hub component.
- Integrate AI-assisted agent creation architect flow using model sessions.
- Provide interactive property strips and model browser integration for agent configuration.
- Update tests and documentation to support the redesigned agents hub.
- Added support for external thinking and forced reasoning disablement across AI provider options and request transformers.
- Implemented the private scratchpad think tool along with its renderer, system prompt rules, and schema configuration.
- Updated agent session management and SDK tools to support dynamic runtime activation of the think tool via the externalThinking setting.
- Added comprehensive unit tests covering reasoning fallbacks, tool activation, and rendering behavior.
showHookCustom hardcoded the overlay geometry and never read
overlayOptions/onHandle, which regressed the v0.45.6 API (PR
badlogic/pi-mono#667). Forward overlayOptions to showOverlay (keeping the
full-cover defaults as fallback), invoke onHandle with the returned
OverlayHandle, widen the options type via a shared ExtensionCustomOptions,
and re-export OverlayHandle/OverlayOptions from the extension API.
Replayed idle transcripts in bounded message and time chunks, painting cleared scrollback between macrotasks so terminal input remains responsive during restore and tree navigation. Kept streaming rebuilds atomic and migrated every rebuild caller to await completion.
Fixes#8133
The #7904 fix stopped masking provider errors as "Handoff cancelled", but
an empty or whitespace-only generation still fell through: whitespace-only
text passed the `!handoffText` guard and produced a bogus handoff, while
empty text returned undefined which the interactive /handoff caller mapped
to "Handoff cancelled" with no detail and no log entry.
Treat empty/whitespace-only output as a real failure: a user-initiated
handoff throws "Handoff generation produced no content" (surfaced as
"Handoff failed: ...") and logs it; auto-handoff keeps returning undefined
so maintenance falls back to context-full compaction. Also log genuine
handoff failures in the command controller so they persist for debugging.
Fixes#7993
- Gated model-issued approval dialogs on the TUI tool preview lifecycle.
- Finalized edit arguments before waiting for the asynchronous diff.
- Added regression coverage for both the preview gate and approval ordering.
Fixes#7957
The handoff catch in session-handoff.ts and the /handoff handler in
command-controller.ts mapped any error named AbortError to "Handoff
cancelled" regardless of whether the handoff signal was actually
aborted. Providers throw name-AbortError errors on non-user conditions
(stalls, idle timeouts, nested resolution failures), so a genuine
generation failure surfaced as a user cancellation and hid the cause.
Only report "Handoff cancelled" when handoffSignal.aborted is set;
re-throw the real error otherwise. The controller now trusts the
normalized "Handoff cancelled" message and drops its own AbortError
check so re-thrown provider failures render as "Handoff failed: ...".
Fixes#7903
app.tools.expand (Ctrl+O) was wired only through the editor's input path,
so when a tool-approval prompt or other selection dialog took keyboard
focus the key was delivered to that component and never reached the expand
handler — a large truncated edit could not be expanded while the user was
deciding whether to approve or deny it.
Promote the shortcut to a global TUI input listener (matching the existing
debug and branch/copy shortcuts) so it fires regardless of focus. It defers
when the main transcript is not the active surface (a fullscreen/anchored
overlay: agent hub, transcript viewer, log viewer, model picker) or when the
focused component rebinds Ctrl+O for its own use (the tree selector's filter
cycle). The editor-scoped handler is removed as a clean cutover.
Fixes#7837
- Routed session tool provenance through live and rebuilt transcript render paths.
- Kept same-named extension tools on the generic renderer while preserving native tool rendering.
- Added regression coverage for an external recall result collision.
Fixes#7770
The ExtensionAPI getAllTools() wired to session.getAllToolNames(),
returning bare tool-name strings. Upstream @earendil-works/pi-coding-agent
promises ToolInfo[] with sourceInfo, so extensions loaded through the
legacy-pi shim (e.g. gentle-pi) crashed on t.sourceInfo.source at every
session start.
Added SourceInfo/ToolInfo types plus SessionTools.getAllToolInfos(), which
returns { name, description, parameters, sourceInfo } and classifies each
tool as builtin/mcp/sdk/extension. Rewired every getAllTools action site
(interactive, acp, print/rpc, subagent executor) and the example extension.
Fixes#7732
- Implemented in-house, zero-dependency utility modules in `pi-utils` covering DOM manipulation, markdown parsing, templating, browser automation helpers, and terminal buffers.
- Migrated packages across the repository to consume the new internal utilities and `omptype` schema validators instead of external dependencies.
- Removed multiple external runtime and development dependencies including Zod, Marked, LRU cache, Turndown, and Puppeteer browser packages.
Repeated /mcp reauth commands could remain blocked by a prior
unfinished login because each command receives a fresh controller.
Coordinate flows through session-shared state so a replacement cancels
and cleans up the old flow before proceeding.
After the fullscreen Plan Review closed on approve-and-execute, the
conversation view stayed blank while the plan ran. The propose write's
tool_execution_end handler runs inside EventController's serialized
dispatch chain and awaited handlePlanApproval, which awaits session.prompt
for the entire execution turn, so every later agent_start/message_start/
tool/message_update event queued behind it until the run finished.
Detach the approval dispatch so the dispatch link settles immediately and
the execution turn's events render live. Follow-up to #5688, which only
moved the overlay close before the still-blocking dispatch.
Fixes#7684
The previous #runSerialized waited on the shared #dispatchTail, then ran
unconditionally: when two or more events queued behind an in-flight run,
each resumed from the same settled await and started its own run in
parallel, defeating the ordering guarantee for a burst landing in one
coalescing window (message_end + agent_end behind a suspended flush).
Each waiter now chains its own link onto the current tail
(tail.then(run, run)), so queued runs start strictly one after another;
the idle path still runs synchronously, preserving the flush timing the
coalescing tests assert on. The in-flight flag clears only when the
settling link is still the tail, so a later chained link's settle does
not clear it early.
Regression test: two message_end events queued behind a suspended window
flush stay serialized (init call count steps 1 -> 2 -> 3 as each gate
opens); fails on the previous implementation.
AgentSession.#emit fires listeners fire-and-forget, and the coalesced
message_update flush fires from its own 33ms timer — neither path awaited
the other. A rapid stream tail (message_update -> message_end ->
agent_end) could therefore run the end handlers while the flush was
suspended mid-await, agent_end removing streamingComponent before
#handleMessageEnd finalizes and records the final message (issue #7443
follow-up).
- #runSerialized chains listener dispatch and the timer flush through one
promise chain; an in-flight run holds later events until it completes.
Idle dispatch stays synchronous (no added microtask), preserving the
timing the coalescing tests assert on.
- Regression test: a message_end landing while the window flush is
suspended on init is queued behind it (initCalls 1 while suspended,
then 2), where the pristine code ran both concurrently (2 while
suspended). Fails without the fix.
Reaching the `isTerminal === false` branch means the superseded-turn guard
above it already passed, so `session.isStreaming` is false: a command
issued from that point mounts immediately while panels queued earlier in
the turn stay in `#pendingCommandOutput` until some later terminal
agent_end. Newer output rendered ahead of older, and the queued panel
could strand for minutes on an async fan-out that keeps settling
non-terminally.
Flush there too. The transcript is quiescent at a settle, which is the
condition #4806 wanted, and the notice now says "until the agent pauses"
rather than promising the current turn.