Repository navigation
Pi-Bolt 0.8.6 on Pi 1.2.0 - #27
Conversation
…eCursor is enabled 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.
* feat(coding-agent): publish configuration schemas * fix(coding-agent): preserve config schema metadata * fix(coding-agent): align schemas with public contracts * fix(ai): place compatibility schema with providers * fix(ai,coding-agent): align configuration schema contracts * fix(ai): reuse session affinity schema * docs(coding-agent): document strict theme validation * fix(ai,coding-agent): align runtime and generated schemas * fix(ai,coding-agent): centralize configuration schema contracts * fix(ai,coding-agent): preserve schema validation contracts * fix(coding-agent): validate keybinding schema grammar * fix(ai,coding-agent): refine configuration schemas * fix(ai,tui,coding-agent): align schemas with runtime behavior * fix(tui): normalize shifted symbol keybindings * fix(ai): preserve compatibility auto-detection
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
…labels are applied
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.
fixes #10633
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
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.
… errors end the Session 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.
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.
…ble Object 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.
…ered 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).
Every reconcile pass walked up from every live task to the root to find cancelling owners, and owned live work, idle scopes, chain loads, reservation, finalization, and failFast checks each scanned all live tasks. A chain of n held tasks cost about n^3 steps: 500 took 12 s, 20,000 never finished. The ownership semantics are unchanged; in-memory indexes, rebuilt at open, hold each pass's candidates: - #below: the ownership tree downward around live work; owned live work, idle scopes, and conversation aborts walk down from one node. Ended tasks stay while walks pass through them (conversation owners, chain loads, owners of known tasks), so loaded chains stay loaded. - #intent: cascades walk down from tasks with cancellation intent, not entering another such task, so the nearest cancelling owner still decides the reason; upward checks return at once when it is empty. - #unloaded: only tasks whose chain is not loaded are loaded, and count as inside every scope until then. - #finalizeChecks: finalize is a worklist in rounds from the nearest live holder of ended work. - #runnable and #waiters: reservation and failFast candidates. No walk recurses. Chain of 500 held tasks: 12.3 s -> 50 ms; 20,000: 1.5 s. 8000 parallel nested calls: 18.8 s -> 9.2 s (bench:scheduler-fanout); the rest is document copying of the nested-call index.
NestedCallsDoc (one index per caller, a key per nested call, copied on every admission) and NestedResultDoc are
replaced by NestedCallDoc (`pi.tool.nested-call`): a task-scoped family of the caller keyed by the call's key,
holding { taskId, result? }. Admission creates the member; the nested call's terminal commit adds its result;
executeTool() reads it by key. No commit rewrites another call's document.
TaskRuntime.ownedTasks(context) returns copies of the live tasks a task owns directly, whichever invocation
created them, from the scheduler's index. A tool call settling aborts the live nested calls among them, which
the removed index used to list. A task memo marks a tool that made a nested call in any attempt, so settling a
leaf still skips scanning pi.live.
8000 parallel nested calls: 9.2 s -> 2.7 s, linear (bench:scheduler-fanout).
Node 24/26 --watch posts watch:import/watch:require messages on the worker channel (nodejs/node#65044), which failed the run as a broken bridge. closes #10725
Session, Harness, and runtime reads such as snapshot(), documentState(), watchDoc(), and getTask() may queue on the mutation line a commit callback holds; on a cache miss they never resolve. Documented on Session.commit, TaskRuntime.commit, and in the spec's pitfalls.
…s() with a held child
The scheduler kept every conversation edge it saw and, for conversation owners, chain loads, and owners of known tasks, the ended tasks above them, for the life of the process. Now an owner edge or an ended task's ownership is kept only while something is listed below it in #below: only the chains above live work are known, whole. Unneeded nodes are queued and swept at the end of each publication, or by a sweep job on the Session line after operations that publish nothing (rejected commits, reads), never during an operation, so a commit that creates work below a chain its own callback loaded keeps it. Later work in an old conversation loads its chain from storage again. Removes #conversationOwners, #pinned, #knownChildren, and #forget. A queued input triggers the withdrawal scan only while some task has cancellation intent, and chains are loaded only for queued inputs. 10,000 ended subagent cycles under a live parent: 10,000 settled tasks and 10,001 edges before, 0 and 1 now (bench:scheduler-memory); a later message into an old subagent conversation reads 7 records instead of 5.
…entionMs) The Session kept every document it loaded or created in memory for the life of the process: every conversation and session document ever touched, built-in and third-party. Now a cached document that no watch or document state observes leaves the cache once it was not read or written for `documentRetentionMs` (HarnessSettings and createSession options; default ten minutes, 0 drops it right after use, Infinity keeps documents; anything that is not a number of milliseconds means the default), and loads from Storage again on its next access. An observer pins the incarnation it observes and releases it when that incarnation retires. Eviction runs on the mutation line, after every commit outcome (published, no-write, rejected, failed settlement) and from one unreferenced timer for the next expiry, so a transaction never loses a tracker it holds. Where timers cannot be unreferenced only commits evict. Cache bookkeeping is contained: a throwing clock or setting never changes an operation's outcome. A hot document moves in the eviction order at most once per granule (a tenth of the retention, up to 1 s), keeping the fan-out bench at HEAD's speed. `npm test` runs the durable suite a second time with every unattached document evicted right after use. 5,000 conversations kept 26,000 documents and about 3.8 KB each; with a 50 ms retention, after waiting, none (bench:document-cache).
createNodePhotonImages() now runs Photon in a worker thread, so preparing a large photo (a second or two) no longer blocks the event loop: in the test a 6-megapixel image blocked the loop over 200 ms inline and under 100 ms with the worker. The worker keeps the process alive only while it prepares an image, exits after 30 s without images to free Photon's WebAssembly memory, and a worker that cannot start falls back to the calling thread. Options: { worker: false, idleMs }.
…tes appended at once
- Input submissions take entry: { kind, data } (InputEntry): placed as that
kind with that data and the content as user message, instead of pi.user.
Kinds must be non-empty strings outside pi.*. Queued inputs carry it in
their pi.inbox item.
- Writes with only kind and data (no model, head, edits) are appended and
settled done at once, busy or not; only a queued reset holds them back.
- Experimental hosts render such inputs as user messages and title by them.
closes #10407
* feat(coding-agent): use pi.dev configuration schemas * refactor(coding-agent): separate schema URL from output path * docs(coding-agent): document published schema URLs
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.
…ction failures visible A generation waiting on a blocking threshold compaction sent its request whatever the outcome. When summarization failed, the context was still above the blocking threshold: a session at 996K of a 1M window got an empty length answer on every turn and nothing showed the error. The generation now fails with model_error (Compaction failed: <error>) when its compaction failed, faulted, was orphaned, or was aborted directly; a compaction that found nothing to compact or was declined still lets the request go ahead. pi.live.compactionFailure keeps the newest failure (task, reason, error) until a compaction produces a summary. The agent events' snapshot carries it, and compaction_end carries error for a failed compaction.
The daemon gets pty, ptyWrite and ptyResize: raw terminal output as events, input in arrival order, SIGWINCH on resize, and the process group killed on cancel or a lost connection. openTerminal() is the client, localDaemon() the daemon for this machine. Unix only; Windows answers ENOTSUP.
The process group in front of the pty when it is not the program's own (a job its shell runs), with the leader's name on macOS and Linux. Exposed as RemoteTerminal.foreground().
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
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
…used overlay Wheel events that no component handles now scroll the transcript instead of reaching the focused overlay's handleInput as raw input. Overlays that scroll with the wheel must implement handleMouse. fixes #10769
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
… refresh A tool call that was aborted or timed out while waiting for a shared OAuth refresh was still retried by StreamableHttpTransport once the refresh finished, so the server executed a call the caller had already given up on. McpClient now passes a per-request signal to McpTransport.send(), aborted once the request settles (cancelled, timed out, answered, or closed). StreamableHttpTransport checks it before each dispatch. The shared refresh itself is not aborted, so other requests waiting on it still succeed. fixes #10598
Pi Mobile (apps/mobile in pi-private) is replaced by p2 (apps/p2). The Android APK workflow and its pre-commit check go; the hook runs p2's own check when apps/p2 exists (only in pi-private).
…response 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
…lock 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
Retry known strict tool capacity rejections once with preferred tools using their original non-strict schemas. Preserve required constraints and payload hooks, and never retry a started stream. closes #10409
An environment may list the files, directories and symbolic links below a directory: relative / paths, depth first in name order, without .git and what git ignores in work trees, bounded with a truncated flag. Callers fall back to their own walk when it is missing or fails with not_supported. The env conformance suite checks it with the new listFiles option.
Lists a directory tree like fd --hidden --exclude .git without following symbolic links, with the ignore crate of fd and ripgrep for git's ignore rules. Records travel as payload (kind byte, relative / path, NUL); the listing stops at maxEntries, maxBytes or timeoutMs with truncated, and a cancel stops it between entries. hello reports features: ["listFiles"] so clients can tell older daemons apart.
RemoteExecutionEnv.listFiles resolves the path and lists through the daemon; a daemon without the listFiles feature yields not_supported. LocalExecutionEnv is NodeExecutionEnv plus listFiles through a daemon on the same machine, so local and SSH sessions list with the same code. nodePlatform maps hello's Rust names to Node's (windows -> win32, macos -> darwin, x86_64 -> x64, aarch64 -> arm64).
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
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
Pi-Bolt's changes kept where both changed the same code: key IDs parsed once per key press (with 1.2.0's rules for shifted symbols such as ctrl++), OSC 133 prefixes stripped only from lines that have one, models.json checked against the generated plain schema (now from 1.2.0's definitions) and the theme schema built on first use and checked with Value, so neither is built nor compiled at start; piBolt.packages added to the settings schema.
The model catalog fetched again with Pi 1.2.0's generator, and the published settings schema with piBolt.packages.
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 53075c1297
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| echo "Checking apps/p2..." | ||
| npm --prefix apps/p2 run check | ||
| if [ $? -ne 0 ]; then | ||
| echo "❌ apps/p2 checks failed." |
There was a problem hiding this comment.
Remove the emoji from the hook message
When the apps/p2 check fails, this newly added output includes an emoji even though the repository rules prohibit emojis in code. Replace it with plain text so the hook conforms to the documented convention.
AGENTS.md reference: AGENTS.md:L3-L6
Useful? React with 👍 / 👎.
Pi-Bolt 0.8.6 on Pi 1.2.0. Release notes:
docs/releases/0.8.6.md.models.jsonchecked against the generated plain schema, now generated from 1.2.0's definitions, and the theme schema built on first use, so neither is built nor compiled at start;piBolt.packagesin the settings schema.e0f307e3d3f66944): the release reuses 0.8.5's runtimes.Checked:
npm run check,./test.sh, tests/aot 61/61 and tests/pi on macOS, tests/pi, e2e tools/screen, ui_check and stress (completions, anthropic, responses) on Linux. Against 0.8.5: startup, one prompt and interactive within 1–3% on Linux and within 1% of instructions on macOS.