Together no longer lists Kimi K2.6, which broke type checks against the hydrated catalog. Tests use Kimi K3 and the Together default model now points at it.
deferred MCP servers now activate tool_search instead of codemode, so their tools load as declared tools without codemode. The previous behavior is available as codemode-deferred. The unreachable-tools warning now accepts an active tool_search as reaching undeclared MCP tools.
Servers with resources are reached through Codex's list_mcp_resources, list_mcp_resource_templates, and read_mcp_resource tools; MCP App resources and icons are left out. toolExposure sets the exposure of single tools by name or pattern. Tool annotations are passed on to ToolDefinition and ToolInfo for permission extensions. MCP text over 20KB is cut in the middle like in Codex and saved to a temp file. OAuth takes scope and a Codex-style callbackUrl, and stdio servers expand ~ in command, args, and cwd. Also fixes the !command header example in the MCP docs.
McpClient lists resources and resource templates, all pages or one page at a time, and reads resources. The OAuth callback server can name another host in its redirect URL than it listens on, and its default path is /callback.
pi mcp add writes stdio or HTTP server entries to the global or project mcp.json, pi mcp remove deletes them. Log messages servers send with notifications/message are appended to mcp.log in the agent directory, rotated at 5 MB.
Adds defineTask, registry-resolved phase handlers, and a scheduler that
decides every task transition in one callback on the Session line:
reservation with migration, dependencies, precedence rules in a step
before each phase, handover to replaced definitions, direct-task abort
(mark, signal, join, fresh abort invocation), orphaning of blocked tasks,
open reconciliation, and close that joins invocations without writing
outcomes. runtime.commit() callbacks return the typed next state; Tx no
longer exposes setTask. Adds Harness.resume, getTask, waitForTask,
abortTask, and task-aware waitForIdle; RegistryReader.subscribe wakes
the scheduler.
This adds experimental support for virtual models. A virtual model is a catalog entry that an extension registers with pi.registerVirtualModel(). It does not talk to a provider itself; for every request it picks a physical model and thinking level. The motivation is to make routing policies pluggable: plan on a strong model and implement on a cheap one, pick a model with a classifier, or move to a larger window when the context grows. Today this needs model switching by hand or hacks in prepareRequest.
The selection stays virtual: model_change records it, resume restores it, and each response records the physical model and thinking level that produced it. Router state can be stored on the session branch, so it follows /tree and forks and survives compaction. Context limits, compaction and image sizing use the physical model a request was routed to. examples/extensions/jev-router.ts shows a full router that uses the Jev classifier to pick Sol or Terra for planning and hands off to Luna after the first edit. docs/virtual-models.md describes the API.
The agent runs every tool call in the final assistant message, including
calls whose output_item.done never arrived. Those can carry cut-off or
mixed-up arguments. With llama.cpp, which omits output_index, two parallel
calls became three (echo a, echo a, echo b), two of them with the same id.
processResponsesStream now fails the stream when a completed response
still has an unfinished tool call.
fixes#9974
Read Finder file URLs before icon image data on macOS. Quote paths in
bash mode and reject terminal control characters before rendering.
Fixes#9999
Co-authored-by: 1dustycy <onedustycy@gmail.com>
Implements Pico5 Package 13: Harness.open over a Session with an application-owned
registry (tools, tool wrappers, hooks, tasks, system prompt sections; batched
publication, stable keyed order), lazy root creation with init, atomic
create/fork with init, conversation-bound commits, fork-aware entries, model
context derivation, and the built-in ConversationConfig document.
Fixes cached documents skipping migration when accessed with another definition
version. Rewrites the Pico5 spec and handoff to the registry/system-prompt
design and removes the superseded live-registries proposal.
Storage reports replayed deltas after the selected base; Session tracks the
count across adopted writes so checkpoint predicates can bound replay.
Also adds internal reserved-root creation and conversation-bound commit
defaults for the upcoming Harness.
Mistral models with effort levels now use reasoning_effort based on
their thinking level map from models.dev instead of a hardcoded id list.
GLM 5.3 no longer gets prompt_mode, GLM 5.2 can use max, and off sends
the model's off value when it has one.
fixes#9678
Add the llama-cpp-classify classifier API. Each question becomes one chat prompt with single-token answer labels; llama-server's pre-sampling next-token log-probabilities give the softmax over those labels. Missing labels are retried with deeper readouts and then reported as errors.
models.dev dropped Kimi K2.6 from OpenCode Go, which broke type checks
against the regenerated catalog. Use OpenCode Go Kimi K3 for
provider-level tests and OpenCode Zen Kimi K2.6 for thinking format tests.
Test each behavior at one layer, share the UI context and tool result helpers in the suite harness, loosen assertions on prompt text, and drop the /mcp manager UI test.
pi-mcp exports toLlmContent(), which converts a CallToolResult to pi-ai-shaped text and image content. The coding agent's MCP adapter uses it.
A typechecked example in packages/agent/examples/mcp-codemode shows a plain Agent with MCP tools and a codemode tool whose nested calls go through runToolCall() and the agent's hooks. The pi-mcp, pi-codemode, and pi-agent-core READMEs describe the same wiring.
pi.registerMcpServer(name, config) adds a server for the session with the mcp.json config shape; pi.unregisterMcpServer() removes it and pi.getMcpServers() lists registrations. Servers registered while loading connect on session_start, later ones right away. A server in mcp.json with the same name takes precedence and /mcp shows the override. Changes to registered servers in /mcp apply to the session only.
The mcp_servers_change event lets any MCP extension connect registered servers; when none handles it, registrations are reported as extension errors.
The tool is named codemode again and scripts configure it with a first-line // @options: {...} instead of the // @exec: pragma. yield_time_ms is gone; max_output_tokens and timeout_ms remain. Comments, docs, and tests no longer describe the tool in terms of another agent.
Built-in extensions marked replaceable are left out when another extension registers the same tool, command, or flag name, so third-party MCP extensions such as pi-mcp-adapter take over /mcp instead of both connecting the same servers.
MCP only activates the codemode exec tool, not another extension's tool named exec, and the first prompt waits at most 10 seconds for startup connections.
pi mcp list connects to every enabled server and reports its state, tools, and errors, exiting with 1 when anything fails, so agents can verify an mcp.json they wrote. pi mcp login runs the OAuth sign-in through the browser, and pi mcp logout deletes stored credentials. Running sessions reconnect servers waiting for a sign-in at the next turn when their credentials changed.
The system prompt points MCP questions to docs/mcp.md, which now covers setting up servers: which file to edit, rules that are easy to get wrong, converting entries from other clients, and verifying with pi mcp list.
/mcp in the TUI lists every configured server with its state, tool count, exposure, and scope, servers that need attention first, and updates while they connect. Selecting a server shows its command or URL, config file, and full error, and offers the actions for its state: sign in (with a paste field for the redirect URL), tools, reconnect, sign out, exposure, and enable or disable. Exposure and enabled are saved to the mcp.json that defines the server.
Startup problems are reported once in the transcript, pointing to /mcp. Outside the TUI, /mcp prints the status, and /mcp login, logout, and the new reconnect subcommand stay available.
The agent loop is back to main's tool execution: it only exports runToolCall(), honors isError results, and passes structuredContent through afterToolCall. Nested calls (ctx.executeTool, ids, events with parentToolCallId, the nestedCalls record) live in coding-agent and start on the first call.
AgentSession no longer knows exec or tool_search. Tools adjust the declared loadout through a generic prepareLoadout hook, and codemode and tool_search use it like any other tool.
exec and tool_search move to built-in extensions under src/extensions/ and register inactive (defaultActive: false). The CLI loads them with MCP; SDK sessions add createCodemodeExtension(), createToolSearchExtension(), and createMcpExtension(). Extensions gain pi.getSettings() and model catalog and classifier access on ModelRegistry.
- structuredContent after tool_result/afterToolCall: providing it replaces it; replacing content without it drops it. No more key-presence checks.
- Tools run outside a session get a ToolContext (loop services plus optional extension fields) instead of a context typed as a full ExtensionToolContext.
- Document that nested calls get synthesized <parent>/<n> tool call ids that never appear in the transcript.
- The exec description no longer mentions yield_time_ms, which is accepted and ignored.
- The sandbox abort test waits for the tool call instead of a fixed delay.
Calls a tool makes through executeTool (for example from exec scripts) now emit tool_execution_start/update/end with parentToolCallId, for extensions and telemetry. Interactive mode keeps showing them inside the parent's row, and Agent.pendingToolCalls only tracks model-issued calls.
The model-issued call's tool result message carries nestedCalls, a bounded record like Codex's ExecutedToolCalls: name, arguments, status, duration, and error, never results. Arguments over 8 KiB per call or 32 KiB in total are omitted, at most 256 calls are kept, and complete is false when anything was lost or still running. Compaction and branch summaries count files touched by nested read/edit/write calls, and HTML exports list nested calls.
While exec is active, codemode.mode on (default) appends the exec declaration to declared tools that scripts can call and lists only non-direct tools in the exec description. codemode.mode only lists every callable tool in the exec description and hides active direct tools from requests. The hiding is a request projection, so the transcript still declares the active set and /tree and resume keep it.
The exec listing depends on exposure, not on the active set, so tool_search loads do not redeclare exec.
- The exec description spends at most codemode.inlineBudget (default 3000) estimated tokens on tool declarations, filled round-robin across namespaces, cheapest first. Every namespace is listed with its tool count, and the listing says COMPLETE or PARTIAL.
- Scripts find omitted and deferred tools with searchTools(query, { limit, namespace }) and describeTool(name).
- tool_search, an optional built-in modeled on Codex, searches codemode and deferred tools and declares the matches for the next model call through the active tool set, so loads are recorded on the branch.
- Both use a BM25 ranker over Codex's search text (name, description, schema properties, namespace) behind a ToolRanker interface.
The codemode tool is now exec and follows the exec tool of OpenAI Codex, so scripts written for Codex run unchanged:
- // @exec: pragma with yield_time_ms, max_output_tokens (default 10000 tokens) and the pi addition timeout_ms; Codex's grammar and error messages
- text(), image() (data URLs, { image_url }, MCP ImageContent), exit(), ALL_TOOLS, tools under normalized identifiers; console and return values append text
- Results start with Script completed/failed and the wall time; failed scripts return an error result that keeps partial output and appends Script error
- Output over the token budget keeps its start and end like Codex; the full text is spilled to a temp file
- MCP tools resolve to their CallToolResult (isError resolves) and are declared as CallToolResult<T>; bash resolves to { output, exit_code, wall_time_seconds }, also for non-zero exits
- The description uses Codex's layout: helper list, deferred-tool guidance, shared MCP types, one section per tool grouped by namespace (MCP servers, with their instructions)
AgentToolResult.isError marks a result as failed while keeping details and structuredContent, so partial output and structured failures (such as MCP isError results) reach the UI and nested callers.
AgentTool.nestedOnly is replaced by AgentContext.nestedTools, the tools callable through executeTool. Tool definitions declare an exposure (direct, model-only, codemode, deferred, hidden). The active set is now the declared set; codemode and deferred tools are callable from codemode whenever registered, so tree navigation, resume and fork cannot drop them.
MCP servers accept deferred and hidden exposure, withdraw tools by re-registering them as hidden, and mcp.json gains autoEnableCodemode.
The SSE parser only limited the partial line, so a stream of short data: lines without a blank line could grow without bound. The event data size is now checked as lines arrive.
When saving a truncated codemode return value to a temp file fails, the truncation notice now names the error.
Restoring tools from the transcript dropped nestedOnly tools because they are never declared to the model, so /tree navigation turned off codemode-exposed MCP tools. Active nestedOnly tools are now kept.
Codemode scripts now run with a 256 MiB QuickJS memory limit instead of growing up to the 4 GiB wasm32 ceiling inside pi's process.
An invalid url made the OAuth store throw while creating connections, which
failed MCP startup for every server. OAuth servers that still answer 401
after a refresh now show as needing sign-in instead of failing each call.
- A script awaiting a promise nothing can settle (no pending host call,
no timers in the VM) now fails instead of hanging until aborted, which
matters because pi runs scripts without a default deadline
- Declarations resolve #/$defs and #/definitions references, which MCP
servers built on pydantic or zod emit for nested arguments
- Share one OAuth refresh between concurrent requests and skip it when the
token was already replaced, so rotating refresh tokens do not sign the
user out; refresh tokens before they expire
- Treat insufficient_scope as needs-sign-in and request the new scope on
/mcp login instead of refreshing
- Only list tools for servers that declare the tools capability
- Retry HTTP connects that fail transiently; show dropped connections as
disconnected
- Deactivate tools a server withdraws on list_changed and restore them
- Keep sanitized tool names unique; default missing input properties
- Accept servers negotiating 2024-11-05 through 2025-11-25; request 2025-11-25
- Default missing tools/call content, never cancel initialize, add onClose
- Open the GET stream only after notifications/initialized, reconnect it
with backoff, and send Last-Event-ID only when resuming
- Resume response streams the server closed before answering; fail the
request instead of hanging when a stream ends without a response
- Reject 202 for requests, ignore bodies for notifications, include the
response body in HTTP errors
- Hand 401 and insufficient_scope 403 to the auth provider with the
rejected token; share one refresh between concurrent 401s so rotating
refresh tokens are not burned; skip refresh for step-up scope
- Track token expiry, normalize issuer trailing slashes
- Shut stdio servers down per spec (stdin, SIGTERM, SIGKILL) across their
process group