Repository navigation
Conversation
models.dev deprecated several Baseten, GitHub Copilot, and Together models, which broke type checks and the default-model test. - Default GitHub Copilot model: gpt-5.4 -> gpt-6.1-sol - Move Copilot live tests to gpt-6-luna/gpt-6-sol - Remove tests and generator entries for retired models, including the Together reasoning-only model list
Sign in with ChatGPT on a ChatGPT Business plan can fail with subscription_sharing_workplace_individual_usage_limit_exceeded. Only the workspace admin can raise this limit, so retrying never helps. - Mark the error as non-retryable - Add a hint to the error message telling the user to ask their workspace admin to increase the limit
Status codes inside request ids or other numbers no longer make an error retryable. closes earendil-works#11057
…ution into pi-ai pi-ai gets a ClientIdentity request option and createModels() default that choose the User-Agent (detailed with version, short, or custom) and provider attribution headers for OpenRouter, NVIDIA NIM, and OpenCode. Attribution is off by default in pi-ai and on by default in pi-agent-core and pi-durable (HarnessOptions.identity). pi-coding-agent sets its identity from the telemetry setting instead of adding headers itself.
- ClientAttribution is { name, url, categories? } with required name and url, so
OpenRouter always gets an HTTP-Referer. identity.ts maps the fields to each
provider's headers; NVIDIA gets the name with its first letter upper-cased.
attribution: null turns it off. PI_ATTRIBUTION is pi's, including the
cli-agent category; agent-core, durable, and the coding agent use it.
- Categories are a string array, joined for OpenRouter.
- withClientIdentity() is a typed wrapper instead of a Proxy and keeps a
MutableModels mutable. Durable no longer caches wrappers in a WeakMap.
- The Codex originator stays pi; login agentName no longer follows attribution.
- Models and the coding agent's ModelRuntime add x-opencode-session for custom
providers pointing at OpenCode, replacing the coding agent's transformHeaders.
…ite replication Portable modules under sqlite/replication: a WAL scanner that returns the newest page versions of committed transactions and stops at the last valid commit frame, incremental whole-database checksums from per-page hashes, the shared segment and base container with header validation, and object keys and start markers. A Node-only zstd page compressor sits beside them. None of it is exported from the package yet.
Every built-in storage implements ReplayableStorage: head(), replay() at an explicit sequence, and export() of the current state as a log that replays to the same reads. SQLite schema version 3 adds sequence indexes so export streams. @earendil-works/pi-durable/sync adds ReplicatingStorage, which appends each committed batch to an outbox with fork copies resolved, compactRecords(), and versioned JSON log lines.
This reverts commit 4c25119. The work continues on the replication branch.
…ch recorded its loadout Resume and /tree restored exactly the tools the branch recorded, so extension tools registered later stayed inactive. Registered extension tools that the branch never added or removed are now activated unless they use defaultActive: false. The whole branch is searched, so tools removed before a compaction stay inactive. Also fixes the /tree case from earendil-works#11075. closes earendil-works#11062
The built-in grep tool spawned rg with the inherited environment, so RIPGREP_CONFIG_PATH (e.g. a config containing --fixed-strings) silently changed tool semantics: literal: false searches became literal and the agent got incomplete results with no error. Pass --no-config so the tool contract is deterministic. Users who want custom rg behavior can use the bash tool or wrap grep in an extension. closes earendil-works#11203
…l call Pre-prompt recovery of a recoverable length stop omitted the assistant message but not the tool results that answered its calls, because only the end-of-run caller passed them. The orphaned results were persisted and providers rejected every later request. _omitRecoveryAttempt now finds the matching tool results in the projected context itself, so every caller omits the pair together. closes earendil-works#11227
Jev (TypeSafe, Workers AI), Clef (Workers AI), and OpenRouter decision models now accept ClassifierContext.images. Each service takes its own wire form: TypeSafe assets with the opt-in header, Clef images data URLs, or OpenRouter image_url parts in the state. A new SystemOneCompat (imageInput, sendImageOptInHeader) selects the form; catalog entries set it explicitly and models.json provider compat now applies to classifiers. Adds Clef Omni and Jev 1.14 on Workers AI and fixes Clef Flash and Workers AI Jev catalog pricing.
Move two post-release entries out of the 1.3.0 sections, add missing entries, duplicate user-facing ai and tui changes into coding-agent, and add New Features.
Use authoritative OpenRouter billing for Chat Completions and Anthropic Messages while retaining catalog component estimates and fallback behavior. Fixes earendil-works#9980
Bump vitest to 4.1.12 (vite 8.3.4) and the gondolin example extension to 0.14.0, which drops node-forge. Gondolin's undici moves to 6.29.1.
* feat: publish preview packages to pkg.earendil.com CI packs every public package after the build, with internal dependencies pointing at the same commit's builds. A workflow_run job publishes them to the earendil-pkg worker with the job's GitHub OIDC token, moves main or pr-<n>, and comments install commands on the PR. Closing a PR deletes its ref. pi-env is left out because its daemons are only built for releases. * feat: publish preview packages only for main and same-repo PRs pkg.earendil.com requires a consumer token, so fork authors could not install their previews anyway. Fork PRs are no longer packed or published, which keeps fork code out of the registry and removes the PR lookup: the PR number now comes from the workflow_run event. The PR comment is dropped; the job summary lists the install commands. * fix: pick the PR into main and describe only the workflow in preview-packages.yml - Read the PR number from the workflow_run pull request list with jq, selecting the PR into main; a branch can also have PRs into other branches, and the index expression tripped workflow linters. - Comments describe what the workflow does and what it needs from pkg.earendil.com, not how the service works. * fix: gate preview publishing to main pushes and open PRs into main - Publish only CI runs for pushes to main and for pull requests. A branch can make its own copy of CI run on push; those runs are unreviewed and were uploaded as push builds. - Move pr-<number> only while the PR is open into main with this commit as its head, checked live, so a late publish cannot recreate the ref of a closed PR; otherwise publish without a ref and print a notice. - Set concurrency per job, so skipped fork runs never wait in a group. * docs: drop the service rule from the preview-packages.yml header * docs: name the earendil-works/pkg worker in the preview script
A mid-conversation system message was placed before checking whether the next assistant message converts to any blocks. When it did not (empty text, or another model's reasoning-only reply), the update landed before a user message and Claude rejected the request with a 400. closes earendil-works#11091
…n Chord, explicit watch retirement Breaking changes: - Durable Storage.commit(writes, adopt, context): storages call adopt(seq) once, synchronously, when the batch is durable and before their next commit - Chord traceIn(options, fn, context): context last - WatchHandle and WatchEnd move to Chord (Durable re-exports them); end reasons ended (was session_closed) and failed (was session_failed); retirement is an explicit source event, and an ended watch drops its references into the Session - Removed Session.documentState(), Conversation.viewState(), Harness.taskGraph() and DocumentState; use watchDoc(), watch(), watchTaskGraph()
…ets, HTTP, the app kit, measurements
…il-works#11565) In regular TUI mode, user and assistant messages wrote OSC 133 (`A`/`B`/`C`) shell-integration markers straight to the terminal. Terminals that display OSC 7501 program status (Rex, iTerm2 3.7.5+) read `133;A` as a new shell prompt and drop the current status. As a result, the `working` indicator disappeared right after a run started and only `done` showed up at the end. Fullscreen mode was not affected because it already strips these markers before writing. ## Change - `TuiMainScreen.doRender()` (`packages/tui/src/tui-main-screen.ts`) now strips OSC 133 markers from the start of each rendered line before compositing and writing. It uses the same pattern as fullscreen (`OSC133_ZONE_PREFIX`). - The message components still produce the markers. Fullscreen uses them internally for prompt navigation and already strips them before writing, so its behavior is unchanged. - In regular mode, the terminal's own jump-to-prompt shortcuts no longer stop at pi messages (added in earendil-works#1805). ## Testing - New test in `packages/tui/test/tui-render.test.ts`: no `ESC]133;` reaches the terminal across a full render and a differential render, with both BEL and ST terminators. It fails without the change and passes with it. - These pass: - `packages/tui` `npm test` - `test/user-message.test.ts` and `test/assistant-message.test.ts` in coding-agent - `npm run check` - I ran `pi -ne --tui-mode regular` with `PI_PROGRAM_STATUS=1` and a streaming test provider, capturing all terminal output with `script`. - Before: `7501 working`, then repeated `133;A`/`133;B`/`133;C`, then `7501 done`. - After: only `7501 idle` → `working` → `done`, and no OSC 133. Fixes earendil-works#11519 Requested by @davidbrai Co-authored-by: triagent <triagent@localhost>
…dil-works#11566) At startup, interactive mode printed every skill diagnostic under the heading `[Skill conflicts]`. Most of these are validation warnings for skills that still load, for example `description exceeds 1024 characters (1508)` or name-format warnings. The heading made them look like name collisions. This change renames the heading to `[Skill warnings]`. Validation and loading behavior are unchanged. The prompt and theme headings are left as they are. **Testing** - Updated the assertion in `packages/coding-agent/test/interactive-mode-status.test.ts` ("still shows diagnostics on quiet startup when requested") to expect `[Skill warnings]`. It fails against the old heading and passes with the change. All 37 tests in that file pass. - `npm run check` reports six type errors in `packages/ai` tests about model IDs. Unchanged `origin/main` reports the same six errors, so they are unrelated to this change. It reports nothing in `coding-agent`. Fixes earendil-works#11521 Requested by @davidbrai Co-authored-by: triagent <triagent@localhost>
…works#11572) When a Zhipu/Z.ai (`open.bigmodel.cn`) plan hits its usage cap, the API returns HTTP 429 with error code `1308`. The cap resets after hours, but `isRetryableAssistantError()` still marked the error as retryable, because the bare `429` in the message matched the transient-error pattern. The agent-level retry then re-sent a request that could not succeed and slowed down the final error. With default settings that is 3 attempts, about 14 s. This change adds the Zhipu `"code":"1308"` error code to the non-retryable provider-limit patterns in `packages/ai/src/utils/retry.ts`, next to the existing quota and subscription-limit entries. The pattern matches the JSON code field, not a bare number, so request IDs or byte counts that contain 1308 do not trigger it. Not changed: the provider-level HTTP retry (`retry.provider.maxRetries`, off by default). It still retries every 429 based on the status code alone. Testing: - `npm run check` passes. - `test/retry.test.ts` passes. - Calling `isRetryableAssistantError` directly on the reported message, `429 {"code":"1308","message":"已达到 5 小时的使用上限。…"}`, returned `true` before the change and returns `false` after. Fixes earendil-works#11537 Requested by @davidbrai Co-authored-by: triagent <triagent@localhost>
…ed version unchanged (earendil-works#11571) ## What was wrong `pi update --extensions` first looks up the newest version of each npm package with `npm view`. If that version is newer than the installed one, pi runs `npm install <name>@latest`. The two commands read different registry documents, and each is cached separately. Shortly after a publish, `npm view` can already report the new version while `npm install` still resolves the old one. npm then exits successfully, pi prints "Updated packages", and the startup banner lists the same package again on the next launch. The user can't tell that nothing was actually updated. ## What the change does - `shouldUpdateNpmSource` now returns the installed version and the registry version along with its decision. Both are carried on `NpmUpdateTarget`. - After `npm install`, `updateNpmBatch` re-reads each package's installed version. If the registry lookup succeeded and the version did not change, it emits a `progress` event with the message: `<source> is still X (registry reports Y); npm registry metadata may be stale, try again in a few minutes`. - The package CLI now prints `progress` events in yellow. Until now nothing emitted them. Install behavior is unchanged: pi still installs `@latest`. ## Testing - New test in `test/package-manager.test.ts`: two packages are flagged for update and only one is actually upgraded. The warning is emitted only for the unchanged one. The test fails without the change. - `test/package-manager.test.ts` passes (129 tests), and `npm run check` passes. - Ran the built CLI with an `npmCommand` stub whose `view` reports `1.0.1` and whose `install` changes nothing: ``` Updating npm:demo-pkg... npm:demo-pkg is still 1.0.0 (registry reports 1.0.1); npm registry metadata may be stale, try again in a few minutes Updated packages ``` Fixes earendil-works#11536 Requested by @davidbrai Co-authored-by: triagent <triagent@localhost>
…ges (earendil-works#11577) When an image needs resizing, `resizeImageInProcess` builds a list of encodings: PNG first, then JPEG at decreasing qualities. It returned the first one under `maxBytes`. Since PNG was always first, it was used whenever it fit, even when it was many times larger than the JPEG. The doc comment says "Try both PNG and JPEG formats, pick the smaller one", and the code worked that way before v0.62.0. Because of this, photos were sent as multi-MB PNGs, which made requests large enough to fail or time out. This change compares PNG with JPEG at the configured quality at each size step and tries the smaller one first. It tries the other one next, and the lower JPEG qualities only after that. Screenshots and other images where PNG is smaller still go out as PNG. **Testing** - Added a regression test to `test/image-processing.test.ts`: a generated photo-like 1200×800 JPEG resized to 1000 px must come back as `image/jpeg`. The test fails without the fix (PNG is returned) and passes with it. - `node ../../node_modules/vitest/dist/cli.js --run test/image-processing.test.ts test/image-resize-callers.test.ts`: all pass. - `npm run check`: no issues in the changed files. It does report type errors, all in `packages/ai/test` (model ID unions), which this change does not touch. - Reproduction with a photo-like 2400×1620 JPEG (460 KB base64): - before: `image/png`, 4,008,480 bytes base64, at 2000×1350 - after: `image/jpeg`, 255,316 bytes base64, at 2000×1350 Fixes earendil-works#11551 Requested by @davidbrai Co-authored-by: triagent <triagent@localhost>
Some Apple Terminal setups send ESC+CR for Shift+Enter, which parsed as alt+enter and queued a follow-up. The native Shift fallback now covers ESC+CR as well as plain CR. fixes earendil-works#11559
…st-requiring resources A saved Do not trust decision was ignored when the project had no trust-requiring resources, so ctx.isProjectTrusted() returned true. The saved decision now takes precedence over auto-trust. closes earendil-works#10947
…ewer version exists Temporary -e npm: sources were installed once and reused forever. Like temporary git sources, check npm on load (unless offline) and reinstall with name@latest or the configured spec when a newer version exists, keeping the cached install if the refresh fails.
A temporary -e git source with a ref was treated as pinned and never refreshed, so git:host/repo@main stayed on its first checkout. Refresh refs too, skipping only a commit-hash ref that is already checked out.
…dil-works#11588) Fixes earendil-works#10488 ## Problem On Windows, `c:\Users\me` and `C:\Users\me` are the same directory, but pi compared paths as exact strings (`===`, `startsWith`, map and set keys). `realpathSync` keeps the input casing, so canonicalizing does not help. The reported case, with cwd `c:\Users\me\project` and home `C:\Users\me`: 1. Ancestor discovery finds `c:\Users\me\.agents\skills`. The exclusion of the user dir `C:\Users\me\.agents\skills` does not match, so global skills load as project skills. 2. The same dir loads again as user skills. Dedup does not match either, so every skill is reported as a name collision. 3. `hasTrustRequiringProjectResources` treats `~/.agents/skills` as a project resource that requires trust. The same pattern affected other places: - Saved trust decisions in `trust.json` were not found when cwd casing differed, so pi asked for trust again. - With a custom session dir, `--continue`, resume by id, and the session list hid sessions recorded with different cwd casing. - Git worktree `AGENTS.md` shadowing failed when cwd casing differed from git's paths. - Local package matching (`pi remove c:\pkg` vs configured `C:\pkg`) and package identity across global and project settings. - Extension preload matching, skill scope classification, resource source info, the config and session selector keys, and the session import self-check. Separately, home-to-`~` shortening used an unanchored `startsWith(home)` in four places, so `/home/me2/x` was shown as `~2/x`. ## Fix New helpers in `utils/paths.ts`, all based on one comparison key: - `getPathComparisonKey(path)`: the resolved path, lowercased on Windows. It does not touch the filesystem. - `pathsEqual(a, b)` - `isPathWithin(parent, child)`: true for equal paths or paths inside `parent`, matching whole path segments. - `shortenHomePath(path, home?)`: replaces a leading home dir with `~`. The helpers pick `path.win32` or `path.posix` from `process.platform` at call time, so tests can exercise real Windows paths (drive letters, UNC). `getResourcePathKey(path)` in `source-info.ts` is the dedup key for resources. Synthetic identifiers (`builtin:<name>`, `<inline:...>`) stay unchanged, because they are not filesystem paths and their lookup is case-sensitive. File paths go through realpath plus the comparison key. The helpers are used only for comparison; stored and displayed paths are unchanged. Trust store lookups and `set` match keys case-insensitively on Windows. Unchanged on purpose: - The package install-root guards (`resolveManagedPath`, `pruneEmptyGitParents`, the `node_modules` dependency check) keep exact string checks. Both sides come from the same root string, and exact is the stricter option. - Checks based on `path.relative()` (`getCwdRelativePath`, the changelog containment check) were already correct: Node's `path.win32.relative` ignores case. NTFS can enable per-directory case sensitivity, but it is rarely used outside WSL interop, so it is ignored (see the comment on `getPathComparisonKey`). ## Tests - `test/paths.test.ts`: Windows drive-letter, UNC and root cases, POSIX case sensitivity, segment-boundary containment, and home shortening, including root and UNC homes. - `test/source-info.test.ts`: file paths fold case on Windows; builtin and inline identifiers keep their case. - `test/trust-manager.test.ts`: the `.agents/skills` exclusion with a lowercase `HOME`, plus saved trust decisions matched and replaced across casing (simulated win32). An end-to-end test of the reported loader scenario is not feasible on Linux: the loader resolves paths with POSIX `node:path`, and the filesystem is case-sensitive. `npm run check` passes. The coding-agent suite passes, except for 13 failures that also occur without this change: clipboard tool detection in this environment, and one test that needs built `dist/` output.
…through at line end (earendil-works#11591) Fixes earendil-works#10925. ## Problem `splitIntoTokensWithAnsi()` attaches pending escape codes to the next visible character. The code that closes a styled word therefore lands on the following space: `**commodo** consequat` tokenizes as `\x1b[1mcommodo`, `\x1b[22m `, `consequat`. `wrapSingleLine()` detected whitespace with `token.trim() === ""`, which is false for `"\x1b[22m "`. When the styled word ended exactly at the wrap width, that space was treated as a word and moved to the next line: ``` before: ["aaaa \x1b[1mbold", "\x1b[1m\x1b[22m next", "word"] (width 9) after: ["aaaa \x1b[1mbold\x1b[22m", "next word"] ``` Because the closing code moved to the next line, the style also stayed open into the right padding of the wrapped line. Separately, `getLineEndReset()` closed only underline at line end, so strikethrough that spans a wrap continued into the padding. ## Fix - Detect whitespace tokens after stripping escape codes. - When a whitespace token is dropped at a wrap, append its escape codes to the wrapped line and update the tracker, so the style closes there and is not re-opened on the next line. - Close strikethrough (`\x1b[29m`) at line end, like underline. `getActiveCodes()` re-opens it on the next line. Background is unaffected. - Whitespace wider than an empty line that carries codes (e.g. background spaces followed by a trailing reset) keeps only the codes. Without this, treating it as whitespace skipped the long-token path, and the trailing reset blocked the final `trimEnd()`, leaving a line wider than `width`. - When a long word follows, the line before it is now trimmed before the line-end reset is added. Otherwise the reset (now also emitted for strikethrough) blocks the final `trimEnd()` and leaves trailing spaces. ## Tests - `wrap-ansi.test.ts`: boundary closing code, dropped space with a color reset, strikethrough reset and restore across a wrap, strikethrough reset with background preserved. - `markdown.test.ts`: padded Markdown with a background at an exact boundary for bold and strikethrough. - The new tests fail without the fix. With the old code, rendering the issue text through `UserMessageComponent` (dark theme, padding 1) at widths 40-120 produced 5 bad lines at width 78. With the fix it produces none. - Fuzzing over 1.2M random styled inputs with SGR and OSC 8 codes found no line wider than `width`, and no case where the old wrapper laid out text the same as the unstyled input but the new one does not. - Benchmark (`wrapTextWithAnsi`, width 77, about 5-18k chars, median of 6 processes) against `main`: plain -5%, light styling -0.5%, heavy styling +1%, long strikethrough +0%, all within noise. The whitespace check scans character codes and stops at the first visible text instead of allocating a stripped copy. - `npm run check` passes. All `packages/tui` tests pass. `./test.sh` shows no new failures; the remaining failures are environment-specific: the clipboard tests also fail on `main` here, and the durable, env (ssh), and chord failures are in packages that do not depend on `pi-tui`.
…s#9329) ## Summary Treat `TERM_PROGRAM=Orca` as Kitty-image capable in `pi-tui`'s terminal capability detection, so image components render inline instead of falling back to text when running inside Orca. - Recognize Orca as supporting Kitty inline images, true color, and OSC 8 hyperlinks. - Preserve the existing tmux and screen precedence guards. - Gate images on Orca's capability advertisement: require both `TERM_PROGRAM=Orca` and Orca's `ORCA_IMAGE_PROTOCOL=kitty` signal before enabling Kitty output, so existing Orca releases that do not yet advertise image support safely keep the text fallback. - Add a regression test and the package/CLI changesets. ## Related - Requested by maintainer approval in [earendil-works#7357](earendil-works#7357) (lgtm). - Depends on the Orca-side capability advertisement, which has landed: [stablyai/orca#19512](stablyai/orca#19512) (merged 2026-09-18, `09073086a8`) exports `ORCA_IMAGE_PROTOCOL=kitty` on the daemon, local, and relay PTY paths, and forwards it across the WSL boundary. Supersedes [stablyai/orca#11706](stablyai/orca#11706), which was closed in favor of #19512. ## Testing - `pnpm --filter @moonshot-ai/pi-tui test` — focused regression suite passes. - `pnpm --filter @moonshot-ai/pi-tui typecheck` — passes.
host.reload(facets, ctx): staging into a staged table with a recording self (all or
nothing), one commit (types, registries, contributions; per-name wakes; installing
holds), and each object's fate: pure swap in place, native restart after its drain,
retry on the new code, or its end by its type's removal, with a report. Old calls
finish on old handles (R15 for documents and tables), routines hand over per object,
jobs keep their versions (accepts, blocked { version }, persisted runs per R3),
registries swap providers and contributions in one batch, holds are kept,
re-subscribed or unbound with failed { waiting } (model §5), accepts maps older
consumers' state, arguments, results and keyed members.
References are the only indirection (owner, 2026-10-29): a consumer's watches and
derives attach through the reference, which passes them straight to the object
instance's own document or derive, moves them to a new instance (one frame) and keeps
its last values while unbound; MemberSource, keyed-members and the per-version
hand-over are removed. Older consumers read through the instance's shared mapping
per version. Examples 33-34, reload benchmark.
oauth.clientRegistration defaults to auto: pi's Client ID Metadata Document when the authorization server supports it for public clients, otherwise dynamic client registration. Registered clients stay in use. pi mcp add gains --oauth-client-registration. closes earendil-works#11426
A Session no longer holds its line while a commit is stored. Commits that arrive while one storage call is in flight go out together in the next call, each with its own seq and outcome; a job runs until it touches a key an unpublished commit wrote, then waits for that commit's publication. Outcomes are delivered in line order; publication is in line order, one commit per macrotask. The scheduler's decisions run as regions that record the keys they read and rerun after a wait, with side effects only after a clean check. Storage.commit(batches, receive, context) replaces commit(writes, adopt, context): prefix semantics (Durable's own checks refuse a batch and keep the ones before it; any other failure rejects the call), receipts per stored batch, optional publishing() scope. Memory, SQLite, JSONL, IndexedDB and StoreStorage implement it natively; StoreStorage through the new ChordStore.commitSeries (one store transaction per call), replacing commitInBatch. A normal run makes no storage reads: bounded record cache, conversation tails, task-document index, kept context ranges. A tool slot's running status and a generation's attempt are stored with the next commit (12 instead of 14 commits per run). One Harness with 128 conversations over storage with a 4 ms round trip: about 5 -> 440 agent runs per second. Design: packages/durable/docs/group-commit.md.
…limit (earendil-works#11605) After a rescan, the env daemon's file watcher sent every changed path in a single `change` event. When a big batch of changes made that event larger than the 16 MiB frame limit, `write_frame` rejected it. The daemon's only output writer then shut down. The daemon process kept running but sent no more replies or pings. Requests hung until the client's 30 s silence limit dropped the connection and reconnected. Now the watcher builds the event with `change_event`. If the serialized path list would not fit in a frame (`MAX_PAYLOAD`), it sends the existing `{ kind: "change", overflow: true }` event in its place. The client already maps that to an `overflow` file change, so the wire format does not change and the connection keeps working. Testing: - Added Rust unit tests in `watch.rs`. One checks that a small path list is sent as `paths`. The other checks that a list larger than `MAX_PAYLOAD` becomes `overflow`. - `cargo fmt --check`, `cargo clippy --locked --all-targets -- -D warnings` and `cargo test --locked` pass in `packages/env/daemon`. - The `packages/env` vitest suite passes. - Reproduced with the real daemon: I watched a directory and renamed in a directory of 6,000 files with long paths, about 20 MB of path JSON. Before the change, `fileInfo` requests timed out from the flush until the client reconnected about 30 s later. After the change, the watcher reports `overflow` right away and `fileInfo` keeps working. With 3,000 files, which fits in a frame, the paths are still reported as before. Fixes earendil-works#10516 Requested by @badlogic Co-authored-by: triagent <triagent@localhost>
…ndil-works#11607) `sanitizeOutput()` in `@earendil-works/chord/output` deleted the ESC byte (0x1b) along with the other control characters, but it left the rest of each escape sequence in place. Colored tool output such as `\x1b[31mERROR\x1b[0m` came back from `OutputBuffer.snapshot()` as `[31mERROR[0m`. Because the ESC byte was already gone, later ANSI stripping could no longer recognize the leftover fragments. `sanitizeOutput()` now removes whole 7-bit escape sequences before it removes single control characters. It handles: - CSI sequences (colors, cursor and erase commands) - string sequences (OSC, DCS, APC, PM, SOS) terminated by BEL or `ESC \` - short `ESC x` forms such as `ESC ( B` or `ESC 7` `snapshot()` sanitizes the concatenated retained text, so a sequence split across pushed chunks is also removed. **Testing** - Added a regression test in `packages/chord/test/output.test.ts`. It covers SGR colors, cursor and erase CSI, charset and short escapes, OSC 8 hyperlinks and an OSC title, plus an `OutputBuffer` fed a color sequence split across three chunks. The test fails without the change and passes with it. - `test/output.test.ts` and `test/output-skip.test.ts` pass. - Biome is clean. The only `tsc` errors in `npm run check` are in `packages/ai` tests and are unrelated to this change. - The durable package test results are the same with and without the change: 71 test files fail on both, and every individual test that runs passes. - Reproduction from the issue: before the change both lines printed `"[31mERROR[0m\n"`; after it, both print `"ERROR\n"`. Fixes earendil-works#11604 Requested by @badlogic Co-authored-by: triagent <triagent@localhost>
…endil-works#11606) In pi-durable, a Code Mode script that called the built-in `read` on an image got `""` back, so `image(await tools.read({ path: "pixel.png" }))` threw a TypeError. `read` declared no `structuredOutputSchema`. Code Mode resolves a schema-less tool with only its text, and drops images on purpose. `read`'s output for an image is a single image block with no text, so the script got an empty string. The coding agent fixed the same problem for earendil-works#10251. With this change, `read` declares a `structuredOutputSchema`: a string, or an image block `{ type: "image", data, mimeType }`. It returns the matching `structuredOutput`: - for a text file, its text, so scripts get the same value as before; - for an image, its `ImageContent`, which `image()` accepts. The "Read image file" note stays an info diagnostic, as before. The README section on structured output and the changelog are updated. **Testing** - New test in `test/codemode.test.ts`. A script reads a real PNG and a text file through `tools.read()`, checks the block's type, MIME type, and data, and passes the block to `image()`. It checks that the script succeeds, the image is saved and shown, and the text read still returns its text. The test fails without the change: the script errors because `image()` gets `""`. - `test/tools-read-differential.test.ts`: the reference implementation now includes the new `structuredOutput` field. - `npm run check`. - `test/codemode.test.ts`, `codemode-describe`, `tools`, `tools-images`, `tools-read-differential`, `harness-tools`, `harness-hook-reads`, `harness-read-only`, and `spec-usage` all pass. Fixes earendil-works#11015 Requested by @badlogic Co-authored-by: triagent <triagent@localhost>
Report unknown options as typed and suggest a configured server for a mistyped name.
The owner's audit decisions (decisions.md, "Simplification audit"): cut the reload-only installing state (staging is synchronous) and the static registry cycle edges (the runtime guard stays); retry propagation by a visited set and a hop budget (retry.hops) instead of operation ids and through lists; a pure swap sends holds a fresh bound; one rule after a live failure (every member followed until the admitted commits published); parked_long for any long park, naming the first blocker in declaration order with its state; one seal token per attempt. Keys follow the old instance during a restart's drain like members. src/next: about 255 code lines fewer.
Port the adapter to Storage.commit(batches, receive, context). A group of batches goes to a new commit_batches function in one round trip: it locks the session row and checks the owner once, then runs commit_batch per batch in a subtransaction, so a refusal by one of the adapter's checks keeps the batches before it and ends the call with the refusal, while any other error rolls the call back and rejects it. A batch with a document.copy starts a new call once the batches before it are durable, since the copy's source is read while preparing; every other group is one round trip. Schema version 3 also admits the sleeping task status added in 1.4.0. A round-trip test pins one query for a group of plain batches and three for a group whose last batch copies a document the earlier ones wrote.
…rite one statement per table
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Rebases the Postgres storage adapter onto upstream
main(which now carries pi-durable's group commit,e5c6182) and ports it to the batchedStorage.commit(batches, receive, context)contract.commit_batchesplpgsql function locks the session row and checks the owner once, then runs the existingcommit_batchper batch inside a subtransaction. A refusal by one of the adapter's checks (SQLSTATEDU003toDU009) keeps the batches before it and returns the refusal; any other error, ownership included, raises and rolls the call back.document.copystarts a new call once the batches before it are durable, because the copy's source is materialized while preparing. Everything else is one call.sleeping, added upstream in 1.4.0.kernel/pimainwas fast-forwarded to upstreammain(50af8c1); the base branchhypeship/durable-postgres-storagestarts there.Tests
PI_DURABLE_POSTGRES_URL=... vitest --run test/postgres-storage.test.ts: 72 tests pass against Postgres 17, including upstream's new conformance cases (prefix kept before a refusal, a malformed later batch, a batched copy against the earlier batches' state, receipts before resolution) on both the plain and the reopen-per-commit storages, plus a new round-trip test: one query for three plain batches, three for a group whose last batch copies a document the first two wrote.Root
tsc --noEmitand biome are clean.