Commit graph

7333 commits

Author SHA1 Message Date
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
Mario Zechner
5e106ab1ca feat(chord): NativeWatchable, the in-process counterpart of Watchable 2026-10-23 19:03:58 +00: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
Ramiz Wachtler
ff68964715
fix(tui): use iterm2 word characters for fullscreen double-click selection (#11340)
- add iterm2's default word characters (`+ \ ~ _ .`) to fullscreen
double-click selection
([source](7f4ff73972/sources/Settings/iTermPreferences.m (L743)))
- `~/...`, `~/.pi/...`, `~\...`, `foo+bar` now select whole
- add regression cases for `~`, `.`, `\`, `+`
- fixes #10980
2026-10-23 19:32:30 +02:00
Nihaal Sangha
bc73eb8b15 docs(tui): remove stale comment about Rex's Kitty images
Rex now fully supports the Kitty graphics protocol.
2026-10-23 19:11:46 +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
robopipi[bot]
8a2ebd3854
fix(tui): stop nested blockquote rendering from growing exponentially (#11321)
**Problem.** A line of many `>` characters makes `marked` produce one
nested blockquote token per `>`. The blockquote branch of
`Markdown.renderToken` renders its children at `Math.max(1, width - 2)`
columns, then wraps each child line again at that width and adds a `│ `
prefix.

Once the nesting uses up the width (about 39 levels at 80 columns), the
width stays clamped at 1. From then on, each child line (`│ x`, 3
columns wide) is wrapped into about 3 lines at every further level, so
the output roughly triples per level. At 80 columns:

- 50 levels: 4096 lines, about 30 s to render;
- 55 levels: `push(...lines)` exceeds the JavaScript argument limit and
throws `RangeError: Maximum call stack size exceeded`, which crashes pi.
Reopening the session crashes it again.

**Fix.**
- When there is no room left for another border (`width < 3`), the
blockquote branch now renders its child tokens at the same width without
adding a border and without wrapping them again.
- Child lines are appended with a loop instead of a spread `push`.

Quotes that fit in the width render exactly as before.

**Testing.**
- Added a regression test in `packages/tui/test/markdown.test.ts`: 60
nested `>` rendered at width 80 give one line that keeps the content.
Without the fix the test fails with the RangeError; with it, all 92
tests in that file pass (`node --test test/markdown.test.ts`).
- `npm run check` passes.
- Reproduction script rendering `">".repeat(n) + "x"` at width 80:
- before: 50 levels gave 4096 lines in 29.5 s, and 55 or 60 levels threw
a RangeError;
  - after: 40, 50, 60 and 200 levels each give 1 line in about 10 ms.

**Not covered.** A run of about 2000 `>` still overflows the stack
inside `marked`'s own lexer, before pi's renderer runs. That is a
separate limit and is not changed here.

Fixes #11272

Requested by @davidbrai

Co-authored-by: triagent <triagent@localhost>
2026-10-23 12:46:03 +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
David Brailovsky
e3bc05e871 fix(ai): send fixed low top-level effort with Anthropic per-message effort
Organizations can cap effort, and the previous fixed top-level high was
rejected even when the requested level was low or medium.

closes #10063
2026-10-23 12:11:54 +02:00
Mario Zechner
3141dfdcf4 Merge remote-tracking branch 'upstream/main' into watchable 2026-10-23 09:31:13 +00:00
Mario Zechner
a2fd25f56c feat(chord): Watchable, the common interface of values that can be watched 2026-10-23 09:31:09 +00:00
David Brailovsky
a1836b06c3 fix: handle npm 12 pack --json output in preview-packages 2026-10-23 11:20:01 +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
David Brailovsky
906e1302f9
fix(coding-agent): build standalone binaries with Bun 1.4.4 (#11319)
Tested locally on darwin-arm64, darwin-x64, linux-arm64, linux-x64,
windows-arm64 and windows-x64: the CI smoke checks plus HTTPS responses,
file tools, image resizing and session resume. Aborting a streamed reply
still stops immediately (#10031, #8823).
2026-10-23 10:31:50 +02:00
Mario Zechner
42cb822f1c feat(chord): shared watch-handle implementation (Watch, NativeWatch); Durable uses it 2026-10-23 07:05:23 +00:00
Armin Ronacher
936e5f40ae fix(evals): drop autoevals dependency
autoevals@0.3.0 declares engines npm: please-use-pnpm, so every npm install printed an EBADENGINE warning. 0.4.1 removes that but adds a pnpm-enforcing preinstall script. The eval only used the Levenshtein scorer, so inline an equivalent normalized similarity helper instead.
2026-10-22 23:32:38 +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
74f4686402 fix: trust robopipi[bot] in PR gate 2026-10-22 16:42:51 +02:00
Armin Ronacher
be14d9732f feat(durable): open a Harness read-only
HarnessOptions.readOnly opens a viewer of a session another process drives, such as a synced copy on a device that only shows it. Open leaves running tasks as committed, commits with writes reject with ReadOnlySession before they reach Storage, and resume() and every call that would run tasks reject with it.
2026-10-22 14:25:58 +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
github-actions[bot]
ac80dd88f3 chore: approve contributors from issue #11088 2026-10-22 11:16:38 +00:00
Mario Zechner
b9fb2b854b docs(durable): renumber examples 2026-10-22 10:56:50 +00:00
Mario Zechner
92ab536024 feat: Durable storage adopt callback inside the commit, watch types in Chord, explicit watch retirement
Breaking changes:
- Durable Storage.commit(writes, adopt, context): storages call adopt(seq) once,
  synchronously, when the batch is durable and before their next commit
- Chord traceIn(options, fn, context): context last
- WatchHandle and WatchEnd move to Chord (Durable re-exports them); end reasons
  ended (was session_closed) and failed (was session_failed); retirement is an
  explicit source event, and an ended watch drops its references into the Session
- Removed Session.documentState(), Conversation.viewState(), Harness.taskGraph()
  and DocumentState; use watchDoc(), watch(), watchTaskGraph()
2026-10-22 10:52:59 +00:00
David Brailovsky
3bc6eebacf fix(ai): hold Anthropic system updates past dropped assistant messages
A mid-conversation system message was placed before checking whether the
next assistant message converts to any blocks. When it did not (empty text,
or another model's reasoning-only reply), the update landed before a user
message and Claude rejected the request with a 400.

closes #11091
2026-10-22 12:43:42 +02:00
Christian Klotz
db774055e3
Set up preview packages (#11260)
* feat: publish preview packages to pkg.earendil.com

CI packs every public package after the build, with internal dependencies
pointing at the same commit's builds. A workflow_run job publishes them to
the earendil-pkg worker with the job's GitHub OIDC token, moves main or
pr-<n>, and comments install commands on the PR. Closing a PR deletes its
ref. pi-env is left out because its daemons are only built for releases.

* feat: publish preview packages only for main and same-repo PRs

pkg.earendil.com requires a consumer token, so fork authors could not
install their previews anyway. Fork PRs are no longer packed or
published, which keeps fork code out of the registry and removes the PR
lookup: the PR number now comes from the workflow_run event. The PR
comment is dropped; the job summary lists the install commands.

* fix: pick the PR into main and describe only the workflow in preview-packages.yml

- Read the PR number from the workflow_run pull request list with jq,
  selecting the PR into main; a branch can also have PRs into other
  branches, and the index expression tripped workflow linters.
- Comments describe what the workflow does and what it needs from
  pkg.earendil.com, not how the service works.

* fix: gate preview publishing to main pushes and open PRs into main

- Publish only CI runs for pushes to main and for pull requests. A branch
  can make its own copy of CI run on push; those runs are unreviewed and
  were uploaded as push builds.
- Move pr-<number> only while the PR is open into main with this commit
  as its head, checked live, so a late publish cannot recreate the ref of
  a closed PR; otherwise publish without a ref and print a notice.
- Set concurrency per job, so skipped fork runs never wait in a group.

* docs: drop the service rule from the preview-packages.yml header

* docs: name the earendil-works/pkg worker in the preview script
2026-10-22 11:43:08 +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
David Brailovsky
5884ee6816 feat(coding-agent): let /ir import a session file from a .jsonl URL 2026-10-22 10:58:13 +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
18e56a069a fix(ai): expect compat on OpenRouter decision models in catalog test 2026-10-22 00:20:24 +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
Armin Ronacher
cae9fd28a2 Revert "feat(durable): replicate storages as a commit log"
This reverts commit 4c25119f54. The work continues on the replication branch.
2026-10-21 14:39:26 +02:00
Armin Ronacher
4c25119f54 feat(durable): replicate storages as a commit log
Every built-in storage implements ReplayableStorage: head(), replay() at an explicit sequence, and export() of the current state as a log that replays to the same reads. SQLite schema version 3 adds sequence indexes so export streams. @earendil-works/pi-durable/sync adds ReplicatingStorage, which appends each committed batch to an outbox with fork copies resolved, compactRecords(), and versioned JSON log lines.
2026-10-21 14:26:19 +02:00