When an uncaught exception fires in interactive mode, the process dies with
stdin still in raw mode and the cursor hidden, leaving the user with a
"borked" terminal that needs `stty sane && reset` to recover. The most
common trigger is an extension's async ChildProcess `exit` callback that
throws (e.g. from accessing a stale ctx after session replacement), but
this affects any uncaught throw from anywhere in pi.
Add an uncaughtException handler in registerSignalHandlers that calls
ui.stop() before exiting, mirroring the existing emergencyTerminalExit
pattern. The handler is registered with prependListener and tracked in
signalCleanupHandlers, so it is removed on graceful shutdown the same way
the other handlers are.
Unlike emergencyTerminalExit (used for SIGHUP / dead-terminal EIO), the
terminal is still alive on uncaughtException, so we run the normal
ui.stop() to restore cooked mode, the cursor, bracketed paste mode, and
Kitty / modifyOtherKeys sequences.
* feat(ai): add Cloudflare Workers AI as a provider
Cloudflare Workers AI hosts open-weight LLMs (Kimi K2.6, GPT-OSS,
GLM-4.7, Llama 4, Gemma 4, Nemotron 3) on Cloudflare's GPU network with
an OpenAI-compatible endpoint. Reuses the openai-completions API
protocol; the per-account URL contains a {CLOUDFLARE_ACCOUNT_ID}
placeholder resolved at request time by a small helper.
Pi automatically sets x-session-affinity for prefix caching:
https://developers.cloudflare.com/workers-ai/features/prompt-caching/
Auth: CLOUDFLARE_API_KEY (matches pi's *_API_KEY convention) +
CLOUDFLARE_ACCOUNT_ID. The User-Agent identifies traffic as
'pi-coding-agent' in Cloudflare analytics.
Verified end-to-end against a real Cloudflare account: 17 e2e tests
pass across stream/empty/tokens/unicode/tool-call-without-result/
total-tokens against @cf/moonshotai/kimi-k2.6.
Cloudflare AI Gateway is a separate, larger change (it requires routing
through provider-specific subpaths with the matching API protocol per
upstream) and will land in a follow-up PR.
* refactor(ai): move Cloudflare User-Agent and session-affinity flag to per-model metadata
Instead of conditionally setting them in openai-completions.ts based on
provider detection, declare them as model-level fields in the catalog
(headers + compat). This is consistent with how the github-copilot and
kimi-coding entries already declare their static headers.
packages/ai/scripts/generate-models.ts: emit headers and compat fields
on each cloudflare-workers-ai entry (CLOUDFLARE_STATIC_HEADERS).
packages/ai/src/providers/openai-completions.ts: drop the
isCloudflareProvider conditional that injected User-Agent and the
isCloudflareWorkersAI override of sendSessionAffinityHeaders.
packages/ai/src/models.generated.ts: re-spliced 8 cloudflare-workers-ai
entries with headers + compat.
Behavior is unchanged - verified via fetch interceptor that User-Agent
and x-session-affinity / session_id / x-client-request-id are still sent
on outbound requests. 5/5 e2e tests pass.
Strip `index.ts` and `index.js` from extension display paths in both
compact and expanded views of the startup banner. This makes the list more
readable by removing redundant filename noise.
Changes:
- Add `formatExtensionDisplayPath()` helper to centralize path formatting
- Update `getCompactExtensionLabels()` to strip index files from segments
- Update expanded view formatting to use the new helper
- Add comprehensive tests for both views
Fixes#3549Fixes#3559
Custom footers registered via ctx.ui.setFooter(...) whose render()
closure touched ctx would crash pi on /quit: runtimeHost.dispose()
invalidates the extension runner before the TUI stops rendering, so
any still-attached footer render tick hit assertActive() and threw.
Call resetExtensionUI() before runtimeHost.dispose() in the interactive
shutdown path, matching the rebindCurrentSession() pattern. This
disposes the custom footer (clearing its interval) and detaches it
from the TUI tree before the runner goes stale.
closes#3595
Co-authored-by: Mario Zechner <badlogicgames@gmail.com>
Adds terminal.showTerminalProgress setting (default false) and wires it
to /settings. Guards all OSC 9;4 setProgress calls in interactive-mode.
closes#3588
Swaps hardcoded "pi" / ".pi" / "π" for the existing APP_NAME and
CONFIG_DIR_NAME extension points at four sites the original APP_NAME
rollout missed:
- /quit slash-command description
- process.title (cli.ts and bun/cli.ts)
- project-local extensions dir in loader.ts
For the terminal title, adds an APP_TITLE export in config.ts that
keeps pi's "π" glyph when piConfig.name is unset and falls back to
APP_NAME otherwise. Presence of piConfig.name acts as the "has been
rebranded" signal, so pi's own package.json drops the redundant
"name": "pi" (the existing `|| "pi"` fallback in config.ts makes this
a no-op for APP_NAME).
No behavior change for pi itself: process.title, /quit description,
terminal title prefix, and project-local extensions dir all resolve
to the same values as before.
closes#3476
Replace transient pendingWorkingMessage with a persistent workingMessage
field on InteractiveMode, matching the workingIndicatorOptions pattern.
New loaders now use this.workingMessage || this.defaultWorkingMessage
instead of always falling back to the default.
Also add working-message-test.ts extension example.
closes#3566