The SDK always passed initial tools, so the constructor never restored the
loadout recorded in the transcript, and resumed sessions dropped tools activated
during the session. Sessions that use the default tools now restore the recorded
loadout, with --tools +name/-name entries applied to it.
fixes#10785
Sends copied text to the terminal with OSC 52 after the local clipboard
write, as over SSH, for remote clients without SSH variables such as
multiplexer attaches and remote IDE daemons.
closes#10826, closes#10835, closes#10114
A misplaced options line was ignored, so its timeout_ms never applied and
the script ran without a deadline. parseCodemodeSource now throws
CodemodeSourceError before the script runs.
fixes#10631
A just-removed auth.json.lock directory can stay delete-pending on Windows
while another process holds a handle, making mkdir fail with EPERM. Retry
this briefly (capped separately from ELOCKED) so real permission errors
still fail fast.
fixes#10801
Print mode now writes a warning to stderr and exits 1 when the final
response stopped at the model's output token limit. The partial text
is still written to stdout.
fixes#10799
The codemode row shows "Elapsed" while the script runs and "Took" once
it finishes, like bash rows. The timer logic moves from the bash renderer
to render-utils, and scripts send an initial update so the timer starts
before the first nested call.
closes#10708
MCP 2026-07-28 allows structuredContent to be any JSON value, including
arrays and null. callTool() rejected non-object values, failing the whole
tool call. CallToolResult.structuredContent is now unknown, and MCP tools
without an output schema declare structuredContent as unknown to codemode.
refs #10416
Servers with resources registered no tools until resources/list and
resources/templates/list finished, so a slow listing (a large drive)
kept their tools out of the first prompt. The lists only fed counts in
/mcp and pi mcp list; both now show that a server offers resources.
The resource tools still list and read resources on demand.
fixes#10526
Entries like !~/skills/pdf, +~/x and -~/x in the extensions, skills,
prompts, and themes settings were compared with a literal ~ and never
matched.
fixes#10765
Deletes packages/client, packages/protocol (CBOR codec), packages/server, coding-agent's
src/experimental (except source-resolver.ts, which source runs still preload), src/cli/experimental,
src/client, the example plugin, and their tests. coding-agent no longer depends on @earendil-works/chord.
The installer lock drops esbuild, so its platform-package sanity check goes too.
On each OpenRouter refresh, combine the catalog (built-in models plus a newer pi.dev catalog) with GET /models/user for the user's key: keep only the listed chat models and take their context size, max output and prices from it. Image and classifier models are kept. The combined list is saved and replaces the OpenRouter catalog, including offline.
- ai: add personalizeOpenRouterCatalog and RefreshModelsContext.baseUrl, so the request follows a base URL configured in models.json (regional endpoints)
- ai: share OpenRouter price conversion with the generator; negative prices (router models such as openrouter/auto) become 0
- coding-agent: add withPersonalizedCatalog next to withRemoteCatalog
- docs: OpenRouter section in providers.md
fixes#10353
joinAdjacentText checked endsWith on the growing joined string, which
makes V8 flatten the concatenation on every item. Collect each text run
in an array, check the separator against the last non-empty part, and
join once. Same fix in the durable code mode copy.
closes#10678
Runs started by pi.sendMessage(..., { triggerTurn: true }) skipped before_agent_start. After their first tool call, the next-turn refresh fell back to the base prompt options and patched away the sections handlers had added, changing the system prompt mid-run and breaking the provider prompt cache.
These runs now go through the same setup as prompt(): compaction check, before_agent_start, tool loadout, nextTurn messages, handler messages, and the system patch. BeforeAgentStartEvent is a union on messageType ("user" or "custom"); custom events carry the starting message.
The session counts as busy from the start of setup, so messages sent while handlers run go into the starting run instead of starting a second one, and an abort requested meanwhile aborts the run once it starts.
closes#10267, closes#5581, closes#10639, closes#10289
partly fixes#10606
Tools that reach other tools declare it with reachesTools(input); MCP uses it to wait for the servers a call needs and to decide which codemode or tool_search tool to activate, instead of parsing codemode scripts and comparing parameter schemas. Namespaces get aliases, replacing codemode's MCP-specific __ suffix rule.
pi-codemode no longer knows MCP: MCP_TYPESCRIPT_PREAMBLE and mcpStructuredContentSchema() are removed, and the MCP result schema describes itself, with the server's local $refs relocated so they resolve as before.
Replayed thinking is often not part of the live context (for example,
radius drops it, and Anthropic drops blocks that do not match their
request), but serializeConversation included all of it. A Pi Mobile
session at ~996K of 1M context tokens produced 1.38M-token summarization
requests that failed on every attempt. Providers only return summaries of
the reasoning anyway; visible text, tool calls, and tool results record
what happened.
* feat(coding-agent): use pi.dev configuration schemas
* refactor(coding-agent): separate schema URL from output path
* docs(coding-agent): document published schema URLs
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
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.
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
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.
Replaces the WeakMap workaround. streamProxy and the agent and coding-agent test
mocks reimplemented AssistantMessageEventStream as plain EventStream subclasses;
they now use createAssistantMessageEventStream().
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
Selection coordinates survived session switches and other transcript rebuilds, highlighting unrelated text in the new transcript. Reset selection and multi-click state before re-rendering session entries.
Based on #9310.
fixes#9311
Co-authored-by: xdagiz <xdagiz@protonmail.com>
A --tools list of only +name/-name entries changes the default tool
selection like the defaultTools setting, e.g. pi -t +codemode. Mixed
lists and patterns in modifiers are rejected, and /reload keeps tools
removed with -name removed.
This adds llama.cpp support for classifiers. Classifier models (Julia-1, Laya, Kev, lev, OpenJev) are listed as typesafe-system-one classifiers instead of chat models; other models keep the llama-cpp-classify fallback. Only 501 and 404 mark a model as chat, and classifier models restored from the model store are checked again once loaded.
Rules and the tool list follow the declared tools (active minus
hiddenDeclarations). The skills hint names no tool when the reader is
hidden. Codemode shows each tool's prompt guidelines with its
declaration, via the new ToolLoadout.getPromptGuidelines().
closes#10343
close() now aborts a per-connection signal that closes the in-flight
client, cancels the retry wait, and waits for the connect attempt to
settle before returning.
closes#10249
--tools no longer removes MCP tools unless an entry starts with mcp__, so
pi --tools codemode can reach MCP servers. Unnamed MCP tools are never
declared directly; only tool_search can load them. --no-tools still
removes everything.
--tools and --exclude-tools accept * patterns, for example
--tools read,codemode,'mcp__radius__*'. --no-mcp disables the built-in
MCP support for one run.
OpenID Connect authorization servers treat a registration without application_type as a web client and reject http loopback redirect URIs. Derive it from redirect_uris when unset (MCP SEP-837): native for loopback hosts and custom schemes, otherwise web.
closes#10493
* fix(coding-agent): keep syntax colors on multiline tokens
- format each line of a highlighted span separately
- add regression test for python docstrings
- fixes#10143
* fix(coding-agent): use text color for string interpolations
- map highlight.js `subst` scope to text color
* feat(ai): support Azure Foundry Chat Completions deployments
The Azure provider only implemented the Responses API, so Foundry
deployments that speak Chat Completions could not be used. On Chat
Completions the deepseek/deepseek-v4-pro model sends `thinking` and
`prompt_cache_key`, both of which Azure rejects with 400.
The provider now dispatches openai-completions alongside
azure-openai-responses. A stream wrapper resolves the Azure endpoint
onto the model first, since the shared openai-completions
implementation reads it off the model.
The request-shape differences are catalog compat rather than api code:
reasoning_effort instead of DeepSeek's thinking field, no prompt cache
parameters, reasoning_content kept on assistant turns so the prefix
Foundry cached stays byte-identical, and effort clamped to the
low/medium/high the deployment accepts.
The provider is renamed from azure-openai-responses to azure, since it
now serves more than the Responses API. The azure-openai-responses api
id is unchanged. Breaking: auth.json and models.json entries keyed on
azure-openai-responses must move to azure, and existing sessions see a
different provider on their next turn.
closes#9645
* feat(ai): apply the Azure deployment name map to Chat Completions
The Responses path already sent the deployment name from
AZURE_OPENAI_DEPLOYMENT_NAME_MAP. The Chat Completions path sent the
catalog model id, so any deployment not named after the model 404'd.
The Azure provider now swaps the deployment name into the request via
onPayload before dispatch, then runs the caller's own onPayload on it.
model.id stays the catalog id. The map helpers move to
azure-openai-config.ts so the provider can share them without
importing the SDK.
* docs(ai,coding-agent): add changelog entries for the Azure provider rename
Breaking: the azure-openai-responses provider is now azure. Users rename
the key in auth.json, models.json, and settings.json; SDK users switch to
getModel("azure", ...) and providers/azure.
Also note Azure Foundry Chat Completions support (DeepSeek V4 Pro).
---------
Co-authored-by: David Brailovsky <davidbrai@gmail.com>
When the terminal goes away, stdin reads and setRawMode fail with EIO (pi left in an orphaned background process group) or ENOTTY (revoked tty on macOS). These were uncaught, recorded in crashes.json, and prompted users to run /bug. Handle them on stdin like stdout/stderr and skip crash recording for dead terminal errors.
The bug report hint test stubbed InteractiveMode without the new
maybeShowInstallChangeWarning method.
On macOS fs.watch returns before libuv's FSEvents stream is live, so
changes made right after starting a watch could be lost. Rescan once
after installing native watchers there.
image() output is also written to a temp file and the result names the
path before each image, so later turns can reference generated images.
Output files are now created through one helper (still tmpdir()) and
are readable only by the user.
closes#10310
Resolve the QuickJS wasm path once and spawn the bundled codemode worker
from an in-memory data: URL. Show a restart hint when errors occur after
the install changed on disk.
closes#10439
Cancelling a request or model refresh while an OAuth token refresh was in
flight discarded the rotated credential. Providers that rotate refresh tokens
(e.g. Sign in with ChatGPT) had already invalidated the old token, so the next
request failed with refresh_token_invalidated.
The caller's signal now cancels only the wait for the credential lock. Once a
refresh starts, it completes and is persisted, bounded by the refresh timeout.
Request auth and model-list refresh share this path.
Add pi.registerToolRenderer((toolName, next) => renderers), which chooses the
renderers for calls to any tool, including tools that are not registered.
Resolvers run in extension load order; next() returns what the remaining
resolvers, then the registered tool, would use. Interactive mode and HTML
export both resolve through it.
The MCP extension uses it to render every mcp__<server>__<tool> call with the
MCP renderers, so calls in a resumed session no longer render fully expanded
before their server connects, or for good if it never does.
closes#10285