Replace build-time shell utilities with Node filesystem operations and retire the native sandbox example in favor of existing isolation options. Upgrade glob matching and worker-test dependencies and refresh the lockfiles.
This PR uses unicode-aware segmentation and normalization to handle tool
search for multilingual text.
Fixes#10816
---------
Co-authored-by: Petr Houška <houskape@gmail.com>
Follow-up to #11625 (#11331). Same change in durable's copy: #11631.
Problem: #11625 made a query term in a tool's name outweigh any number
of description mentions. That regressed queries where another tool
matches more of the query in its description:
- `screenshot window`: `peekaboo__window` (name has `window`) beat
`peekaboo__image` ("Take a screenshot of the screen, an app, or a
window.").
- `describe app`: `peekaboo__app` (whole name `app`) beat
`peekaboo__see` ("... describe the UI elements of the app.").
Fix: `Bm25Ranker` sorts by coverage first: a tool matching more of the
query's distinct terms, in any field (name, description and schema,
namespace), ranks above one matching fewer, whatever the scores. Within
the same coverage, the score decides as before. A query that is exactly
a tool's name still ranks that tool first.
On the 16-tool set from #11631 (5 servers, 16 queries), this fixes both
regressions and changes nothing else; `search github issues` stays at
rank 5, since `list_issues` lacks "search" and ties on coverage with
`search_code` and `linear__search_issues`.
Changelog: updates the unreleased #11331 entry from #11625.
Fixes#4748.
## Problem
Extensions that import `keyText`/`keyHint` from
`@earendil-works/pi-coding-agent` can resolve their own copy of the
package (their own `node_modules`, or the bundled `cli.js` vs
`dist/index.js`). That copy's keybindings are never set by the host, so
it falls back to a manager with only `tui.*` bindings, and
`keyHint("app.tools.expand", "to expand")` renders as ` to expand`.
## Change
The host passes its own `keyText` and `keyHint` to extension renderers,
the same way it passes `theme`:
- `ToolRenderContext` (`renderCall`/`renderResult` context):
`context.keyHint(...)`
- `MessageRenderOptions` and `EntryRenderOptions`:
`options.keyHint(...)`
All three extend a new exported `KeybindingHints` interface. The docs
and the `debug-provider` example now use the injected functions and warn
against importing them in renderers.
Not done:
- Moving the `pi-tui` keybindings singleton to `globalThis`. Per the
discussion in #7011, extension-facing state should be host-owned and
passed through the extension API instead of shared through process-wide
module state.
- `ctx.ui.keyText`/`keyHint`. No reported case needs them outside
renderers, RPC and print modes never install app keybindings, and adding
them to `ExtensionUIContext` would break SDK hosts that implement it.
## Tests
- `test/extension-keybinding-hints.test.ts`: tool, message, and entry
renderers format a user-overridden `app.tools.expand` binding.
- `npm run check` passes.
Refs #7756, #5607.
Problem: when a wrapper or another package manager provides Pi (mise, a
`PNPM_HOME` wrapper such as nub, Nix), `pi update` cannot update it and
prints "pi cannot self-update this installation." Users with several
machines have to remember which tool updates Pi on which machine.
Example:
```sh
export PI_SELF_UPDATE_COMMAND="mise use -g npm:{packageName}@{version}"
pi update
# Updating pi with mise use -g npm:@earendil-works/pi-coding-agent@1.4.0...
# Updated pi from 1.3.0 to 1.4.0
```
or in `~/.pi/agent/settings.json`:
```json
{ "selfUpdateCommand": ["nix", "profile", "upgrade", "pi"] }
```
Behavior:
- `pi update` still checks pi.dev first. The command runs only when a
newer release exists, or with `--force`, and the release note prints
first.
- Placeholders come from that release: `{packageName}`, `{version}`,
`{installSpec}` (`<packageName>@<version>`). A package rename announced
by pi.dev flows into them.
- `PI_SELF_UPDATE_COMMAND` takes precedence and is split on whitespace
(no quoting). The setting is argv, for arguments with spaces, and is
read only from global settings, so a project's `.pi/settings.json`
cannot inject a command.
- The configured command replaces Pi's own update logic for every
install method, including installer-managed installs: it is explicit
user intent.
- Exit code 0 does not prove an update. Pi reports "Updated pi from X to
Y" when its own `package.json` now shows Y (in-place updates) or when
`pi --version` from PATH reports Y (wrappers such as mise or Nix that
install each version into a new directory). Otherwise it warns: ``Ran
<cmd>, but `pi --version` reports X, not Y.``
- "cannot self-update this installation" now mentions the env var and
setting.
Docs: `docs/cli.md` (new "Custom update command" section),
`docs/settings.md`, `docs/environment-variables.md`, `--help` env list,
regenerated `schemas/settings.schema.json`.
Tests: unit tests for argv resolution and placeholders in
`test/config.test.ts`; CLI tests in `test/package-command-paths.test.ts`
for env precedence over the setting and the managed install, global-only
setting gated by the version check, and command failure. `npm run check`
passes.
Manual end-to-end check with real mise 2026.10.23 in a sandboxed HOME,
running this branch as the mise-installed 1.3.0 (`PI_PACKAGE_DIR`
pointing at mise's install):
| Scenario | Result |
|---|---|
| `mise use -g npm:{packageName}@{version}` | mise installs 1.4.0;
`Updated pi from 1.3.0 to 1.4.0` |
| Already 1.4.0 | `pi is already up to date (v1.4.0)`, mise not run |
| In-place `npm install -g --prefix <dir> {installSpec}` via the setting
| `Updated pi from 1.3.0 to 1.4.0` |
| Command `true` (exit 0, installs nothing) | ``Ran true, but `pi
--version` reports 1.3.0, not 1.4.0.`` |
This PR is AI-generated.
Follow-up to #11440 for the latest comment on #11331.
Problem: the ranker scored a tool as one bag of words. A query term in
the tool's name counted like a mention in its description, and the
server's instructions were copied into every tool's text, which
lengthened it and lowered its score. Models often add words to a tool
name, so `peekaboo app` ranked `mcp__peekaboo__app` 14th: other peekaboo
tools mention "app" in shorter descriptions, and its own description
says "applications".
Fix: `Bm25Ranker` scores three fields separately:
- name: a query term in the name counts 3x its IDF; the description part
of BM25 is at most 2.2x, so a name match beats any number of description
mentions;
- description and schema text: BM25 as before, length-normalized on this
field only;
- namespace name, description, and instructions: 0.5x IDF per matching
term, without length normalization.
A tool whose whole local name (last `__` segment) is in the query gets a
bonus worth one more match of every query term, so it mostly wins
(`peekaboo app`, `describe app`, `read thread_read state`). A query that
is exactly a tool's name still ranks it first (#11440). Applies to
`tool_search` and codemode `searchTools()`.
Not included: the comment's second point (loading a deferred tool on a
direct call instead of `Tool X not found`). That error comes from
`prepareToolCall` in `packages/agent`, which has no hook for unknown
tools; it needs an agent-loop API change.
Fixes#11592.
## Problem
WSL starts Windows programs in the current directory, translated to
`\\wsl.localhost\<distro>\<cwd>`. When that path exceeds MAX_PATH, every
Windows program fails to start with `Invalid argument`. pi inherits its
cwd when spawning `powershell.exe`, so in a deep directory:
- clipboard image paste returns `null` (the PowerShell backend fails,
stderr is discarded)
- copy via `powershell.exe` (WSL without WSLg) fails
- opening the browser for sign-in fails (`xdg-open` on WSL runs
`explorer.exe`)
Reproduced on WSL (FedoraLinux-44): `powershell.exe` and `cmd.exe` work
from a 225-char cwd and fail from 230. With an image on the Windows
clipboard, `readClipboardImage()` returns the PNG from `/tmp` and `null`
from a 245-char cwd; with this change it returns the PNG from both.
`LongPathsEnabled=1` does not help.
## Changes
- `runClipboardCommand` and `openBrowser` run from `/` on POSIX. These
tools never use the cwd (temp files are passed by absolute path), and
`/` is always the shortest UNC path. `tmpdir()` was not used because
`$TMPDIR` can itself be deep.
- The external editor keeps pi's cwd on purpose, so project-relative
paths and integrated terminals keep working. A Windows editor such as
`code` can still fail from a deep WSL directory, as with git's
`core.editor`. A comment documents this.
- External editor failures were silent: the UI redrew over the error
immediately. pi now shows `Failed to launch external editor: <cmd>`,
`External editor exited with code N; prompt unchanged`, or the signal
name. This shows in the status line for the main editor and inline in
the extension editor dialog.
## Testing
- New tests: clipboard commands run from `/` (POSIX); an editor that
cannot be launched is reported (POSIX); the existing failure test checks
the message.
- Verified on WSL by running the real modules: clipboard image paste
from a deep cwd, and editor success, exit code 1, launch failure, and
SIGTERM.
- On Windows, the 3 existing `external-editor.test.ts` tests already
fail on `main`: the editor command is split on spaces, so `C:\Program
Files\nodejs\node.exe` breaks. Not addressed here.
This PR is AI-generated.
Fixes#7756.
Problem: `detectInstallMethod()` classifies any path containing `/pnpm/`
as pnpm. An install that only lives under `PNPM_HOME`, for example a
wrapper under `$PNPM_HOME/global-nub/5/node_modules/...`, is detected as
pnpm. `pi update` then correctly refuses to self-update (the path is not
under `pnpm root -g`), but `getSelfUpdateUnavailableInstruction()`
printed:
```
This installation is not managed by a global pnpm install. Update it with the package manager, wrapper, or source checkout that provides it.
```
Fix: name the package manager only when it owns the install (managed but
not writable). All other cases fall through to the neutral message:
```
Update @earendil-works/pi-coding-agent using the package manager, wrapper, or source checkout that provides this installation.
```
`detectInstallMethod()` is unchanged: every user-facing path already
goes through the ownership check, and returning `unknown` would trip the
Windows "only supported for npm and pnpm" guard with a worse message.
The update banner already says `Run pi update` and no longer suggests
`pnpm install -g`.
The pi.dev installer is not affected: it verifies npm ownership with
`npm ls -g` and never infers pnpm from paths.
Tests: regression test in `test/config.test.ts` (fails without the fix).
`npm run check` passes.
This PR is AI-generated.
oauth.clientRegistration defaults to auto: pi's Client ID Metadata Document
when the authorization server supports it for public clients, otherwise
dynamic client registration. Registered clients stay in use. pi mcp add
gains --oauth-client-registration.
closes#11426
Fixes#10488
## Problem
On Windows, `c:\Users\me` and `C:\Users\me` are the same directory, but
pi compared paths as exact strings (`===`, `startsWith`, map and set
keys). `realpathSync` keeps the input casing, so canonicalizing does not
help.
The reported case, with cwd `c:\Users\me\project` and home
`C:\Users\me`:
1. Ancestor discovery finds `c:\Users\me\.agents\skills`. The exclusion
of the user dir `C:\Users\me\.agents\skills` does not match, so global
skills load as project skills.
2. The same dir loads again as user skills. Dedup does not match either,
so every skill is reported as a name collision.
3. `hasTrustRequiringProjectResources` treats `~/.agents/skills` as a
project resource that requires trust.
The same pattern affected other places:
- Saved trust decisions in `trust.json` were not found when cwd casing
differed, so pi asked for trust again.
- With a custom session dir, `--continue`, resume by id, and the session
list hid sessions recorded with different cwd casing.
- Git worktree `AGENTS.md` shadowing failed when cwd casing differed
from git's paths.
- Local package matching (`pi remove c:\pkg` vs configured `C:\pkg`) and
package identity across global and project settings.
- Extension preload matching, skill scope classification, resource
source info, the config and session selector keys, and the session
import self-check.
Separately, home-to-`~` shortening used an unanchored `startsWith(home)`
in four places, so `/home/me2/x` was shown as `~2/x`.
## Fix
New helpers in `utils/paths.ts`, all based on one comparison key:
- `getPathComparisonKey(path)`: the resolved path, lowercased on
Windows. It does not touch the filesystem.
- `pathsEqual(a, b)`
- `isPathWithin(parent, child)`: true for equal paths or paths inside
`parent`, matching whole path segments.
- `shortenHomePath(path, home?)`: replaces a leading home dir with `~`.
The helpers pick `path.win32` or `path.posix` from `process.platform` at
call time, so tests can exercise real Windows paths (drive letters,
UNC).
`getResourcePathKey(path)` in `source-info.ts` is the dedup key for
resources. Synthetic identifiers (`builtin:<name>`, `<inline:...>`) stay
unchanged, because they are not filesystem paths and their lookup is
case-sensitive. File paths go through realpath plus the comparison key.
The helpers are used only for comparison; stored and displayed paths are
unchanged. Trust store lookups and `set` match keys case-insensitively
on Windows.
Unchanged on purpose:
- The package install-root guards (`resolveManagedPath`,
`pruneEmptyGitParents`, the `node_modules` dependency check) keep exact
string checks. Both sides come from the same root string, and exact is
the stricter option.
- Checks based on `path.relative()` (`getCwdRelativePath`, the changelog
containment check) were already correct: Node's `path.win32.relative`
ignores case.
NTFS can enable per-directory case sensitivity, but it is rarely used
outside WSL interop, so it is ignored (see the comment on
`getPathComparisonKey`).
## Tests
- `test/paths.test.ts`: Windows drive-letter, UNC and root cases, POSIX
case sensitivity, segment-boundary containment, and home shortening,
including root and UNC homes.
- `test/source-info.test.ts`: file paths fold case on Windows; builtin
and inline identifiers keep their case.
- `test/trust-manager.test.ts`: the `.agents/skills` exclusion with a
lowercase `HOME`, plus saved trust decisions matched and replaced across
casing (simulated win32).
An end-to-end test of the reported loader scenario is not feasible on
Linux: the loader resolves paths with POSIX `node:path`, and the
filesystem is case-sensitive.
`npm run check` passes. The coding-agent suite passes, except for 13
failures that also occur without this change: clipboard tool detection
in this environment, and one test that needs built `dist/` output.
A saved Do not trust decision was ignored when the project had no
trust-requiring resources, so ctx.isProjectTrusted() returned true.
The saved decision now takes precedence over auto-trust.
closes#10947
Some Apple Terminal setups send ESC+CR for Shift+Enter, which parsed as
alt+enter and queued a follow-up. The native Shift fallback now covers
ESC+CR as well as plain CR.
fixes#11559
In regular TUI mode, user and assistant messages wrote OSC 133
(`A`/`B`/`C`) shell-integration markers straight to the terminal.
Terminals that display OSC 7501 program status (Rex, iTerm2 3.7.5+) read
`133;A` as a new shell prompt and drop the current status. As a result,
the `working` indicator disappeared right after a run started and only
`done` showed up at the end. Fullscreen mode was not affected because it
already strips these markers before writing.
## Change
- `TuiMainScreen.doRender()` (`packages/tui/src/tui-main-screen.ts`) now
strips OSC 133 markers from the start of each rendered line before
compositing and writing. It uses the same pattern as fullscreen
(`OSC133_ZONE_PREFIX`).
- The message components still produce the markers. Fullscreen uses them
internally for prompt navigation and already strips them before writing,
so its behavior is unchanged.
- In regular mode, the terminal's own jump-to-prompt shortcuts no longer
stop at pi messages (added in #1805).
## Testing
- New test in `packages/tui/test/tui-render.test.ts`: no `ESC]133;`
reaches the terminal across a full render and a differential render,
with both BEL and ST terminators. It fails without the change and passes
with it.
- These pass:
- `packages/tui` `npm test`
- `test/user-message.test.ts` and `test/assistant-message.test.ts` in
coding-agent
- `npm run check`
- I ran `pi -ne --tui-mode regular` with `PI_PROGRAM_STATUS=1` and a
streaming test provider, capturing all terminal output with `script`.
- Before: `7501 working`, then repeated `133;A`/`133;B`/`133;C`, then
`7501 done`.
- After: only `7501 idle` → `working` → `done`, and no OSC 133.
Fixes#11519
Requested by @davidbrai
Co-authored-by: triagent <triagent@localhost>
In sessions created by `createAgentSession`, compaction, branch
summaries and bug-report summaries resolved the stored OAuth credential
in `AgentSession._getSummarizationRequestAuth`, then passed the access
token on as `options.apiKey`. The SDK's stream function calls
`modelRuntime.streamSimple`, which resolves auth again. Because it was
given an explicit `apiKey`, `resolveProviderAuth` took the API-key
override branch instead of using stored OAuth. Built-in providers accept
the token either way. A provider whose API-key path differs from its
OAuth path, such as one that refuses explicit keys, failed every summary
while normal prompts worked.
`createAgentSession` now marks its stream function with
`markRuntimeStreamFunction`. This records it in a module-level `WeakMap`
keyed by the function, mapped to its `ModelRuntime`.
`_getSummarizationRequestAuth` skips pre-resolution when the agent's
stream function is marked for the session's own runtime, so that runtime
resolves auth once, as it does for normal prompts. The check runs after
virtual-model routing. Bare `streamSimple` and custom `streamFn`s still
receive resolved credentials, so the fixes for #6324 and #7579 are
unchanged. If a caller replaces `agent.streamFunction`, the old behavior
applies.
**Testing**
- Added `test/suite/regressions/11520-sdk-summary-oauth-auth.test.ts`.
It builds an SDK session on a provider that has stored OAuth and also an
API-key method, runs a branch summary, and asserts two things: no
`getAuth` call carries an `apiKey` override, and the provider receives
the OAuth access token. It fails without the change and passes with it.
- `npm run check` passes and `./test.sh` passes. I also ran the related
tests: 6324 branch-summary auth, compaction model overrides,
auto-compaction cancellation, SDK stream options, virtual models, and
the compaction and branch-summary tests.
- Manual reproduction: an SDK script with an `openai` OAuth credential
that logs `ModelRuntime.getAuth` during `navigateTree(..., { summarize:
true })`. Before the change there were two calls, the second with
`apiKey = <oauth token>`. After the change there is one call, with no
override.
Fixes#11520
Requested by @badlogic
Co-authored-by: triagent <triagent@localhost>
Closes#11360.
## Summary
- Cap combined stdout/stderr with `ExecOptions.maxBuffer`, defaulting to
the smaller of 256 MiB and the runtime string limit. Invalid explicit
limits reject before spawning.
- Terminate oversized commands and return `truncated: true`, `killed:
true`, and a nonzero exit code. Close output pipes so writing
descendants cannot keep the call pending, and discard incomplete UTF-8
suffixes rather than adding replacement characters.
- Fix SIGKILL escalation for children that ignore SIGTERM, and simplify
completion to `await` with one cleanup block and result.
- Document the API and add regressions for byte limits, shell
descendants, UTF-8 chunk boundaries, and 32-bit runtime limits.
`ExecResult.truncated` is required. Existing extensions reading
execution results continue working; typed mocks or wrappers constructing
results must add the field.
## Verification
- `npm run build` and the post-rebase `npm run build:offline`
- `npm run check`
- 39 focused execution and extension tests
- `./test.sh` passes after building the env daemon. Earlier runs hit
remote-cleanup and reftable-watcher timeouts; both passed in focused
reruns and the final full run.
stripAnsi treated <, = and > after ESC [ as final bytes, so ESC[<35;59;1M
(SGR mouse report) left 35;59;1M in tool output. Accept one private prefix
byte only directly after a CSI introducer (ESC [ or 0x9B). After a bare ESC
these bytes stay final bytes, so ESC= and ESC> (keypad modes) keep the
digits of the text that follows.
closes#11377
tui.select.pageUp/pageDown were configurable but ignored by SelectList,
SettingsList, editor autocomplete, and most coding-agent selectors. In
fullscreen mode, the default PageUp/PageDown also scrolled the transcript
instead of reaching the focused list.
- add getSelectPageIndex() and handle page keys in SelectList, SettingsList,
editor autocomplete, and the model, scoped models, auth, user message,
settings submenu, llama search, and /mcp selectors
- add Component.capturesViewportInput: a focused component that sets it
receives fullscreen viewport keys instead of the transcript, as a focused
overlay already does
- let page keys move an open autocomplete menu before app actions, so a
remapped Ctrl+V pages the menu instead of pasting an image
closes#7629
Co-authored-by: muyiyr <940955635@qq.com>
Addresses #5958.
Adds `changelogPath` to `PiManifest`. Safe package-relative changelog
resolution, with `CHANGELOG.md` fallback, and carries it through
`PathMetadata`/`SourceInfo`.
Added extension changelog handling like Pi’s own changelog.
If the package exposes `pi.changelogPath` or has `CHANGELOG.md`, Pi
stores the last seen package version in settings. On each fresh
interactive launch, we compare the stored version to the installed
package version. Then parses the changelog entries in between, shows an
Extension Updates section, then marks the current version as seen. The
first time an extension is seen, its version is recorded without showing
a changelog, same as Pi's own on a fresh install. `collapseChangelog`
condenses this to one line per package.
Any update that bumps the extension's version shows this on the next
launch, e.g.:
```bash
pi update --extensions
pi update --all
pi update <extension-source>
pi update --extension <extension-source>
pi update --self --extensions
pi update pi --extensions
pi update self --extensions
```
<img width="798" height="626" alt="Screenshot 2026-08-28 at 16 34 15"
src="https://github.com/user-attachments/assets/c882f01c-94b9-4857-b9b7-243d88c0d437"
/>
The system prompt lists tool snippets and per-tool guidelines in the
order of the active tools. When an extension called
`pi.setActiveTools()` with the same tools in a different order (for
example `[...pi.getActiveTools()].reverse()`), the "tools" and "rules"
sections came out different. pi then recorded a "System Prompt Update:
tools, rules" message even though the active tools had not changed. On
providers without mid-conversation system messages, this also rewrites
the prompt head and loses the cache.
`_applyToolLoadout` in `agent-session.ts` now treats the requested names
as a set:
- tools that stay active keep their current order;
- newly activated tools are appended at the end.
Reordering the same set therefore leaves the prompt unchanged. Adding a
tool only appends its bullet. The same rule covers tool lists changed by
`before_agent_start` handlers, since they go through the same function.
Testing:
- Added a test in `test/system-prompt-updates.test.ts`: it reverses the
active tools between two prompts and expects a single system message. It
fails without the change (a second message patches `tools` and `rules`)
and passes with it.
- `npm run check` passes.
- `./test.sh`: all coding-agent suites pass. The only failure is an
unrelated Anthropic model metadata test in `packages/ai`.
Fixes#11495
Requested by @badlogic
Co-authored-by: triagent <triagent@localhost>
- ai: OAuth logins only accept http(s) URLs from servers (shared
trustedHttpUrl, now also checked by Radius); pi-messages rejects
contentIndex values that are not the next or an existing block; the
CLI auth file is written with mode 0600
- ai: OpenCode DeepSeek V4.1 Flash (renamed from V4 Flash upstream) no
longer sends prompt_cache_retention
- tui: setTitle() replaces control characters; export
replaceControlCharacters()
- coding-agent: the login dialog only opens web URLs and never prints
control characters from sign-in, device-code, or info URLs
- durable: partial commits no longer merge a new __proto__ member into
Object.prototype
When the agent directory is reached through a symlink (for example
`~/.pi` linked into a dotfiles repo), pi passed the symlinked path as
`--prefix` to `npm install` and `npm uninstall`. npm then writes lock
entries such as `../../../dotfiles/pi/agent/npm/node_modules/foo`
instead of `node_modules/foo`. Each later install adds one more nesting
level, so `package-lock.json` keeps growing.
This change resolves the install root with `realpathSync` before passing
it to the package manager:
- in `getNpmInstallArgs()`, which covers install and update for npm,
pnpm and bun;
- in `uninstallNpm()`.
The paths pi loads extensions from are unchanged.
**Testing**
- Added a regression test in `test/package-manager.test.ts`. It uses a
symlinked agent dir and checks that install and uninstall get the real
path as `--prefix`. The test fails without the fix and passes with it.
- Wrapped the existing `--prefix`/`--cwd` expectations in
`realpathSync(...)`. They now match on systems where the temp dir is
itself a symlink, such as macOS.
- Ran `npm run check` (clean) and `test/package-manager.test.ts` plus
`test/package-manager-ssh.test.ts` (all pass).
- Manual reproduction with `~/.pi` as a symlink, running `pi install
npm:ms` several times:
- before: lock keys grew to
`../../../dotfiles/pi/agent/npm/node_modules/ms`,
`../../../dotfiles/dotfiles/pi/...`, and so on;
- after: `[ '', 'node_modules/ms' ]`, and `pi remove npm:ms` leaves `[
'' ]`.
Fixes#11358
Requested by @badlogic
Co-authored-by: triagent <triagent@localhost>
- ai: StreamingJsonParser / ToolCallArgumentsStream read each argument delta once (tolerant,
immutable snapshots, linear cost); every provider, pi-messages and the faux provider use it
and keep stream state (partialJson, partialArgs, index, customInput, streamIndex, Bedrock
reasoning bytes) off the content blocks; grammar tool input appends per delta
- agent: streamProxy uses the same parser
- chord: Delta append ops own their text (ownedSlice), so ops and replicas no longer keep the
whole growing string alive
- durable, ai, mcp: bounded error excerpts and summaries own their characters
- coding-agent: tool calls render streamed arguments once per frame, not once per delta
Updates of pi.dev installer installs run npm ci against a release
lockfile that pins every package, so npm never resolves versions and a
configured min-release-age does not apply. The --min-release-age=0 flag
had no effect, but security scanners that block it stopped the update.
Global npm, pnpm and bun self-updates keep their age-gate overrides.
closes#11262
Move two post-release entries out of the 1.3.0 sections, add missing entries, duplicate user-facing ai and tui changes into coding-agent, and add New Features.
Jev (TypeSafe, Workers AI), Clef (Workers AI), and OpenRouter decision
models now accept ClassifierContext.images. Each service takes its own
wire form: TypeSafe assets with the opt-in header, Clef images data URLs,
or OpenRouter image_url parts in the state. A new SystemOneCompat
(imageInput, sendImageOptInHeader) selects the form; catalog entries set
it explicitly and models.json provider compat now applies to classifiers.
Adds Clef Omni and Jev 1.14 on Workers AI and fixes Clef Flash and
Workers AI Jev catalog pricing.
Resume and /tree restored exactly the tools the branch recorded, so extension
tools registered later stayed inactive. Registered extension tools that the
branch never added or removed are now activated unless they use
defaultActive: false. The whole branch is searched, so tools removed before a
compaction stay inactive. Also fixes the /tree case from #11075.
closes#11062
pi-ai gets a ClientIdentity request option and createModels() default that choose the User-Agent (detailed with version, short, or custom) and provider attribution headers for OpenRouter, NVIDIA NIM, and OpenCode. Attribution is off by default in pi-ai and on by default in pi-agent-core and pi-durable (HarnessOptions.identity). pi-coding-agent sets its identity from the telemetry setting instead of adding headers itself.
models.dev deprecated several Baseten, GitHub Copilot, and Together models,
which broke type checks and the default-model test.
- Default GitHub Copilot model: gpt-5.4 -> gpt-6.1-sol
- Move Copilot live tests to gpt-6-luna/gpt-6-sol
- Remove tests and generator entries for retired models, including the
Together reasoning-only model list
Extensions importing the public /models subpath (e.g. clampThinkingLevel)
failed with "Cannot find module" because the loader aliased/virtualized
only the pi-ai root, /compat, /oauth, and /providers/all entrypoints.
Add /models to the jiti aliases and the embedded virtual module map,
plus a regression test importing it without a local pi-ai install.
Closes#11156
Co-authored-by: Dante-dan <dantedan@users.noreply.github.com>
Scripts wait in arrival order for a free worker instead of failing
immediately. The wait counts toward timeout_ms, honours abort, and is
capped at 60 seconds. PI_CODEMODE_MAX_WORKERS raises the process-wide
limit of two workers for hosts running many sessions in one process.
closes#11184
A second select/confirm/input/editor dialog or inline custom() component
replaced the visible one and left its promise pending forever. Dialogs in
the editor slot now form a stack: closing the visible dialog shows the
previous one again, and dialogs opened from inside a custom component work.
closes#11166
Windows PowerShell 5.1 adds a BOM when it rewrites managed-install.json, which made pi update fail with "Managed install marker is missing or invalid".
* docs(ai,coding-agent): generate models.json reference from schemas
Add npm run generate:references, which renders docs/models-configuration.md
from the models.json TypeBox schemas, and describe provider and model fields
in those schemas. Overrides get their own descriptions without defaults, and
compat sections cover the Responses API aliases.
closes#10790
* Modify changelog
* docs(ai,coding-agent): use shared thinkingLevelMap and promptCache schemas
Replace the per-use schema helpers with fixed schemas whose descriptions
hold for both models and modelOverrides entries.
* feat(coding-agent): default NVIDIA to Nemotron 3.5 Super VL
Co-authored-by: prademacher-nvidia <235594174+prademacher-nvidia@users.noreply.github.com>
* docs(coding-agent): changelog for NVIDIA default model change
* fix(ai): run NVIDIA E2E tests against Nemotron 3.5 Super VL
---------
Co-authored-by: prademacher-nvidia <235594174+prademacher-nvidia@users.noreply.github.com>
Compaction summaries were limited to 0.8 * compaction.reserveTokens (13107 by default), turn-prefix summaries to 0.5 * reserveTokens, and branch summaries to a hardcoded 4096. On providers where reasoning shares the output limit (OpenAI Responses, adaptive Anthropic), high thinking levels used most of that limit and compaction failed with "generation hit the token cap", even though the model allows far more output.
Tying the limit to reserveTokens assumed the summary request is as large as the full context, so its output had to fit in the reserved space. It is usually much smaller: recent messages kept by keepRecentTokens are not sent, a split turn goes to a separate request, tool results are truncated, and tool definitions and the normal system prompt are not included. Manual compaction can also run long before the context is full. The provider layer already sizes output from the actual request, using the model's output limit reduced to fit the remaining context, as for normal turns.
Summary requests now set no maxTokens and rely on that. compaction.reserveTokens only controls when compaction starts.
generateSummary() and generateSummaryWithUsage() now ignore the reserveTokens parameter.
closes#9512, closes#9075, closes#10908