Edit-tool results carried the full pre/post file content in
`details.oldText` / `details.newText`. For large files this bloated each
per-turn JSONL line by hundreds of KB even though the snapshots are
never sent to the LLM (provider serializers send only `content`) and
only consumed by the ACP event mapper for diff visualization.
Add `pruneOversizedEditSnapshots` and apply it at every site that
constructs an `EditToolDetails` / `EditToolPerFileResult`:
`executePatchSingle`, `executeReplaceSingle`, hashline `renderSection`
(delete + update branches), and both aggregators in `edit/index.ts`.
When combined `oldText` + `newText` exceeds 32 KB the helper returns a
shallow copy with both fields omitted; smaller edits pass through
unchanged. The diff, path, firstChangedLine, op, move, and diagnostics
fields are preserved, and ACP returns no diff content for over-budget
files (the text content still flows — graceful degradation).
Fixes#3786
- Enhanced the edit renderer to support visual tracking of delete and move/rename operations.
- Updated diff computation to correctly handle file-level changes and suppress erroneous "No changes" warnings.
- Improved terminal output with a clearer activity indicator and accurate state representation during multi-file operations.
- Extended the rendering pipeline to display source-to-destination paths for file renames and added validation tests for edit workflows.
- Migrated all wire protocol, schema definitions, and tools validation from Zod to ArkType across multiple packages.
- Updated extension runtimes, custom tools loader, and TypeBox compatibility shim to expose and use ArkType instances.
- Added a comprehensive ArkType migration guide, validation parity tests, and helper utilities.
- Removed redundant PDF asset routing and parsing implementations from the read tool.
LspFileSystem.write received the already-resolved absolute path, so
isInternalUrlPath could never see the original scheme ('/Users/theo/.omp/agent/sessions/-Projects-oh-my-pi/2026-06-10T09-11-41-506Z_019eb0cd-3ec2-7000-92aa-1b82aa4d78f0/local/,' vault://)
and the bridge guard only caught the active plan file via the secondary
absolutePath comparison.
Fix: store the original user-provided path in LspFileSystem as
requestedPath and pass it as the first arg to routeWriteThroughBridge.
This matches replace.ts and hashline/filesystem.ts which already pass
the unresolved path correctly.
Also remove the ReturnType<typeof spyOn> annotation from makeBridge()
per the AGENTS.md "NEVER use ReturnType<>" rule.
Addresses all three review concerns from @roboomp and @chatgpt-codex-connector:
1. **Contract regression fixed**: guard
(extracted from ) is now called by all four write sites.
Internal-URL paths (e.g. ) and the active plan file
in plan mode are never routed to the editor bridge.
2. **Bridge call hygiene**: bridge calls are now wrapped in ,
is called, and
is bumped — matching the contract already had.
3. **Tests**: adds 6 parity tests
(2 per write site) mirroring :
- routes plain workspace writes through the bridge, skips writethrough
- writes local plan artifacts to disk instead of the ACP bridge
**Shared module**: exports
and (guard +
bridge call + ToolError wrap + invalidateFsScanAfterWrite +
bumpFileMutationVersion). All four write sites call it; the duplicated
6-line pattern is gone. is simplified to use the same helper
instead of its private method.
When Zed (or another ACP client) advertises the fs.writeTextFile
capability, the write tool already routes through it so the editor's
open buffer is updated immediately. The edit, patch, and replace modes
were missing this path — they always wrote directly to disk via the LSP
writethrough, leaving open buffers stale and requiring a workspace
reload before Zed's TypeScript diagnostics panel would reflect the
changes.
All three modes now check for an ACP bridge with writeTextFile support
and route through it when available, falling back to the existing LSP
writethrough path otherwise.
non-exact patch matches warn and prefix/substring matches must preserve the discarded suffix; multi-entry edits stop at first failure and report applied vs not; ast-edit and file-mention snapshots use canonical realpath keys and re-record post-apply; notebook marker-shaped lines escaped on render; fuzzy matcher pre-normalizes once per seek; streaming preview caches text+tree per tick.
- Added tree-sitter `enclosing_block_boundaries` API with line range models.
- Added N-API `enclosingBlockBoundaries` bridge and exported JS declarations.
- Replaced matching-bracket context resolution with source-aware block context in read and diff flows.
- Passed source path through diff/read generators to surface native block boundary previews.
- browser tool's existing-tab re-nav defaults to waitUntil: 'load' (matching
new-tab path); identical acquireTab() calls no longer hang on dev servers
- patch tool error path uses caller-supplied relative path; absolute
resolvedPath stays in structured context only ($HOME no longer leaks to TUI)
- splitInternalUrlSel keeps mcp:// resource URIs opaque even when they end in
':raw' or '/:1-50' (McpProtocolHandler matches by verbatim URI)
- find tool: timeout signal honored by onMatch; partial results sorted by
mtime desc; backslash-escaped commas skipped in path-list validation
- DAP throwPreferredDapStartError waits up to 50ms for the underlying
launch/attach error instead of one microtask
- debug tool surfaces 'python missing' and 'pip install debugpy' diagnostics
separately when adapter: 'debugpy' is requested
The size+mtime check from 094273df5 is unreliable on filesystems with
coarse mtime resolution: a same-length rewrite within the same tick
(e.g. "a" → "b") leaves both fields unchanged and falsely trips the
"file content did not change on disk" guard. CI on ubuntu Bun 1.3.14
hit this in the create-then-update aggregation test.
Re-read the file post-write and compare bytes to the previous content
instead — deterministic regardless of FS timestamp granularity.
executePatchSingle returned success based on the writethrough callback
resolving, but the LSP-backed writethrough could resolve to an in-memory
editor buffer while disk stayed unchanged. Capture pre-write mtime+size,
re-stat post-write, and throw a ToolError when nothing changed.
- Replaced all StringEnum(...) usages with z.enum([...]) across tools, examples, and tests.
- Removed StringEnum re-export from @oh-my-pi/pi-coding-agent public API.
- Condensed verbose tool parameter descriptions to minimal lowercase phrases.
- Renamed AuthCredentialStore to SqliteAuthCredentialStore at usage sites.
- Added canonical `pi.zod` schema API exports and removed TypeBox package exports/imports.
- Migrated Tool schema typing from TypeBox to shared `TSchema`/Zod flow with legacy TypeBox compatibility.
- Updated AI provider adapters and MCP/agent builders to convert tool params through `toolWireSchema()`.
- Reworked schema validation from AJV to Zod-safe parsing with `fromTypeBox`, `toolWireSchema`, and meta schema checks.
Addresses the codex review comments on #1015 plus a sweep of adjacent
ACP conformance gaps surfaced while wiring them up.
Tool call + diff metadata
- acp-event-mapper: thread session cwd through and resolve every
`ToolCallLocation` (initial args, in-flight updates, result details)
to absolute paths against it; ACP requires absolute paths for
client-side file mapping.
- edit/modes/patch: emit the destination path for moves in the diff
result so post-edit "open file" actions land on the new file.
Permissions
- agent-session: pass cwd into `extractPermissionLocations` and resolve
raw `path`/`file`/etc. fields against it before sending
`session/request_permission`.
- agent-session: gate the permission wrapper on
`bridge.capabilities.requestPermission && bridge.requestPermission`,
matching the read/write/bash capability+method pattern.
acp-agent
- `authenticate`: validate `methodId` against the methods advertised by
`initialize` and reject anything else, so malformed clients fail fast.
- `setSessionConfigOption(MODE_CONFIG_ID)`: also emit
`current_mode_update` so clients tracking `modes.currentModeId` see
the same transition `session/set_mode` would produce.
- Pass `runtime.notifyConfigChanged` to builtins; emit
`available_commands_update` from a shared `reloadPlugins` helper
reused by `/reload-plugins`, `/marketplace`, and `/plugins`.
- prompt resource handling: route `resource` content with `image/*`
MIME into the `images` array instead of dropping it as an opaque
blob; non-image blobs still fall back to the URI placeholder.
- pass session cwd to the event mapper.
Builtins
- model: call `runtime.notifyConfigChanged()` after a successful
`setModel` so the ACP config selector reflects the new model
immediately.
- mcp: redact query strings and userinfo from MCP server URLs before
emitting them in `/mcp list` (prevents leaking `?exaApiKey=…` style
secrets); wire `manager.setAuthStorage(...)` before `prepareConfig`
in `/mcp test|resources|prompts` so OAuth servers can refresh tokens.
- ssh: reject non-integer `--port` values via a `^\d+$` guard instead
of silently coercing through `Number.parseInt`; list project hosts
first and dedupe user-scope duplicates to match capability-loader
precedence.
- export: reject clipboard aliases (`--copy`, `clipboard`, `copy`)
before passing them to `exportToHtml` as a filename.
- compact / force / move / browser: surface underlying failures via
`usage(errorMessage(...))` instead of letting them crash the command.
- session save|delete: route through the active SessionManager so the
persist writer is consulted and stale storage references are removed.
- marketplace / plugins / reload-plugins: call `runtime.reloadPlugins()`
on install/uninstall/upgrade and enable/disable so slash command
registries and command lists refresh consistently.
- shared.usage: make async and `await runtime.output(...)` so
`sessionUpdate` text is never dropped or reordered.
- types: document the new `reloadPlugins` and `notifyConfigChanged`
runtime hooks.
bash tool
- Use a shared `fireKill()` from the abort listener so `session/cancel`
terminates the remote command immediately instead of waiting for the
next `currentOutput()` round trip.
- Race `currentOutput()` against the abort signal so a stuck
`terminal/output` RPC cannot delay cancellation.
- Kill the terminal before reading final output on timeout so a slow
output read cannot let a timed-out command keep running past the
enforced timeout.
Tests
- acp-agent.test: extend the existing config-option assertions to
verify both `model` and `thinking_level` changes emit
`config_option_update` notifications scoped to the right session.
- acp-builtins.test: cover `/model` emitting both
`notifyTitleChanged` and `notifyConfigChanged`; lock in the parsed
`mcp add` / `ssh add` call shapes so future arg-parser regressions
fail the test instead of silently writing different configs; add a
`reloadPlugins` stub plus a typed `notifyConfigChanged` slot to the
shared test runtime factory.
- acp-stdout-hygiene.test: drain stderr in parallel and assert no
JSON-RPC frame leaks onto it; terminate the spawned process so the
stderr pump resolves deterministically.
CHANGELOG: itemize the above under `[Unreleased] > Fixed`.
CI
- bun run check: clean (TS + Rust)
- bun run test: 4128 pass / 689 skip / 0 fail (TS); 252 pass / 0 fail
(Rust nextest)
- bun run ci:test:smoke: --version / --help / `stats --help` all OK
- EditTool now carries oldText/newText in per-file results for patch, replace, and multi-file aggregation paths
- Renderer updated to display diff content for all edit modes
- Enables downstream consumers (ACP event mapper, TUI) to render accurate diffs
- Added `.ipynb` detection and editable-cell conversion utilities, including merge/serialize helpers in `edit/notebook`.
- Rerouted hashline, patch, and replace edit flows, plus read/write paths, through notebook-aware helpers before persistence.
- Removed the dedicated `notebook` tool, its schema flags, renderer, and built-in registration/settings checks.
- Updated notebook read behavior, docs, and tests so `.ipynb` reads return editable `# %%` cells and edits reserialize to JSON.
- Required top-level `path` in edit requests, removed per-entry `path` fields, and updated docs/tests to match.
- Updated patch/hashline/atom streaming preview generation to return a single request-level path diff preview.
- Changed edit execution to route patch/replace/atom/hashline through single-path handlers sharing `path`.
- Added `scripts/analyze-edit-formats` Go CLI with reports to audit edit-tool usage from session JSONL logs.
- Added raw read output propagation so read/archive commands bypass anchors, line numbers, and chunk formatting.
- Fixed atom/hashline editing by tightening hashline prefixes and applying grouped anchor edits in stable order.
- Hardened chunk parsing and path handling by using `bigram_end` checks and resolving edit paths via shared `args.path` fallback.
- Updated path-related behavior for chunk, replace, patch, and hashline previews to honor optional edit paths.
- Removed mode-level param validators and replaced them with runtime handling, then removed obsolete validation tests.
- Added `atom` edit mode to `EditTool` with single-point hashline-style dispatch and `executeAtomSingle`.
- Added `AtomToolEdit`/`AtomParams` schema and `atom` prompt docs to require one op per edit entry.
- Changed `EditTool` entry processing to inherit per-entry `path` from a top-level request `path` fallback.
- Added top-level `path` defaults to patch and replace schemas and now reject missing entry paths.
- Added an `atom` streaming strategy and registered it in `EDIT_MODE_STRATEGIES` for live mode support.
- Enabled hashline read anchors for `atom` mode via `file-display-mode.ts` path-display logic.
- Added `atom` unit tests covering schema checks, anchor conflicts, and hash mismatch diagnostics.
- Added multi-file edit payload support by requiring per-entry `edits` arrays across patch, hashline, replace, and chunk modes.
- Changed edit schemas and validators to move `path` (and `sel`->`path`) and op data into each edit entry.
- Changed edit execution flow to parse `file:selector` paths, run single-entry executors per file, and return grouped per-file results.
- Updated chunk diff rendering and test expectations to emit consistent anchor gutters with spaced pipes and `^` head-line markers.
- Added `onAssistantMessageEvent` callback to Agent API for inspecting and aborting assistant streaming events.
- Added `setAssistantMessageEventInterceptor()` method to dynamically update assistant message event handlers.
- Converted `checkAutoGeneratedFileContent()` from async to synchronous for improved streaming edit abort detection performance.
- Implemented LRU caching in auto-generated file detection with early path-based checks to prevent unnecessary edits.
- Refactored streaming edit pre-caching to use assistant message event interception for real-time abort capability.
- Extracted `peekFile()` utility for efficient file prefix reading with pooled buffer reuse strategy.
- Reorganized edit tool from `patch/` to `edit/` directory with dedicated mode subdirectories (chunk, patch, hashline, replace).
- Replaced line-scoped edit operations with substring-based `find` parameter and added `replace_body` operation for preserving signatures.
- Added chunk focus modes (Expanded, Collapsed, Container) and focused rendering to display only touched chunks and adjacent siblings.
- Implemented notebook (ipynb) language support with virtual source conversion and cell-based chunk parsing.
- Enhanced chunk edit error messages with consistent checksum mismatch reporting and improved chunk selector auto-resolution.
- Extracted edit mode implementations into separate modules with improved helper functions and LSP integration for diagnostics.