Without metadata there is no issuer to compare an RFC 9207 iss parameter with, so the check was skipped and a malicious MCP server could receive a code from another authorization server. Reject the response before the code exchange instead.
Reported by Alexios Stefanopoulos (@AizenvoltPrime).
Split the host into transport-independent execution logic and a VM driver, so the VM can run in a worker thread (CodemodeSandbox, unchanged), on the calling thread (InlineCodemodeSandbox), or in another isolate over RPC (RemoteCodemodeSandbox).
Add the /portable entry without Node imports and the /cloudflare entry with CodemodeSandboxDurableObject, meant to be deployed as its own Worker so scripts never block the caller's thread. Scripts that compute without awaiting a tool are bounded by an interrupt budget, now also polled while draining promise jobs. examples/cloudflare shows the setup.
read returns an image file as one image block (programs get an ImageContent) plus an info diagnostic,
instead of an unsupported_image error. Limits are the conversation model's inputLimits.images.resize,
by default 2000x2000 pixels and 4.5 MB of base64.
- Without a processor: PNG, JPEG, GIF, and WebP within the limits pass through undecoded; the byte limit
is checked before reading the file, dimensions from the header. Larger images and BMP are error results.
- With createCodingTools({ images }): the processor orients by EXIF, converts BMP to PNG, and shrinks to
fit. ImageProcessor.prepare is async.
- @earendil-works/pi-durable/images: createPhotonImages(wasm) on @silvia-odwyer/photon 0.3.3 (web build);
/images/node and /images/cloudflare load the wasm. The root and ./tools entries never reach it (enforced
in check-entry-graphs).
- An image must be read whole: one that changes while read is read again, unlike a growing text file.
- A model without image input gets a diagnostic that it sees a placeholder.
Nested tool calls: ToolExecutionApi.executeTool() runs another tool as its own pi.tool task owned by the caller,
keyed for replay-safe reattachment. Results for programs live in the caller's NestedResultDoc until it settles;
pi.live.nestedTools shows each call with the arguments it runs with and a summary. TaskOptions.abandonOnRestart
aborts work its creator awaited only in memory before it runs again after a restart.
Structured output: structuredOutputSchema and structuredOutput, validated; a tool without a schema gives programs its
bounded output (one text as a string, one image as itself, else the list). ToolExecutionResult.content is now output.
callers and modelTools decide who may call a tool.
Storage errors: any error a Storage method throws fails the Session, which closes itself; later calls and waits reject
with SessionFailed, watches end with session_failed, and Session.closed settles. Nothing retries in the Harness.
StorageRejected is removed; StorageRequestError marks invalid reads that fail only their call. Listener and onReport
errors are contained and reported.
Also updates scripts/issues/triage.ts and the vacation experiment for the content -> output rename.
Syncs issues with gh, triages new ones with a pi-durable agent (category, duplicates, summary, p0/p1/none suggestion), and serves a web UI for decisions.
The nested worktree dedup compared the context file's realpath with the
main repo's file, so a worktree AGENTS.md symlinked to the main repo's
copy was skipped along with it. Compare the walked directory's realpath
plus the file name instead.
fixes#10681
Remove hardcoded Opus 5.5, Sonnet 5.5, and Haiku 5.5 fallbacks now that models.dev lists them. Fix the Sonnet 5.5 test to expect the official cache read price of 0.1.
Long Mistral streams (e.g. extended thinking) were aborted once timeoutMs
elapsed, even while data was still arriving. The timeout now covers only
waiting for response headers; body stalls are left to the HTTP client idle
timeout.
fixes#10609
Editor and Input wrap their drawn cursor in fake cursor markers. The TUI
resolves them to reverse video, or drops the focused one when the hardware
cursor is shown, so terminals that extend edge cell colors into the window
padding no longer stretch the drawn cursor at the first or last column.
Also keep CURSOR_MARKER when an overlay ends directly left of the cursor.
The concurrent prompt tests slept 10 ms and assumed the first prompt was streaming, and the reftable watcher test allowed 3 s for a real fs event. Both failed under full-suite load during the release run.
models.dev now lists the input rate as Kimi K3's cache write cost on Moonshot, which does not bill cache writes. Claude Haiku 5.5 uses adaptive thinking, so the metadata test now expects it.
models.dev loaders for opencode, opencode-go, google, minimax, and others built costs by hand and dropped tiers; they now use getModelsDevCost. OpenRouter prompt-length overrides and Vercel AI Gateway per-rate tiers are parsed into request-wide tiers. Rates a tier does not list keep the base price. OpenRouter time-of-day overrides are skipped.
Adds claude-haiku-5-5 to the anthropic provider with its prompt-length pricing tier (5x above 100k input tokens), adaptive thinking with xhigh/max effort, per-message effort, and mid-conversation system messages and tool changes. Bedrock requests detect Haiku 5.5 for adaptive thinking, native xhigh, block binding, and prompt caching.
Report idle, working, blocked, done, and error to terminals that support
the Program Status Protocol. ProcessTerminal sends the support query with
the keyboard query and reports only after the terminal answers before DA1;
PI_PROGRAM_STATUS=1|0 overrides detection. Interactive mode reports runs,
compaction, extension dialogs, and logins. agent_settled now carries
aborted so cancelled runs report idle.
closes#10607
The OAuth flow in pi-mcp takes a signal for every request, and an aborted
refresh no longer falls back to a new authorization. The streamable HTTP
transport closes its session with the last request's token instead of
asking the auth provider, which could refresh first.
The MCP extension owns one signal per session: it aborts sign-ins on Esc
and on session shutdown, replaces the generation counter, and shutdown
waits for aborted sign-ins to clean up. Requests to the authorization
server time out after 15s, and pi mcp login --timeout limits the whole
sign-in.
closes#10565
Termux reports process.platform as android, so the Linux-only branch skipped
termux-clipboard-get and Ctrl+V pasted nothing. Also show the Termux:API hint
when a Termux copy fails.
fixes#10391
Under `node --watch`, Node 24.19+ and 26.x post `{ "watch:require": [...] }`
on the worker message channel (nodejs/node#65044). The image resize code
treated the first message as the worker's reply, resolved it to `null`, and
dropped the image as "could not be resized below the inline image size limit".
Worker replies now carry a `type` marker, and the main thread ignores any
message without it. The reply type is now either a result or an error.
fixes#10527
Mistral reports transient server failures as finish_reason "error". Its
error message now includes "server error" so the retry classifier treats
it as retryable.
closes#10487
TERM_PROGRAM=herdr enables hyperlinks with image protocols off. Herdr is
checked before outer-terminal detection so leaked variables such as
KITTY_WINDOW_ID do not enable images inside Herdr panes.
closes#10573
Try 53692 first so forwarded setups keep working, then let the OS pick a
free loopback port when 53692 cannot be bound (e.g. Hyper-V/WSL port
exclusions on Windows). The authorize URL, paste prompt and token exchange
use the bound redirect URI.
closes#10571
Open the manager before startup connections finish and keep connection actions in the background. Serialize reconnects and replacements, wait for current readiness, and resolve the active connection when executing tools.
Adds the openai-decisions classifier API with gpt-6-luna on the openai provider. bool questions map to predicates with the criteria in the instructions, refusals fail the result, and gateway timeouts (504) are not retried. The model needs an API key and is hidden from availability while openai uses Sign in with ChatGPT.
Adds optional images to ClassifierContext. Models without image input and APIs that cannot send images return an error result. Codemode's models.classify() accepts images.
Moves the shared classifier HTTP code out of system-one-shared.ts into classifier-shared.ts.
Text items get ==> text N/M <== headers when there are several, and
console output is grouped into one <console_output> block. Console text
items are marked with console: true.
The idle check reads settings.contextRetentionMs from commit listeners,
reconciliation, and the expiry timer. A throwing settings getter escaped as an
unhandled rejection; it is now reported and the kept contexts are dropped.
runtime.context() keeps each conversation's last read range and derived
view. A later read by any of the conversation's tasks with the same head
marker scans and derives only the entries committed since, so a
generation no longer rereads and re-derives the whole transcript for
each model request. Busy conversations keep the range; idle ones for
settings.contextRetentionMs (default ten minutes), checked on task
changes and by one unreferenced timer where the runtime supports it.
Kept entries are frozen; each read returns its own view arrays.
Add openDurableObjectSqliteStorage(ctx.storage) in
@earendil-works/pi-durable/storage/sqlite/cloudflare: SQLite storage on
a SQLite-backed Cloudflare Durable Object, without per-row copies.
The faux provider's prompt-cache usage estimate compared the previous and
current prompt as two joined strings, character by character, on every
call. It now compares the prompts message by message and compares
characters only from the first differing message. Usage numbers are
unchanged.
A run's input is committed before the system prompt is rendered, so requests
started with the user message. Anthropic then sent the tool list at the top of
every request and each tool change invalidated the whole prompt cache. Context
derivation now moves a system message preceded only by user messages to the
front. A live e2e test gates the cache reuse across a tool change.
closes#10542
Replaces the WeakMap workaround. streamProxy and the agent and coding-agent test
mocks reimplemented AssistantMessageEventStream as plain EventStream subclasses;
they now use createAssistantMessageEventStream().
The response timing added private fields, which made test and host streams built on
EventStream no longer assignable to StreamFn. Keep the start in a WeakMap instead.
pi-ai's AssistantMessageEventStream sets AssistantMessage.durationMs with a
monotonic clock for every API implementation. The agent loop and the durable
tool task set ToolResultMessage.durationMs around execute(). Durable task
records carry startedAt/endedAt stamped by the Session. The coding agent's bash
renderer shows Took from the recorded duration, also after a reload.
closes#10549
buildAdditionalModelRequestFields only handled Claude, so the configured
thinking level never reached OpenAI models on Bedrock and they always ran at
Bedrock's default effort. gpt-oss now gets the flat reasoning_effort, clamped
to low/medium/high. Other GPT models (GPT-5.x, GPT-6) get nested
reasoning.effort, with minimal sent as low since Bedrock rejects it.
closes#9331