Commit graph

7105 commits

Author SHA1 Message Date
robopipi[bot]
911fbab32f
fix: unset all supported provider env vars in pi-test.sh --no-env (#11395)
`./pi-test.sh --no-env` is supposed to run pi without any API keys, but
its hard-coded `unset` list had fallen behind
`packages/ai/src/env-api-keys.ts`. Credentials such as
`DEEPSEEK_API_KEY`, `MOONSHOT_API_KEY` and `ANTHROPIC_AUTH_TOKEN` were
still picked up from the environment.

This change adds the 26 missing variables to the list:
- the Anthropic auth token and federation variables
- the API keys for Ant Ling, Baseten, Cloudflare, DeepSeek, Fireworks,
Google Cloud, Kimi, Meta, Moonshot, Nvidia, Qwen token plan, Radius,
Together, Typesafe, Xiaomi (all plans) and ZAI coding CN

The list now covers every variable name in `env-api-keys.ts`.

Testing:
- No automated test covers this shell script, so none was added.
- A loop over every variable name in `env-api-keys.ts` confirmed each
now has an `unset` in `pi-test.sh`.
- Before the change, `PI_CODING_AGENT_DIR=$(mktemp -d)
DEEPSEEK_API_KEY=x ./pi-test.sh --no-env --offline -ne --list-models
deepseek` listed DeepSeek models.
- After the change, the same command with both `DEEPSEEK_API_KEY` and
`MOONSHOT_API_KEY` set prints "No models available."
- `bash -n pi-test.sh` and `npm run check` pass.

Fixes #11359

Requested by @badlogic

Co-authored-by: triagent <triagent@localhost>
2026-10-25 08:43:53 +01:00
Mario Zechner
38ff99177f fix(durable): continue runs after tool handoffs 2026-10-24 22:48:11 +02:00
Mario Zechner
6c2fd73b0c feat(durable): deferred storage of throttled commits
A commit may be marked deferrable ({ persistence: { kind: "deferred", withinMs? } }) on Session, Conversation,
TaskRuntime and a tool's api.commit. When every write changes an existing conversation document with history
"latest" and fork "initial", it is adopted in memory and shown to watches at once, and stored with the next commit
that reads or writes the document, before the document leaves memory, at close, or within withinMs (default
settings.progress.persistWithinMs, 1000 ms). The generation partial throttle and the tool output throttle use it.

subscribeCommits() gets these commits only once stored, with exactly what was stored; internal observers get
memory-only publications. Spec §1/§13 changed, new §4.1. 32 streams over SQLite: storage transactions 562/s -> 99/s,
disk 14.3 -> 4.9 MB/s, CPU 29% -> 22% of one core. A crash loses up to about 1 s of streamed content.
2026-10-24 17:49:30 +00:00
Mario Zechner
d0aa218cb1 perf(chord): cheaper container copies in Delta's tracker and applyImmutable
Tracker revisions, applyImmutable() and applyImmutableBatches() copy each container on a changed path with
spread (Object.assign() onto a null-prototype object) instead of one Object.defineProperty() per key: same own
data properties, prototype and own __proto__ member, about 25 times faster per container. The copy-count test
counts key listings through a proxy instead of spying on Object.keys.

Adds a tracker GC regression test: 10,000 adopted changes retain only the document, not a context registration
per change. Follow-up to #9265.
2026-10-24 16:56:53 +00:00
Mario Zechner
f321e5bdd2 docs(chord): delta README, growing strings through drafts and appendText 2026-10-24 12:17:38 +00:00
Armin Ronacher
1de2d7adea fix(ai): treat Kimi K3.1 Pro models as Kimi K3 family
Kimi Coding, Moonshot, and OpenCode Go generated K3.1 Pro and K3.1 Pro Highspeed with Kimi K2 settings because K3 detection matched exact IDs. Detect the K3 family by ID pattern, and Kimi Coding K3-style models by their effort options. Add implied subscription costs and clamp gateway output limits like K3.
2026-10-24 13:46:57 +02:00
Mario Zechner
bdb151774e fix(chord): give the PendingOps fuzz tests room on loaded CI runners
The tracker fuzz test hit vitest's 5 s default timeout on CI (6.2 s); fewer sequences and a
30 s timeout per fuzz test.
2026-10-24 09:48:13 +00:00
Mario Zechner
d8d99950ac feat(chord,durable): PendingOps folds pending ops for slow consumers; Watch delivers one call per busy period
- chord/delta: PendingOps (push, size, take -> PendingTake): raw pending ops, r drops everything
  before it, a leaf index merges appends and primitive sets per path (cleared by any other op),
  past 10,000 ops only "replace with the current value"; one batch is kept by reference and
  received ops are never mutated
- chord: Watch queues frames in PendingOps while its listener is busy or not started and delivers
  one call with the newest value, the folded ops (or [["r", newest]] after an overflow) and the
  newest frame's context; replaces the 100-frame collapse. NativeWatch is unchanged and takes an
  optional replace()
- durable: watchEvents keeps every event batch in order (NativeWatch with a snapshot replace);
  document, view and task-graph watches fold; spec, README, example 04 and tests updated
- tests: fold fuzzers (tracker, appendText, splices/reorders, root arrays, diffRevisions) with
  codec round trips, adversarial cases, cap, ownership, cost and GC guards
2026-10-24 09:37:12 +00:00
Mario Zechner
1e42501bb3 fix(durable): keep pi.live cached in the partial-commit cost test
The suite's evicting run (PI_DURABLE_DOCUMENT_RETENTION_MS=0) evicts the unwatched pi.live after every
commit, so each partial commit replayed the document's delta chain and the per-commit cost grew with the
stream. Pin the document retention in that test so it measures appends, as the default retention does.
2026-10-24 08:35:20 +00:00
Mario Zechner
da412b0392 feat(chord,durable): appendText for cheap string appends; Durable partial commits append instead of reassigning
Follow-up to #9265.

- chord: Delta appendText(draft, key, text) / StringKeys<T> record an append op without the
  O(length) prefix compare that a plain `draft[key] += text` costs in prepare()
- durable: partial commits append text and thinking deltas with appendText (assignJson plan,
  checked by length), and apply tool-call argument changes from their own StreamingJsonParser
  of the raw toolcall_delta text instead of reassigning the arguments snapshot
- durable "." entry-graph budget 73 -> 76 for the parser it now imports
2026-10-24 08:21:41 +00: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
136b87e8a7 docs: require changelog entries for work targeting main from any branch 2026-10-23 22:16:54 +02: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
Petr Houška
1c8cb17817 fix(tui): bound image line search by tallest registered image
Placeholder rows below a Kitty image line can contain box padding,
borders, or side-by-side text. Stopping the backward search at the
first non-blank row dropped those images once their first row scrolled
above the viewport. Search back at most the tallest registered image
height instead; the existing hiddenRows check keeps results correct.
2026-10-23 21:59:31 +02:00
triagent
4464442f95 fix(tui): crop boxed images scrolled above the fullscreen viewport
Image placeholder rows inside padded or colored boxes contain spaces and
SGR codes, which stopped the backward search for the image line, so the
whole image disappeared once its first row scrolled out of view.
2026-10-23 21:59:31 +02:00
Petr Houška
a7b94a03f0 fix(ai): preserve literal data in Gemini tool schemas
Restrict const rewriting to schema-bearing keywords and preserve __proto__ names when copying schema maps. Add regression coverage.

Closes #11308
2026-10-23 21:53:34 +02:00
triagent
7314819f0f fix(ai): send const as enum in Gemini tool schemas 2026-10-23 21:53:34 +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
triagent
f3fa8bd924 fix(ai): do not count faux cache writes as input tokens
With a sessionId, the faux provider reported newly cached prompt tokens
both as input and as cacheWrite, so input + cacheRead + cacheWrite
exceeded the estimated prompt. Every prompt token now goes either to
cacheRead or to cacheWrite, and input is 0 while caching is simulated.
2026-10-23 21:22:02 +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
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