Skip to content

v17.2.0

Latest

Choose a tag to compare

@github-actions github-actions released this 30 Jul 07:09
8db0228

@oh-my-pi/pi-agent-core

Fixed

  • Provider-native compaction failures now surface their transport error instead of silently switching to generic summarization; streaming V2 still falls back to native V1 when available.

@oh-my-pi/pi-ai

Added

  • Added first-class parentTurnId support for nested Codex requests, allowing stream options and metadata helpers to accept and safely propagate the initiating turn's ID.
  • Added preservation of the Codex encrypted_function_args plaintext-collaboration marker on replayed function calls, keeping server-marked plaintext tool arguments from being reinterpreted as encrypted on subsequent turns.
  • Added interactive Exa API-key login through /login exa, opening the official API-key dashboard and saving pasted keys to the credential store (#1798).
  • Cursor's modern exec wire protocol is now handled end to end. agent.proto models the frames current Cursor CLI builds emit — the seven Pi tools (ExecServerMessage 45-51), hooks, subagents, allowlist prechecks, MCP state, smart-mode classification, canvas diagnostics, conversation search, agent-store conflicts and git diff — and every one of them gets a typed answer. The Pi frames run their local equivalents (read/bash/edit/write/grep/glob); the rest answer with the error, not-found or empty-but-valid variant that is actually true of this client. Frames this build cannot name at all now raise ExecClientControlMessage.throw with unknown_exec_variant, and recognised frames with no truthful answer (git_diff_request, whose GetDiffResponse has no error variant) raise exec_variant_unsupported, instead of a silent ack that leaves the server waiting.
  • lsp is advertised in the MCP tool catalog again. It was filtered out as a Cursor-native tool, but the native diagnostics frame covers one of roughly ten LSP actions, so the other nine were unreachable.
  • Added pinSessionOAuthAccount support for backdating the sticky's last-use timestamp (options.lastUsedAtMs), so pins restored from persisted sessions keep the provider's warm-window semantics: resumes inside the prompt-cache TTL reuse the account, stale resumes still re-rank.

Changed

  • Codex turn metadata now reserves the codex-rs code_mode_tool_names key, preventing caller-supplied client metadata extras from colliding with the core-owned field.
  • Codex SSE requests to the official endpoint now use zstd-compressed bodies by default to match the official client, which can be disabled with PI_CODEX_ZSTD=0.
  • API-key validation now preserves provider HTTP status and retry headers, allowing authentication, rate-limit, and server failures to retain their original error classifications.
  • The Cursor Pi arg translation (piReadPath, piJoinPath, piLsPath, piEscapeRegexLiteral, piLimit) moved to providers/cursor-pi-args, re-exported from providers/cursor/exec-modern so existing imports are unaffected. The legacy pi shim shares these helpers and is compiled into the bundled virtual module registry, where a nested providers/<dir>/<mod> specifier is unresolvable under bunfs — and importing them from the exec module would drag the whole protobuf graph in for two string functions.

Fixed

  • Fixed Novita login rejecting valid API keys belonging to Developer and Basic team members by validating against the chat completions endpoint instead of the billing balance endpoint.
  • Fixed Cursor resource_exhausted errors being incorrectly classified as QUOTA_EXHAUSTED (which caused 30-minute credential blocks), mapping them to MODEL_CAPACITY_EXHAUSTED with a shorter backoff instead.
  • Fixed a crash in Amazon Bedrock and Devin providers when Context.systemPrompt is passed as a bare string.
  • Fixed aborted usage-limit recovery incorrectly blocking credentials or waiting on local usage fetches after the session had already changed.
  • Fixed Codex WebSocket sessions echoing stale or missing turn states by capturing x-codex-turn-state refreshes from response metadata event headers.
  • Fixed Harmony-dialect models (e.g., gpt-5.x, openai-codex) failing with invalid_prompt or "Request blocked" errors by escaping reserved control tokens in untrusted user and tool-result text.
  • Fixed named forced tool_choice not being enforced on string-only OpenAI-compatible hosts (such as llama.cpp and LM Studio) by narrowing the advertised tools to the forced tool.
  • Fixed direct Anthropic Claude Opus requests failing with HTTP 400 when the endpoint rejects strict tool fields.
  • Fixed usage-based credential ranking for Anthropic accounts where a missing long-window (7-day) metric was incorrectly treated as a short-window metric.
  • Fixed legacy Codex usage blocks continuing to gate all models after per-meter backoff was introduced, splitting the old shared scope into independent chat and spark blocks while maintaining backward compatibility with older clients and database schemas.
  • Fixed Anthropic retry loops ignoring maxRetryDelayMs for long server retry-after hints, so over-budget delays surface immediately without losing response details or abort cleanup (#7003).
  • Added interactive xAI API-key login with key validation through the xAI models endpoint.
  • Fixed Google Gemini and Vertex tool declarations carrying numeric, boolean, object-valued, or mixed enum arrays that the Google Schema wire type cannot represent. Unsupported enums are omitted while valid string enums remain constrained.
  • Umans usage provider: fetches GET /v1/usage and surfaces the rolling 5h request window + concurrency limits in /usage, omp usage, and the TUI status bar.
  • Fixed ranged legacy Cursor reads reporting the returned window byte length as the full file size.
  • Updated the Cursor client build advertisement to activate the modern exec-frame protocol handled by this provider.
  • Fixed a windowed Cursor read reporting the window's line count as the file's. total_lines and file_size were derived from the payload, which is the whole file only for an unranged read — a 20-line page of a 100-line file answered total_lines: 20, which a paginating server reads as the end of the file. The count now comes from the read's own record of the file (details.meta.truncation.totalLines), falling back to counting the payload when the read returned the file whole.
  • Fixed a pi_grep that hit the native backend's internal match ceiling answering as an unqualified success. GrepTool folds that cap into the flat details.truncated alone, setting neither details.truncation nor perFileLimitReached — the two fields the Pi result was built from — so the one truncation a caller can neither detect nor page around was the one it was never told about. The flat flag is now translated into a PiTruncation, and only when no specific cap already reported itself.
  • Fixed a pi_grep frame's context and limit vanishing from the transcript. The bridge honors both by building a scoped grep, but neither is expressible in the model-facing schema, so the synthesized block recorded a plain pattern/path search — replaying a context-widened or capped search as an ordinary grep sitting beside output no ordinary grep produces. Both are now recorded on the block.
  • Fixed a Cursor MCP resource listing shrinking to a count in the transcript. The full URI/name/mime catalog goes out on the wire, but the paired local result recorded Listed N MCP resource(s) — and rebuilt history is serialized from that result, so one reload later the model knew it had seen N resources and could name none of them. The paired result now lists what the answer carried.
  • Fixed the pi_read range translation padding the slice it asks for. piReadPath composed a plain :N+K selector, which the local read tool expands by one leading and three trailing context line — so a frame naming offset 5/limit 20 received lines 4-27. Ranged Pi reads now compose :raw:N+K; the wire result is an opaque output string, so the line-number gutter raw also drops carries nothing the contract needs.
  • Fixed four Cursor exec frames answering with a result whose oneof was never set. In proto3 that is not an empty result — the server reads it as "the tool ran and produced nothing", indistinguishable from real success. listMcpResourcesExecResult, readMcpResourceExecResult, recordScreenResult and computerUseResult now send ListMcpResourcesSuccess{resources: []}, ReadMcpResourceNotFound{uri}, RecordScreenFailure and ComputerUseError respectively.
  • The MCP resource frames now answer from the host instead of a fixed verdict. CursorExecHandlers gained listMcpResources/readMcpResource, so a host holding live MCP connections advertises them; the empty catalog and not_found above remain the answer when no handler is supplied. A handler that throws surfaces as ListMcpResourcesError/ReadMcpResourceError rather than collapsing into "none exist", which the model cannot retry. A read carrying download_path forwards it and answers with ReadMcpResourceSuccess.download_path and no content, which is what that mode means.
  • Fixed Cursor connect_scm calls losing their repository and settling on a fabricated verdict. The target rides in the ConnectScmArgs.target oneof, so reading a flat github property always saw undefined; and the authoritative success/error/rejected result only arrives on the completion frame, so answering at the announcement persisted a fixed failure for every call — including the ones the server went on to accept. The block now opens on the start frame and settles from the completion's decoded result.
  • Fixed interleaved Cursor tool calls corrupting each other. The stream decoder tracked a single "current" block and settled it on any toolCallCompleted, ignoring the envelope's call_id: a completion for one call closed whichever block happened to be open and paired it with the wrong result, and start A, start B orphaned A entirely so its own completion settled B while A was never paired — which strips the whole interaction from every rebuilt transcript. Open blocks are now retained per envelope call_id, and end-of-stream closes all of them rather than only the last.
  • Fixed a Cursor search_conversations call leaving no transcript block. The frame is answered from a fixed verdict, so nothing downstream pairs a result for it, and an unpaired call takes its whole interaction out of every rebuilt transcript.
  • Fixed a Cursor read_mcp_resource call leaving no transcript block. The frame runs locally — and in download mode writes a workspace file — but synthesized no tool call and paired no result, so the read was invisible in the UI and absent from every rebuilt history; a resource download could mutate the workspace with nothing on record. The frame now synthesizes a read_mcp_resource block (not read: it is a remote MCP operation, and the name drives rendering and prune semantics) and pairs a result on success, not-found and error alike. Frames answered without a handler still synthesize nothing, since nothing ran.
  • Fixed a Cursor list_mcp_resources call leaving no transcript block. The model consumed the catalog, but the frame synthesized no tool call and paired no result — its streamed ListMcpResourcesToolCall announcement was equally unrecognized — so the listing was invisible in the UI and absent from every rebuilt history. Frames a handler answered now synthesize a list_mcp_resources block and pair a result derived from the same answer that went on the wire; frames answered from the fixed no-handler catalog still synthesize nothing, since nothing ran.
  • Fixed an unavailable pi_edit/pi_write answering with the error variant. Both results model refusal and failure as separate oneof cases, and a denial reported as error reads as "the tool ran and broke" — inviting a retry of an operation that was never permitted. A frame whose tool is not granted, or whose handler produced nothing, now answers with PiEditExecRejected/PiWriteExecRejected; execution failures keep the error variant.
  • Fixed a Cursor MCP approval probe actually running the tool. A modern mcpArgs frame carrying smart_mode_approval_only asks only whether a call would be permitted, not for the call itself. The decoder dropped the flag, so the frame ran a side-effecting MCP tool the user had not been asked about, then ran it again when the real call followed. The flag is now carried through and the probe is answered from the host's policy without executing: approved only for a definite allow, refused for a deny, for a mode that demands a prompt the frame cannot raise, and for a tool the session does not have. No transcript block is synthesized either, since nothing ran.
  • Fixed the Cursor stream's end-of-transport cleanup erasing the arguments of every block still open. Blocks whose args arrive whole (todo, connect-SCM, MCP) never feed the streamed partial-JSON buffer, and reparsing an absent buffer yields {}, so a truncated or disconnected turn rebuilt those calls with no arguments at all. Only blocks that actually streamed their args are reparsed now.
  • Fixed a Cursor stream dying mid-turn stranding the call it left open. connect_scm and native todo blocks are stamped resolved the moment they open, so the agent loop synthesizes no placeholder and only their completion frame pairs a result — a transport that closed first left the card animating and the call unpaired, which takes the whole interaction out of every rebuilt transcript. The terminal-error path now closes open blocks and pairs those server-owned calls with an interrupted result; the flush ran only on clean completion before, which is not the path a dying stream takes. Exec-settled MCP blocks are left alone, since the dispatch that ran them owns their result.
  • Fixed the Pi exec frames displaying a different operation than the one they run. The provider synthesized its transcript block from a second, hand-rolled translation of the frame args, so pi_read's offset/limit were shown as a whole-file read, pi_grep's literal pattern as an unescaped regex, and pi_find's path/glob join differed from the executed one. Both sides now share a single translation.
  • Fixed the streamed pi_*_tool_call announcements that modern builds send alongside each exec frame being unrecognized. The exec channel already synthesizes those blocks when it runs the tool; the duplicate was avoided only because the decoder recognized none of the variants, which would have started double-rendering as soon as any one was added.
  • Fixed pi_bash results reaching Cursor clipped with no truncation notice. Two truncation records exist locally: read/grep set details.truncation, which carries an explicit truncated flag, while bash sets details.meta.truncation, whose record has no such flag — its presence is the signal. piTruncation read only the first shape and required the flag, so every real Bash truncation was dropped and the server was told the clipped output was complete. Both shapes now translate, and an explicit truncated: false still suppresses the field.

@oh-my-pi/pi-catalog

Added

  • Regenerated the Cursor agent protobufs (discovery/cursor-gen/agent_pb.ts) against the modern agent.proto, adding the message and enum families current Cursor CLI builds emit: Pi tool exec frames, hook queries and responses, subagents, allowlist prechecks, MCP state, smart-mode classification, canvas diagnostics, conversation search, agent-store conflicts and git diff. Purely additive — no existing exported symbol changed shape.

Fixed

  • Fixed an issue where LM Studio first turns failed with a 400 Invalid tool_choice error when a named tool was forced, by using the supported tool_choice: "required" string selector.

@oh-my-pi/pi-coding-agent

Breaking Changes

  • Removed the DEL, DEL.BLK, COPY, and COPY.BLK hashline edit operations. Use CUT / CUT.BLK for deletion; removed content remains available to PASTE.

Added

  • Added server-name autocomplete for /mcp commands (enable, disable, test, remove, reconnect, reauth, unauth) using configured and runtime-discovered MCP servers.
  • Added CUT and PASTE ops to the hashline edit tool for moving code without retyping it: CUT N.=M (and .BLK block forms) capture lines into a clipboard register, and PASTE operations insert them. The register flows across sections within a patch (cross-file moves) and persists across edit calls per session.
  • Added --from-claude and --from-codex session imports (including compaction state for Codex), also available from /resume @claude and /resume @codex.
  • Added interactive Exa API-key onboarding through /login exa, opening the official key dashboard and saving pasted keys for authenticated web search while preserving EXA_API_KEY and explicit-selection public MCP fallback behavior (#1798).
  • Added ExtensionContext.getAsyncJobSnapshot() so extensions can read the owning session's async-job state without relying on process-global job-manager identity
  • Added opt-in tui.codexResetFireworks celebrations for unscheduled Codex weekly usage resets and newly banked saved resets, shown in a theme-aware top-third modal until Escape (#6858 by @joshrzemien).
  • The Cursor exec bridge serves the seven modern Pi tool frames, mapping each to its local equivalent: pi_read/pi_lsread, pi_bashbash, pi_editedit, pi_writewrite, pi_grepgrep, and pi_findglob. The frames are a separate wire family from the legacy args, not aliases, so each mapping is a real translation — pi_grep's ignore_case is the inverse of the local tool's case-sensitivity flag, pi_find searches filenames rather than contents, and pi_edit's replacements are renamed to the local snake_case pairs.
  • providers.autoThinkingMaxEffort (xhigh | max, default xhigh) raises the ceiling of the auto thinking classifier. max became a first-class effort tier after the classifier prompt was written, so auto could never reach it on models that expose the tier — only the ultrathink keyword could. Opting in adds max to the classifier's vocabulary, gated on the target model actually supporting it; the default keeps today's prompt byte-for-byte. The ceiling is enforced inside the effort clamp rather than on the classifier's answer, so a sparse ladder cannot snap an excluded request back up, and the Low floor is still resolved against the model's own ladder. The on-device 3-bucket classifier stays capped at xhigh regardless of the setting. The ceiling governs what auto resolves: a ladder with nothing underneath it yields no auto level, and a thinking.requiresEffort model still gets its lowest supported effort from the transport.

Changed

  • Improved grouped read-call layout by nesting each request's usage metrics beneath its final path.
  • Improved turn recovery to prevent duplicate output streaming during credential rotation or model fallback when visible text has already been streamed.
  • Optimized tool guidance for bash, grep, and glob to be more concise while clarifying shell boundaries and search timeouts.
  • Optimized models configuration resource probing to run in a single child process, reducing startup contention.
  • Startup release notes now default to a compact change-count summary. Use startup.changelogMode (summary | expanded | hidden) to control them; legacy collapseChangelog choices migrate automatically (#6771).

Fixed

  • Fixed Anthropic prompt-cache cold misses on session resume with multiple OAuth accounts: the account that served a session is now recorded in the session file (as a credential_pin sha-256 of the account + org/project scope, so exports carry no plaintext identity) and re-pinned on resume with the session's effective last-use time, so a fresh process no longer re-ranks accounts by usage headroom — which systematically routed away from the just-used account and cold-missed the entire account-scoped cache prefix. Sticky routing was previously stored only in the auth store's KV cache, which is in-memory when a remote auth broker is configured.
  • Fixed Anthropic prompt-cache cold misses on session resume with multiple OAuth accounts: the account that served a session is now recorded in the session file (as a PII-free credential_pin hash) and re-pinned on resume, so a fresh process no longer re-ranks accounts by usage headroom — which systematically routed away from the just-used account and cold-missed the entire account-scoped cache prefix. Sticky routing was previously stored only in the auth store's KV cache, which is in-memory when a remote auth broker is configured.
  • Fixed concurrent createAgentSession calls with the default agent id failing initialization with Agent "Main" was replaced during session initialization — each in-process embedder (e.g. the edit benchmark runner) can now pass a private registry via the newly exported AgentRegistry, keeping every top-level session's "Main" out of the process-global roster race.
  • Fixed task tool blocks duplicating their per-agent progress rows into terminal scrollback on every update: live task frames now pin the transcript live region so mid-run rows are never recorded as frozen snapshots, and a detached background task freezes its progress the moment any of its rows commit to scrollback instead of mutating committed history.
  • Fixed Codex reset fireworks comparing different quota tiers or plans, preventing false celebrations when usage reports switch between Spark and base weekly limits.
  • Fixed Cursor ranged-read results losing the full file byte size after applying the requested window.
  • Fixed empty Codex final-stop recovery discarding an earlier commentary message when both messages shared response metadata.
  • Fixed Advisor availability with providers that refuse echoed reasoning by retrying once with primary thinking stripped and surfacing persistent refusals immediately.
  • Fixed /tan agents being unable to read parent-session local:// attachments by correctly resolving local protocol options against the parent session's artifacts.
  • Fixed Codex web search silently returning plain completions when the hosted web search tool was skipped.
  • Fixed TUI collaboration guest loader not starting when joining or reconnecting mid-turn.
  • Fixed multi-second TUI freezes in reftable-format repositories by moving branch resolution off the render path and adding a timeout to synchronous git spawns.
  • Fixed xd:// device summaries containing control characters and exceeding size budgets by stripping control characters and bounding summaries by UTF-8 bytes.
  • Fixed task.softRequestBudget configuration having no effect on bundled scout and sonic subagents.
  • Fixed quick LSP server exits being misreported as reader failures and resolved an issue where explicit reloads were blocked by initialization backoff.
  • Forced Git subprocesses to use the stable C locale to ensure predictable, non-interactive command output.
  • Fixed compatibility replay issues for pre-upgrade launch brokers evaluating xterm inside the client process.
  • Fixed Advisor cost tracking in the status line across conversation boundaries, ensuring session transitions, forks, and resumes correctly restore or isolate conversation spend.
  • Fixed validation failures for legacy extensions importing from the package root, which previously blocked installations.
  • Fixed ACP clients (such as Zed), TUI status lines, and collaboration guests not updating when model changes occur dynamically within the agent loop.
  • Fixed assistant-facing resource summaries omitting parameterized MCP resource templates, ensuring failed reads list templates alongside concrete resources.
  • Fixed redundant xd:// mount notices and prompt-cache invalidation when resuming sessions or reconnecting devices.
  • Fixed the model picker displaying placeholder model lists instead of the actual credential-aware catalog resolved at registration.
  • Fixed file corruption and snapshot mismatches when writing files through the ACP client bridge by verifying the final on-disk content after client-side post-save formatting.
  • Fixed omp ttsr test silently evaluating source files as prose when their extensions were missing from the allowlist, and expanded the allowlist to support .NET, Shell, SQL, Zig, Dart, Scala, Elixir, and Protobuf files.
  • Fixed automatic light/dark theme switching in direct WezTerm sessions on macOS when DEC Mode 2031 is unsupported, and improved theme-change color responsiveness.
  • Fixed configured retry.maxDelayMs not being forwarded into Anthropic retry handling, so over-budget server retry delays fail fast.
  • Added tokens-per-second throughput to RPC get_state responses for non-TUI clients.
  • Added the RPC set_fast_mode command and typed TypeScript/Python client methods for live fast-mode control.
  • Added fastModeEnabled and fastModeActive to RPC get_state responses.
  • Fixed RPC fast-mode state reporting after direct Anthropic rejects speed: "fast", while allowing explicit re-enable requests to retry priority service.
  • Added opt-in subagent access to checkpoint, rewind, learn, and manage_skill when explicitly listed in an agent definition's tools: frontmatter. Listing one of checkpoint/rewind auto-includes the other. Settings (checkpoint.enabled, autolearn.enabled) remain master toggles.
  • Added a browser.cdpUrl setting that points browser automation at an already-running CDP endpoint by default, so app.cdp_url no longer has to be repeated on every call. Explicit app options still take precedence.
  • Native compaction preserves provider-native success and non-authentication failure semantics while retaining authenticated cross-provider fallback when the native provider rejects credentials.
  • Fixed the Cursor Pi exec bridge silently dropping frame arguments. pi_read's offset/limit were ignored, so a ranged read returned the whole file; pi_grep's literal was ignored, so a fixed-string search ran as a regex and matched the wrong lines; and the path/glob join produced a ./-prefixed spec. Ranges are now composed onto read's :N+K inline selector, literal patterns are escaped, and the join uses node:path. These are optional int32 fields, so a present 0 is honored rather than folded into a default: pi_read with limit: 0 answers with empty output instead of the entire file, and pi_find with limit: 0 clamps to 1 the way the reference client does.
  • pi_grep's context and limit are honored. Neither is expressible in the model-facing grep schema — context width comes from grep.contextBefore/grep.contextAfter fixed at tool construction — so the bridge builds a per-call grep for frames that supply them. GrepTool accepts these as constructor options; the model-facing schema is unchanged, and a frame that supplies neither keeps the shared instance and the session's defaults.
  • pi_ls's limit is still not mapped, now deliberately: it caps directory entries, while the local read tool renders a depth-2 tree with per-directory caps and elision rows and applies a selector as a rendered line slice. Mapping it to :1+K would cap a different unit while appearing honored.
  • The legacy pi shim's regex-literal escaper and path/glob join were verbatim copies of the modern bridge's. Both paths now call the shared helpers, so the two Pi translations cannot drift.
  • Fixed every Cursor pi_edit frame failing instead of editing. Two independent causes: the session drops edit from the tool registry for Cursor so the model uses full-file write, but that registry is also the exec bridge's tool source, so the native frame — which the server sends regardless of the advertised catalog — found no tool; and the retained instance followed the session's configured edit mode, while PiEditExecArgs carries old_text/new_text pairs that only replace accepts (the default hashline takes a single input string). The bridge now resolves a replace-mode instance through its fallback resolver, still wrapped for approval.
  • Fixed a pi_grep frame carrying context or limit escaping the approval gate. Honoring those fields needs a per-call grep, and the per-call instance was built raw while every registry tool is wrapped, so such calls bypassed tools.approval.grep and the exec-tier check for SSH-targeted paths. Both bridge callsites now build it through one shared factory that applies the same wrapper.
  • Fixed Cursor advisors ignoring pi_grep's context and limit. Only the primary session supplied the per-call grep factory, so advisor frames silently fell back to session defaults. Advisors now receive the same factory, gated on the advisor actually having been granted grep.
  • Fixed Cursor advisors failing every pi_edit. The advisor roster handed the bridge the edit instance built for the advisor's own loop, which follows the configured edit.mode (hashline by default) and rejects the frame's old_text/new_text pairs — the same mode mismatch the primary bridge already fixed, on the path it missed. The exec map now substitutes a replace-mode instance, gated on the advisor actually having been granted edit, while the advisor's own loop keeps the tool it was given.
  • Fixed pi_bash killing commands that explicitly asked for no deadline. timeout is optional int32 and bash documents 0 as "disables the command deadline", but a truthiness check folded a supplied 0 into unset, applying the 300s default instead. A present 0 now passes through; negatives, which have no local meaning and would otherwise clamp to the 1s floor, still fall back to the default.
  • Fixed the Cursor exec bridge granting edit and grep to sessions that withheld them. Both bridge-only tools are constructed rather than looked up, and executeTool prefers a constructed override over the registry, so a restricted tool set (toolNames without them, or restrictToolNames) still got a working pi_edit/pi_grep — native frames arrive regardless of the advertised catalog. Both are now gated on the session having actually granted the tool, matching the delete frame's existing check (issue #5680).
  • Fixed Cursor advisor bridge tools bypassing approval settings. The advisor's pi_edit/pi_grep instances are approval-wrapped, but the wrapper reads tools.approvalMode, per-tool tools.approval.<tool> policies and autoApprove only from the execute-time tool context — which the advisor bridge never supplied, so every native advisor frame resolved as yolo with empty policies and ran past a configured ask or deny. Advisors now receive the same context store as the primary bridge.
  • Fixed Cursor's list_mcp_resources/read_mcp_resource frames answering as though the client hosted no MCP servers. The bridge hardcoded an empty catalog and not_found, so resources from servers the session held live connections to were invisible to the model even while the same session read them through mcp://. Both frames now answer from the session's MCPManager — awaiting a server's background resource discovery rather than reading the not-yet-populated cache and reporting "advertises nothing" — and a lookup failure surfaces as an error rather than an empty catalog, which would read as "asked, none exist". A read carrying download_path writes the resource to that path and answers with the path alone, per the wire contract, instead of putting the payload back in the model's context. That path arrives from the server while the general-purpose resolver deliberately honors absolute paths and .., so downloads are confined to the workspace: the resolved target and its deepest existing ancestor must stay inside it, and a target that is itself a symlink is refused. The write then opens O_NOFOLLOW and refuses a non-regular or hard-linked file before truncating, so the final component cannot be swapped for a link or an inode shared outside after the check. A parent directory replaced by a symlink mid-write is still followed; closing that needs openat/dirfd walking, which this does not attempt.
  • Fixed the Cursor native delete frame bypassing approval settings. Unlike every other frame it removes the file directly instead of running a registry tool, so no approval wrapper sat in front of it — the bridge's allowDirectFileMutation grant answers whether a mutating tool was granted, which is a different question from whether the user's policy allows the call. A configured tools.approval.delete: deny, or an always-ask session that this channel cannot prompt in, now refuses the frame and keeps the file.
  • Fixed Cursor download-mode resource reads bypassing the session's mutation restrictions. A read_mcp_resource frame carrying download_path creates and overwrites workspace files without running a registry tool — the same hole the native delete frame had — so a session that withheld write/edit, or one whose write tier is deny/always-ask, still had files written. Both frames now share one grant (allowDirectFileMutation, renamed from allowNativeDelete now that it gates more than deletion) and one write-tier policy check, and the download refuses before the read so a blocked call does not fetch the resource either. The primary session derives that grant before it rewrites its registry: Cursor moves edit out of the tool map and write may be auto-registered later, so reading the map at bridge-construction time would have misjudged both.
  • Fixed pi_ls never reporting that a listing was clipped. The bridge read the entry cap from a flat details.resultLimitReached, which glob sets but read — the tool serving pi_ls — does not: it records the cap through OutputMeta at details.meta.limits.resultLimit.reached. Every capped listing therefore reached Cursor with entry_limit_reached unset, reading as complete. Both shapes are now checked, the same way the truncation translation already handles its two producers.
  • Fixed a mixed-content MCP resource read reaching Cursor mislabelled. The mime type was taken from the first content item while the payload came from whichever item supplied it, so an image blob followed by a text note sent the text as image/png. Each branch now reports the type of the part it actually sends.
  • Fixed pi_read's offset/limit returning more lines than the frame asked for. The range is composed onto the local read tool's inline selector, and a plain :N+K deliberately pads with one leading and three trailing context lines — helpful when a human reads a snippet, wrong for a caller that named an exact range: offset 5/limit 20 handed Cursor lines 4-27. Ranged Pi reads now compose :raw:N+K, which slices exactly the requested lines.
  • Fixed pi_grep returning fewer matches than it asked for when they spread across many files. The local grep windows results to the first 20 files and tells the caller to paginate with skip, but PiGrepExecArgs has no skip field — so a frame asking for 100 matches over 25 one-match files got 20, match_limit_reached unset, and advice it could not act on: output silently short and labelled complete. A search carrying a total match cap now reads enough files to satisfy it (cap+1, so a result landing exactly on the cap is distinguishable from a clipped one) and reports the cap when it actually bites.
  • Fixed every native pi_edit failing after a session switched onto Cursor. The replace-mode edit instance the frame needs was built only for sessions created on Cursor, and the tool roster is not rebuilt on a model switch — so a session that started elsewhere kept its configured-mode edit in the registry, which the bridge resolves before its fallback, and the frame's old_text/new_text pairs failed validation against a hashline schema. The instance is now built from the edit grant regardless of the session's initial provider (lazily, so a session that never reaches Cursor never constructs one) and pi_edit asks for it explicitly through a dedicated accessor. A session that was never granted edit is still refused.
  • Fixed the Cursor bridge's tool resolver being able to execute an unadvertised edit. That resolver doubles as the agent loop's fallback for any call outside the advertised set, so serving edit from it meant a hallucinated call — or one naming a tool the session deselected after startup — could run a replace-mode edit the model was never offered. It is device-only again; pi_edit uses its own accessor.
  • Fixed the legacy Cursor read frame ignoring the offset/limit modern builds paginate with. Only the Pi variant composed a range, so every page of a legacy read returned the whole file (or its own truncation) and a model walking a large file never advanced past the first window. Both frames now translate a range through the same helper, and the answer sets range_applied to describe whether a window was actually composed.
  • Fixed the legacy Cursor grep frame ignoring its pagination offset. The local grep paginates by file through skip and advertises exactly that in its own "use skip=N" advice, so an unforwarded offset re-ran the identical search and answered page one for every page. The answer now reports the offset it applied in offset_applied.
  • Fixed a paginated Cursor read or grep frame being recorded as an unpaginated one. The executed call and the transcript block are built separately, so forwarding the frame's range and page fixed only the execution: the block still showed a bare path and an unskipped search, which is what a reloaded session replays and what the next turn reasons from — a slice of a file presented as the whole thing, and results from a later window presented as page one. Both are now synthesized from the same translation that runs them, including a limit: 0 read, which is recorded as the zero lines it returns rather than a whole-file read.
  • Fixed Cursor advisors answering every MCP resource frame as though the client hosted no servers. Only the primary bridge received the MCPManager-backed resource adapter, so an advisor's list_mcp_resources reported an empty catalog and its read_mcp_resource a not_found even though the advisor shares the session's live connections. Advisors now receive the same adapter; it is not gated on a tool grant, since reading what a server advertises is a different permission from calling one of its tools.
  • Fixed advisor tools bypassing the approval gate. They are built straight from the builtin table, outside the loop that wraps every registry tool, and both the advisor's own agent loop and its Cursor exec bridge (pi_write, pi_bash) run those instances directly — so an advisor granted write or bash executed them regardless of a configured ask or deny. They now carry the same ExtensionToolWrapper as every other tool.
  • Added mcp_notification extension event and multi-listener MCPManager.addNotificationListener API. The runtime already received MCP server-initiated JSON-RPC notifications at the transport layer but had no path to forward them to extensions; every notification (including server-custom methods) is now delivered as { server, method, params } after the manager's own list/update handling. For known list-change methods (notifications/tools/list_changed, notifications/resources/list_changed, notifications/prompts/list_changed) the internal refresh promise is awaited before fanout, so a listener acting on tools/list_changed sees fresh getTools(). Notifications received before any listener attaches are buffered (bounded FIFO, cap 100, drop-oldest — matches IrcBus's MAILBOX_CAP) and drained into the first subscriber, so startup-time frames aren't lost even if the extension binds after MCP discovery. Extensions can use this to bridge push-capable MCP servers (e.g. peer messaging) into session behavior by injecting a mid-turn steer via pi.sendMessage / pi.sendUserMessage.

Removed

  • Removed the dangling MCPManager.setOnNotification single-slot setter, which had no callers in the runtime. Replaced by MCPManager.addNotificationListener — multi-listener, per-listener error isolation, returns an unsubscribe function.

@oh-my-pi/collab-web

Fixed

  • Fixed an issue where the agent would stop silently without a message by ensuring terminal auto-retry failures are properly surfaced as error notices.

@oh-my-pi/hashline

Breaking Changes

  • Removed DEL, DEL.BLK, COPY, and COPY.BLK from the patch language. Use CUT / CUT.BLK for deletion; a cut does not require a following PASTE and leaves the removed content available to later pastes.

Added

  • Added clipboard ops: CUT N.=M captures lines into a register (and deletes them), CUT.BLK N captures tree-sitter blocks, and PASTE.PRE|POST N / PASTE.HEAD|TAIL / PASTE.BLK.POST N insert the captured lines without retyping. The register flows top-to-bottom across sections, so content moves between files in one patch; PASTE does not consume it and the last capture wins.
  • Added PatcherOptions.clipboard for a host-owned register that persists across Patcher.apply batches. Batches work on a fork (forkClipboard) published per landed section (commitClipboard), so failed batches never poison the register and a mid-batch write failure still preserves content already cut from disk.
  • Added clipboard safety guards: a PASTE with an empty register, a capture overwriting un-pasted CUT content, and clipboard ops in same-path sections interleaved across another file's section are all rejected with targeted diagnostics. CUT ranges participate in overlap validation, the seen-lines guard, and drift recovery (every captured line must remap).

Changed

  • Simplified grammar.lark around shared target and position shapes, collapsing the concrete and block CUT forms plus the INS / PASTE position variants into their common grammar rules.

Fixed

  • Prevented CPU and memory exhaustion in streaming previews by rejecting line anchors above Number.MAX_SAFE_INTEGER and ranges spanning more than 100,000 lines.
  • Fixed an issue where recorded snapshot tags desynced from disk when the filesystem transformed content on write (e.g., auto-formatting on save), which previously caused subsequent edits to incorrectly reformat unrelated parts of the file. Patcher.commit now correctly keys the returned file hash and snapshot on the actual content written to disk and issues a warning when a drift is detected.

@oh-my-pi/pi-natives

Changed

  • Split the native voice engine (miniaudio capture/playback, WebRTC peer, Opus media) out of the pi-natives addon crate into a napi-free pi-voice rlib. The addon keeps thin #[napi] adapters, so the JS API is unchanged; the webrtc/opus/miniaudio dependency graph now compiles once into the library and no longer rebuilds with the addon leaf (which recompiles every release via its version-sentinel edit).
  • Release binaries now build in parallel with the test fan-out; npm leaf publishing moved to a dedicated post-validation job (release_native_leaves), and darwin release bazel caches are pre-warmed on native-affecting main pushes — cutting release wall time from the previous serialized tests → cold darwin build pipeline.

@oh-my-pi/pi-tui

Added

  • Added response-level OSC 11 appearance subscriptions to help terminal consumers distinguish confirmed unchanged background classifications from missing replies.

Fixed

  • Fixed native Windows terminal panes freezing their host during forced closure by skipping the stdout-drain wait after ConPTY disconnects.
  • Fixed high CPU usage in the Loader spinner during idle waits by optimizing text wrapping and caching during frame updates.
  • Fixed hash-prefixed UUIDs in prose being misclassified as 8-digit CSS colors and receiving spurious swatches.
  • Fixed unbounded memory growth and potential host freezes when a PTY consumer stalls by capping the pending stdout backlog and treating undrained consumers as a disconnect.

What's Changed

  • feat(coding-agent): autocomplete MCP server names in /mcp subcommands by @Mathews-Tom in #6454
  • fix(tui): bound stdout backlog when the pty consumer stalls by @roboomp in #6856
  • fix(coding-agent): scope Advisor cost to the active session by @paolomazzitti in #6883
  • docs: clarify ttsr edit/write matcherDigest is introduced lines by @roboomp in #6886
  • fix(ttsr): flag text-source inference for unlisted file extensions by @roboomp in #6888
  • fix: forward parseArgs and CONFIG_DIR_NAME from the legacy pi shim by @Gy-Hu in #6907
  • feat(mcp): expose server-initiated notifications to extensions via mcp_notification event by @asteriskSF in #6535
  • feat(ai): add xAI API key login by @paralin in #6647
  • feat(coding-agent): add opt-in max ceiling for auto thinking by @everton-dgn in #6680
  • feat: add opt-in Codex reset fireworks by @joshrzemien in #6858
  • feat(coding-agent): allow checkpoint/rewind/learn/manage_skill in subagents when explicitly requested by @szavadsky in #6938
  • feat(extensions): expose session async job snapshots by @usr-bin-roygbiv in #6939
  • feat(tools): add a browser.cdpUrl setting for the default automation target by @terrxo in #7007
  • fix(ai): bound Anthropic retry-after waits by @metaphorics in #7028
  • feat(rpc): expose live fast-mode control and token throughput by @fredluz in #7036
  • Umans usage provider by @hpost in #4484
  • feat(ai): add Exa API key login by @will-bogusz in #6652

New Contributors

Full Changelog: v17.1.8...v17.2.0