Commit graph

4074 commits

Author SHA1 Message Date
robopipi[bot]
fecc19e4e9
fix(coding-agent): keep grep context lines aligned with ripgrep when a file has a lone CR (#11465)
When `context` is set, the grep tool reads the file again to print the
lines around each match. It turned CRLF into LF and also turned every
lone CR into LF before splitting the text into lines. ripgrep, which
supplies the match line numbers, counts lines only at LF. A lone CR
therefore added an extra line on pi's side, so every later line was off
by one.

For example, in a file containing `before\rprogress\nneedle\nafter`, a
search for `needle` with `context: 1` showed `progress` as the line-2
match and left out `after`.

The fix keeps the CRLF normalization and splits only on LF, so pi
numbers lines the same way ripgrep does. Displayed lines already have
`\r` removed, so the line before the match now shows as
`beforeprogress`.

**Testing**
- Added a test in `packages/coding-agent/test/tools.test.ts` for a file
with a lone CR searched with `context: 1`. It fails without the fix and
passes with it.
- `test/tools.test.ts`: all 87 tests pass.
- `npm run check` passes.
- Reproduction from the issue. Before the fix:
  ```
  f.txt-1- before
  f.txt:2: progress
  f.txt-3- needle
  ```
  After the fix:
  ```
  f.txt-1- beforeprogress
  f.txt:2: needle
  f.txt-3- after
  ```

Fixes #11398

Requested by @badlogic

Co-authored-by: triagent <triagent@localhost>
2026-10-27 09:07:23 +01:00
Armin Ronacher
89c921d8b5 fix: harden handling of untrusted URLs, terminal text, and streamed data
- 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
2026-10-26 20:46:31 +01:00
Petr Houška
ae823bc0bb fix(coding-agent): rank tools first when the search query is their name
A query that equals a tool's full name or the part after a __ separator
(thread_read for mcp__app__thread_read) now ranks that tool first in
tool_search and codemode searchTools(). BM25 length normalization could
otherwise rank a shorter description sharing the name's words above it.

closes #11331
2026-10-26 14:17:45 +01:00
triagent
66c2fa3d7a fix(coding-agent): update undici to 8.12.3 for security advisories 2026-10-26 13:50:55 +01:00
robopipi[bot]
984cf6643d
fix(coding-agent): resolve provider/id references when the provider id contains a slash (#11397)
`--model "<provider>/<id>"` returned "not found" when the provider id
itself contains a `/`. URL-derived ids such as
`llama-server=http://host:10800` are one example.
`splitProviderPrefix()` in `model-resolver.ts` split the reference at
the first `/`, which turned the prefix into `llama-server=http:`. No
provider has that id, so resolution fell through to not-found.

`splitProviderPrefix()` now compares the text against the provider ids
in the catalog. A provider matches when the text starts with its id
followed by `/`, and the longest matching id wins. Ordinary provider ids
resolve exactly as before.

Testing:
- I added two cases to the `resolveModelReference` table in
`test/model-resolver.test.ts`, using a provider
`llama=http://host:10800`: one plain, one with a `:high` thinking
suffix. Both fail without the change and pass with it. The test runs
every case against the catalog in both orders.
- `node ../../node_modules/vitest/dist/cli.js --run
test/model-resolver.test.ts`: 64 passed.
- `npm run check`: passes.
- Before the change, a standalone check of `resolveModelReference` with
the reporter's exact provider and model
(`llama-server=http://host:10800/unsloth/Qwen3.8-27B-GGUF:BF16`)
returned `not-found`. It now returns `resolved`.
- One test, `test/model-runtime-classifiers.test.ts` ("applies provider
compat from models.json to classifier models"), fails in my environment
both with and without this change. It is unrelated to this fix.

Fixes #11394

Requested by @badlogic

Co-authored-by: triagent <triagent@localhost>
2026-10-25 08:48:27 +01:00
robopipi[bot]
5f960a0e2b
fix(coding-agent): pass real path as npm prefix when agent dir is a symlink (#11396)
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>
2026-10-25 08:47:34 +01:00
Mario Zechner
f715a15c10 fix(ai,chord,durable,coding-agent): parse streamed tool-call arguments incrementally (fixes #9265)
- 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
2026-10-24 08:00:07 +00:00
Petr Houška
acf83acfd8 docs: restore existing changelog attributions 2026-10-23 22:10:30 +02:00
Petr Houška
e9c3ac9d98 docs: drop author attribution from backfilled changelog entries 2026-10-23 22:10:30 +02:00
Petr Houška
a8fe8e0adc docs: add missing changelog entries for #9301, #11289, #11290, #11293, #11296, #11347 2026-10-23 22:10:30 +02:00
David Brailovsky
ed76cca69b fix(coding-agent): test forkFrom copied content instead of fs calls
Move the forkFrom test into custom-session-id.test.ts, drop the fs mock
and call-count checks, and clean up the temp directory.
2026-10-23 21:24:38 +02:00
triagent
8ddc44ec8b fix(coding-agent): write forked session through one fd
forkFrom reopened the target file with appendFileSync for every entry,
which is slow where open/close is expensive. fixes #11253
2026-10-23 21:24:38 +02:00
Petr Houška
606af8662e feat(coding-agent): show model names in model selectors when they add information
/model and /scoped-models listed only `id [provider]`, so renamed
overrides from models.json were invisible in the list. Rows now show
the name in parentheses when it has words the id lacks, e.g.
`minimax/minimax-m3 (MiniMax M3 [parasail]) [openrouter]`. Names that
only restate the id with different casing, separators, or a dropped
namespace/version suffix stay hidden.

closes #5916
2026-10-23 21:19:51 +02:00
yzchen
3953e9ef49 fix(examples): todo renderResult returns undefined on validation errors
When a todo call fails schema validation, createErrorToolResult produces
details: {} (a truthy empty object), so renderResult skips both the
!details and details.error branches, then switch (details.action) has no
default and returns undefined. ToolExecutionComponent.updateDisplay()
then calls addChild(undefined), and the next Box.render pass crashes the
whole interactive TUI.

Add a default case that renders the validation error text.
2026-10-23 20:20:02 +02:00
Petr Houška
c55972086d docs(coding-agent): add changelog entry for #11209 2026-10-23 19:48:59 +02:00
Petr Houška
c8ec752cf9 fix(coding-agent): skip fd/rg downloads on unsupported architectures
Only x64 and arm64 map to prebuilt release archives. ARM32 and ia32 previously
fell back to x86-64 binaries; now pi warns and recommends the system package manager.

fixes #11209
2026-10-23 19:48:59 +02:00
Christian Klotz
ede2c9b468
docs: fix provider checklist, MCP anchors, and missing commands/settings (#11338)
Fix docs and `--help` drift: update the provider checklist to the
`src/api` layout, fix broken MCP anchors, and document `/mcp`, `pi mcp
add|remove`, `PI_CLEAR_ON_SHRINK`, and `"disabled"` timeouts.

closes #11243
2026-10-23 17:02:41 +02:00
robopipi[bot]
36be9c612e
fix(coding-agent): decode pi.exec output across UTF-8 chunk boundaries (#11326)
`execCommand()` (the function behind `pi.exec()`) decoded each stdout
and stderr chunk on its own with `data.toString()`. When a multi-byte
UTF-8 character was split across two pipe chunks, each half was turned
into U+FFFD, so the result was corrupted. For example, `"你好"` came back
as `"���好"` while the command still exited with code 0.

This change gives stdout and stderr each their own
`StringDecoder("utf8")`. A decoder holds back incomplete bytes until the
rest of the character arrives. Both decoders are flushed with `end()`
before the promise resolves, on both the success path and the error
path. `waitForChildProcess()` can resolve without a stream `end` event,
so the decoders are flushed there and not in an `end` handler. The
return shape, exit codes, and kill behavior are unchanged.

**Testing**
- Added a regression test to `packages/coding-agent/test/exec.test.ts`.
The child process splits `"你好"` across two writes on both stdout and
stderr. The test fails without the fix and passes with it: `node
../../node_modules/vitest/dist/cli.js --run test/exec.test.ts` reports 3
passed.
- `npm run check` passes.
- Reproduction from the issue: a child that writes the first byte of
`"你好"` and then the rest 100 ms later. Before the fix the result was
`{"stdout":"���好",...}`. After the fix it is
`{"stdout":"你好","stderr":"","code":0,"killed":false}`.

Fixes #11312

Requested by @davidbrai

---------

Co-authored-by: triagent <triagent@localhost>
Co-authored-by: David Brailovsky <davidbrai@gmail.com>
2026-10-23 14:47:34 +02:00
Lincoln
6dfb546785
fix(coding-agent): start when a configured package fails to install (#11304)
fix(coding-agent): start when a configured package fails to install

A package in settings.json that could not be installed (registry
unreachable, package missing, failing git prepare step) crashed startup
with an uncaught rejection and a raw Node stack.

Resolving packages now catches the install failure, skips that package
the same way offline mode does, and reports it as an extension warning:

  Warning: Extension package "npm:foo": failed to install (...).
  Run "pi remove npm:foo" to remove it from settings.json.

Project packages point at `pi remove <source> --local`. `-e` sources
say to check the source instead. Pi tries the install again on the next
start or /reload.

--help and --list-models now print runtime diagnostics, so the warning
shows there too. pi config prints it as well. pi install and
pi update --extensions still fail with a non-zero exit.

closes #11088
2026-10-23 12:27:45 +02:00
Armin Ronacher
9ffd27cfaa fix(mcp): accept only http and https OAuth URLs
A malicious MCP server could supply a file: or custom protocol
authorization endpoint, which sign-in passed to the platform URL handler.
2026-10-23 12:19:56 +02:00
Vegard Stikbakke
14c8118412
feat(ai): carry responseModel on pi-messages terminal events (#11265)
This commit makes it so we store the responseModel (i.e. the "phyiscal"
model used) on each PiMessage. This allows e.g. Radius to know which
physical model was used by virtual models (like `cheap`). It's
especially important after we merged
https://github.com/earendil-works/pi/pull/11000
(9113094291), where Pi will drop thinking
traces when the model name doesn't match - with this change we can check
the physical model instead.
2026-10-23 10:54:18 +02:00
Petr Houška
8c03fef4d3 fix(tui,coding-agent): rank contiguous fuzzy matches and search session fields separately
fuzzyMatch scored only the greedy leftmost alignment, so a query could be
scattered over an earlier unrelated part of the text (opus via "openrouter",
astra via "radius radius/gpt") and the real match ranked last. It now also
scores every contiguous occurrence and keeps the best alignment.

/resume matched one concatenated id/name/messages/cwd string. Scattered
matches in megabytes of message text or in UUIDs were noise, and the
position penalty buried real hits deep in a transcript. Fields are now
matched separately: name and cwd fuzzy, id and messages exact only, with no
position penalty. Ranking is name text, then id/message/cwd text, then
scattered name/cwd matches. Lowercased message text is cached per session.

closes #11220
2026-10-22 21:39:17 +02:00
Petr Houška
90d5cf7846 docs(coding-agent): shorten device-code login guidance 2026-10-22 21:38:58 +02:00
Petr Houška
7151099d02 feat(coding-agent): confirm device-code browser and clipboard actions
Knowing how annoying re-logins in corporate environments can be, I think
we can open the browser and copy the code to the clipboard (what the user
asks for) without forcing it upon users (which I assume was the reason
it was removed in 8f0e9251b9).

This change adds optional browser opening and code copying during
device-code login after an additional confirmation (usually Enter).
It should work for all providers, with an optional opt-out controlled
by the provider author, not user settings.

Fixes #9282.
2026-10-22 21:38:58 +02:00
muyiyr
c60de42e98 fix(coding-agent): preserve pi.exec spawn errors
Fixes #11085
2026-10-22 21:14:23 +02:00
Petr Houška
1c720d2850 fix(coding-agent): render HTML export for sessions deeper than the call stack
Replace recursive tree walks in the export template (sortChildren, mapNodes,
markActive) with iterative traversals, and build getPath with push + reverse.

closes #11225
2026-10-22 20:51:38 +02:00
Ville Penttinen
64627838b2
feat(coding-agent): add messageId and timestamp to markdown transformer context
* feat(coding-agent): add messageId and timestamp to markdown transformer context

* fix(coding-agent): update compaction render test for messageMeta argument

---------

Co-authored-by: Petr Houška <houskape@gmail.com>
2026-10-22 20:49:38 +02:00
Petr Houška
9359adf0c6 fix(coding-agent): render system messages in HTML export
Render the leading system message as the system prompt and tool list,
and later system messages as named section and tool updates. The header
prompt/tools from agent state remain as a fallback for sessions without
system entries. CLI --export now shows the prompt and tools too (#3464).

closes #11261
2026-10-22 20:37:03 +02:00
Petr Houška
27cc6bfc0c fix(coding-agent): stop macOS shell groups before killing them
Reduce the race where killing a foreground child wakes its shell before the shell is signalled. Keep Linux and Windows termination unchanged and preserve kill fallbacks when stopping fails.

closes #11252
2026-10-22 20:22:20 +02:00
David Brailovsky
476a303c12
fix(coding-agent): drop min-release-age override from managed npm ci (#11270)
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
2026-10-22 13:50:26 +02:00
Armin Ronacher
2685de7a17 fix: update deps for security advisories
Bump vitest to 4.1.12 (vite 8.3.4) and the gondolin example extension to 0.14.0, which drops node-forge. Gondolin's undici moves to 6.29.1.
2026-10-22 11:24:50 +02:00
Vegard Stikbakke
cae8d69e57
fix(ai): use OpenRouter-reported total cost (#10286)
Use authoritative OpenRouter billing for Chat Completions and Anthropic Messages while retaining catalog component estimates and fallback behavior.

Fixes #9980
2026-10-22 11:01:39 +02:00
Vegard Stikbakke
7c22a93d0e
feat(ai): add totalFromProvider flag to usage cost (#11178)
Add Usage.cost.totalFromProvider to record whether cost.total is the
billed cost reported by the provider or an estimate from catalog rates.
Catalog-derived totals set it to false; a missing value also means
false. combineUsage marks a sum as provider-reported only when both
parts are.
2026-10-22 09:02:01 +02:00
Armin Ronacher
58c0b02ce5 Add [Unreleased] section for next cycle 2026-10-22 00:29:24 +02:00
Armin Ronacher
4a080252f0 Release v1.4.0 2026-10-22 00:29:13 +02:00
Armin Ronacher
f76b248cae docs(ai,tui,coding-agent): audit changelogs since v1.3.0
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.
2026-10-22 00:16:31 +02:00
Armin Ronacher
7f225f25d8 feat(ai,coding-agent): image input for System One classifiers
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.
2026-10-21 23:53:47 +02:00
Petr Houška
02726086d1 fix(coding-agent): recovery omits tool results with the truncated tool call
Pre-prompt recovery of a recoverable length stop omitted the assistant
message but not the tool results that answered its calls, because only
the end-of-run caller passed them. The orphaned results were persisted
and providers rejected every later request.

_omitRecoveryAttempt now finds the matching tool results in the
projected context itself, so every caller omits the pair together.

closes #11227
2026-10-21 22:44:44 +02:00
Petr Houška
8c4e873899 fix(coding-agent): grep tool ignores ripgrep config via --no-config
The built-in grep tool spawned rg with the inherited environment, so
RIPGREP_CONFIG_PATH (e.g. a config containing --fixed-strings) silently
changed tool semantics: literal: false searches became literal and the
agent got incomplete results with no error.

Pass --no-config so the tool contract is deterministic. Users who want
custom rg behavior can use the bash tool or wrap grep in an extension.

closes #11203
2026-10-21 22:19:03 +02:00
David Brailovsky
bd3c9b8237 fix(coding-agent): activate extension tools registered after the branch recorded its loadout
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
2026-10-21 16:39:13 +02:00
Mario Zechner
04a93bad8f fix(ai,agent,coding-agent,durable): client identity follow-ups
- ClientAttribution is { name, url, categories? } with required name and url, so
  OpenRouter always gets an HTTP-Referer. identity.ts maps the fields to each
  provider's headers; NVIDIA gets the name with its first letter upper-cased.
  attribution: null turns it off. PI_ATTRIBUTION is pi's, including the
  cli-agent category; agent-core, durable, and the coding agent use it.
- Categories are a string array, joined for OpenRouter.
- withClientIdentity() is a typed wrapper instead of a Proxy and keeps a
  MutableModels mutable. Durable no longer caches wrappers in a WeakMap.
- The Codex originator stays pi; login agentName no longer follows attribution.
- Models and the coding agent's ModelRuntime add x-opencode-session for custom
  providers pointing at OpenCode, replacing the coding agent's transformHeaders.
2026-10-21 11:18:16 +00:00
Armin Ronacher
adc5fa9f80 feat(ai,agent,coding-agent): move client identity and provider attribution into pi-ai
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.
2026-10-21 11:17:02 +00:00
David Brailovsky
adfc14461a fix(coding-agent): apply /compact instructions to split-turn prefix summary
closes #11058
2026-10-21 12:42:17 +02:00
David Brailovsky
e5fe9777b9 fix(ai,coding-agent): drop models retired upstream
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
2026-10-21 10:10:55 +02:00
Mario Zechner
bdd9f0399d fix(coding-agent): accept mcp.json files with a UTF-8 BOM
closes #11191
2026-10-21 00:14:21 +02:00
Petr Houška
22dc6c26f3 fix(coding-agent): expose @earendil-works/pi-ai/models in the extension loader
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>
2026-10-20 21:47:52 +02:00
Mario Zechner
885a564b3b fix(coding-agent): queue codemode scripts at the worker limit
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
2026-10-20 20:50:27 +02:00
Mario Zechner
f8ab362cca fix(coding-agent): keep clipboard helpers out of pi's process group so Ctrl+Z does not stop them
closes #11179
2026-10-20 20:49:48 +02:00
Mario Zechner
38847563f4 fix(coding-agent): stack concurrent extension dialogs instead of replacing them
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
2026-10-20 20:45:12 +02:00
Mario Zechner
9df819de39 fix(tui): restore terminal modes when the process exits without stop()
Registers a process exit fallback that synchronously writes mouse, focus,
bracketed paste, Kitty keyboard, and alternate-screen resets.

closes #11170
2026-10-20 19:52:54 +02:00