- Removed copy and delete operations across tokenizer, parser, grammar, and clipboard logic.
- Standardized line-editing operations and block resolvers to use cut exclusively.
- Updated documentation, prompts, and test suites to reflect the removal of copy and delete syntax.
- Implemented clipboard register management, parsing, and execution rules for CUT, COPY, and PASTE operations in the hashline engine.
- Added session-persistent clipboard state and integration across agent session execution, diff previews, and streaming tools.
- Added comprehensive validation, error messages, recovery handling, and test coverage for clipboard and block operations.
Download-mode resource reads created and overwrote workspace files
without running a registry tool - the same hole the native `delete`
frame had - so a session that withheld `write`/`edit`, or whose `write`
tier is `deny`/`always-ask`, still had files written. Both frames now
share one grant and one policy check, and the download refuses before
the read so a blocked call never fetches the resource.
`allowNativeDelete` is renamed `allowDirectFileMutation`: it now gates
more than deletion. The primary session derives it from the registry
BEFORE its own rewriting (Cursor moves `edit` out of the tool map and
`write` may be auto-registered later, so reading the map at bridge
construction would misjudge both) and unconditionally, since the bridge
is installed for every session and one that starts on another provider
can switch to Cursor later.
`pi_grep` with a match cap: the local tool windows to 20 files and
suggests `skip`, which `PiGrepExecArgs` cannot express - 100 matches
requested over 25 one-match files returned 20, with the cap reported
unreached. A capped search now reads cap+1 files, so a result landing
exactly on the cap is distinguishable from a clipped one, and
`match_limit_reached` is truthful either way.
`read_mcp_resource` synthesized no transcript block and paired no
result, so a read - including a download that mutates the workspace -
was invisible in the UI and stripped from every rebuilt history. It now
synthesizes a `read_mcp_resource` block (not `read`: the name drives
rendering and prune semantics) and pairs success, not-found and error.
(cherry picked from commit 5ff27a3efe8bec522d9d5dbd7763055eb03eae3b)
Hard link escape: a hardlink inside the workspace is a regular file
that passes containment AND `O_NOFOLLOW` while sharing its inode with
a file anywhere else, so truncating it clobbers that file. Proven
before the fix. The open now drops `O_TRUNC`, checks `nlink`/regular
on the OPEN handle, and truncates only after - the pattern
`autolearn/managed-skills.ts` already uses. `O_NOFOLLOW` covers the
final component only; the parent-swap window is documented, not
claimed shut.
MCP resource discovery: `getServerResources` is async and awaits
`ensureServerResources`, so a frame arriving while a server's catalog
still loads no longer reads the empty cache and reports "advertises
nothing" - a lie the model cannot distinguish from the truth.
Mixed-content reads: the mime type came from `contents[0]` while the
payload came from whichever item supplied it, so an image blob
followed by a text note sent the text as `image/png`.
Ranged `pi_read`: a plain `:N+K` selector pads one leading and three
trailing context lines, so offset 5/limit 20 handed Cursor lines 4-27.
Ranged reads compose `:raw:N+K`, verified against a real `ReadTool`.
The wire result is an opaque string, so the gutter `raw` drops is not
part of the contract.
(cherry picked from commit 679785aa6b3243ea39b26abf4dda9435960019b1)
A lexical containment check is not containment. `out/config` is
relative and `..`-free, so it passed - while a `ws/out -> /elsewhere`
link inside the workspace sent the write straight out. Proven before
the fix: the download landed in the link's target.
Containment now realpaths three things: the target when it exists, the
immediate link destination when it is a dangling symlink (a write still
follows it), and otherwise the deepest existing ancestor with the
not-yet-created segments re-applied. Each branch has a regression, and
all three fail the suite when individually reverted.
Also moves this branch's ai/catalog changelog entries back under
[Unreleased]; two commits had re-landed them inside the released
[17.1.5] section, which left `packages/ai/CHANGELOG.md` with two.
All three released sections are now byte-identical to upstream/main.
(cherry picked from commit e3ed4035ab8a1781c49a68c1fbe92c88bec3aa25)
`download_path` is workspace-relative by contract, but it arrives from
the server and `resolveToCwd` deliberately honors absolute paths, `~`,
and `..` - correct for a path a user typed, a write-anywhere primitive
for one a remote peer supplied. `/etc/cron.d/x` or `../../escape` would
have been written wherever the process can reach.
`confineToWorkspace` accepts only a non-empty relative path resolving
under the live cwd, and the download refuses anything else. The refusal
throws inside the dispatch's existing try, so it reaches the model as a
`ReadMcpResourceError` rather than a silent success or a crash.
(cherry picked from commit 963cfee21a56576033ec115db745bb18ab0a8d06)
`pi_grep` carries a context width and a total match cap. Neither is
expressible in the model-facing `grep` schema — context comes from
`grep.contextBefore`/`grep.contextAfter`, fixed when the shared tool is
constructed — so both were dropped.
`GrepTool` now takes them as constructor options. The model-facing
schema is unchanged: this is a seam for wire bridges whose protocol
supplies the values, mirroring `GlobTool`'s existing options bag. The
bridge builds a per-call `grep` only for frames that supply them;
everything else keeps the shared instance and session defaults.
`pi_ls`'s `limit` stays unmapped, now deliberately and documented. It
caps directory entries, while the local `read` renders a depth-2 tree
and slices rendered lines — nested rows, headers and elision summaries
all count — so `:1+K` would cap a different unit while looking honored.
Verified against real files in a temp dir, not captured arguments:
match counts and context lines are asserted from actual search output.
Mutation-checked — ignoring either option, or dropping the scoped tool
in the bridge, fails a test.
(cherry picked from commit 299ded5a274427c2c2d5de27c00a2056a709581c)
Catalog summaries of mounted xd:// devices are inlined verbatim into the
system prompt. External devices (MCP servers, plugins) supply that text, and
it was bounded only by character count: a summary of multi-byte script passed
roughly three times the intended budget, and control characters survived into
the prompt where they can forge structure.
Summaries now go through a single sanitize-and-bound step that strips C0/C1
control characters and bounds the result in UTF-8 bytes via the central
truncateHeadBytes helper, so a cut lands on a code point boundary and never
renders a partial code point. The built-in/external distinction is derived
once per entry, and that same boolean both selects the description cap and is
exposed as `dynamic`, so the cap and the flag cannot disagree. The prompt uses
the flag to state that dynamic summaries are untrusted metadata, and the mount
notice says the same for newly appeared devices.
(cherry picked from commit 5989da6235d820bc687779a791e655e6f1b2df0f)
Root cause of the reported "edit tool silently reformats the whole
file" corruption: fs/write_text_file has no verbatim guarantee. When
an ACP client (e.g. Zed with format_on_save: on) reformats a buffer
on save, routeWriteThroughBridge reported the pre-write content as
successfully written, and Patcher.commit keyed the returned snapshot
tag on that same pre-write text instead of what actually landed on
disk. The next edit anchored on that tag then resolved hunks against
a baseline the file had already drifted away from, which is what
produced whole-file "corruption" from single-line hunks -- reproduced
live in this session against real Swift/JSON/TypeScript files with
Zed as the ACP client.
- routeWriteThroughBridge reads the file back after the bridge write
and returns the verified content plus a drift flag (best-effort:
ACP defines no ordering between the client acking the write and its
own async format-on-save settling, so this degrades gracefully to
the old stale-tag-on-next-read failure mode, never to corruption).
- HashlineFilesystem.writeText propagates that verified content in
view-space (the same space readText returns -- e.g. a notebook's
editable cell text, not its raw JSON), not storage-space, so tag
validation on the next edit compares like with like.
- Patcher.commit keys fileHash/header/snapshot on the verified
post-write content (normalized, so BOM/line-ending restoration never
produces a false "drift") when it diverges from what was sent, and
appends a warning naming the drift -- but deliberately leaves the
returned `after` (and therefore the model-visible diff) scoped to
the intended hunk. Diffing against the full drifted file would
balloon the tool response to span every reformatted line (measured
~6.8x inflation on a 245-line file with one touched line); the
warning is the correct O(1) channel for "your editor reformatted
this," not an O(file-size) diff.
- write.ts keys its own snapshot header on the verified bridge content
too (no diff-size concern there since write always replaces the
whole file).
Caught via code review (dispatched against the first pass of this
fix): a naive "just use the verified content everywhere" fix broke
.ipynb editing outright (write-space vs read-space content mismatch,
tag invalid on every notebook edit) and would have inflated every
drifted edit response by ~6.8x. Both are now covered by regression
tests that fail against the pre-fix code and pass against this one.
(cherry picked from commit 35ab80e43be5800b2f48728e4400eb9fd7f7f7d2)
Attaching to a long-running browser (a signed-in profile, an Electron app kept
open for a session) meant repeating app.cdp_url on every browser call, and any
call that omitted it silently launched a fresh headless Chromium instead.
browser.cdpUrl supplies that endpoint once. It is a default rather than an
override: app.cdp_url and app.path still win, and an unset or blank value leaves
cmux and headless resolution exactly as before.
The !restrictToolNames guard on the pairing blocks was wrong: a restricted
session with tools:[checkpoint] passes isToolAllowed (requestedTools is
defined) but the pairing is skipped, stranding the agent without rewind.
Remove the guard — this is a safety pairing, not a convenience widening.
Added restricted-session tests in both createTools and SDK active-set paths.
Address Codex review: createTools auto-includes the sister tool in the
registry, but createAgentSession rebuilds the active set from the original
toolNames — so a one-sided tools: entry left the sister tool registered
but inactive. Mirror the pairing into explicitlyRequestedToolNames, gated
to !restrictToolNames for consistency with the manage_skill/learn mirror.
Also gate the index.ts pairing block with !restrictToolNames to match its
AST/auto-learn siblings, and fix the prompt to say 'or' not 'and'.
Address review feedback on PR #6938:
- One-sided tools: list checkpoint without rewind (or vice versa) now
auto-includes the sister tool, preventing a stuck subagent
- Add changelog entry under [Unreleased]
Closes#3762
When an agent definition's frontmatter list explicitly includes
checkpoint, rewind, learn, or manage_skill, allow them in subagents.
Previously all four were hard-gated to top-level sessions.
- Relax taskDepth gates in isToolAllowed using the already-captured
requestedTools variable (no signature change needed)
- Remove isTopLevelSession function and its 4 guard sites from checkpoint.ts
- Update checkpoint prompt with enablement docs
- Add tests for subagent explicit-request, no-request, disabled-setting,
and top-level paths
The guard now probes the full target before refusing, so an existing file
named like a selector list stays writable. Say so in the CHANGELOG entry
and the readSelectorListMisfire doc comment.
Probe the full target with probeLiteralPathExists before classifying it as a
mis-dispatched read-selector list, matching the single-selector guard, so an
existing POSIX file like 'report:1-2;archive:3-4' can still be overwritten.
The symmetric-padding change shrank the framed-block content width to
outputBlockContentWidth(width); the Ask renderer still pre-rendered its
question/result Markdown at the old width-2 budget, so maximal-width rows
re-wrapped inside the block and spilled a trailing fragment row.
- Reworked the `/guided-goal` command to send a hidden interview brief instead of a modal popup flow.
- Removed the deprecated `guided-setup.ts` module and system prompt template.
- Updated goal tool availability and activation logic to support goal creation during the interview.
- Replaced existing tests and added new verification for the updated guided-goal workflow.
- Remove the per-call `save` option from `tab.screenshot()` to simplify usage.
- Update `tab.screenshot()` to return the saved file path as a promise string.
- Configure screenshot persistence to use daemon path or custom `browser.screenshotDir`.
- Add comprehensive tests verifying temp path return and custom directory saving.
- Replaced the `XdevRegistry` class with the `XdevState` interface and pure helper functions across core and session tools.
- Updated session configurations, tool execution, and renderers to utilize canonical tool map initialization and sharing.
- Adapted unit tests and mocks to use `XdevState` and associated helper functions for permission and dispatch verification.
- read now treats an xd://-mounted inspect_image as available (top-level
predicate OR mounted device gated by the effective mode), so default
xdev sessions with a text-only model keep metadata-guidance reads
instead of inlining images the provider boundary would scrub
- advisor tool session stops inheriting the primary's isToolActive and
xdevRegistry: advisors cannot execute xd:// devices, so their reads
inline images again
- setModelWithProviderSessionReset is now async and awaited at every
callsite, so retry-fallback model switches cannot race the
inspect_image tool-slate reconcile
- regression tests for both xd:// availability directions
- read now derives its image behavior from actual tool availability
(session.isToolActive) with the mode computation as fallback, so
restricted sessions whose explicit slate omits inspect_image (e.g.
subagents) never get metadata-only reads pointing at an absent tool
- reconcile passes the post-change availability into the read
description sync, keeping the advertised prompt correct across flips
in both directions and when tool construction fails
- flat quoted-dotted inspect_image.mode is normalized into the nested
target during migration instead of being silently dropped when a
legacy flat enabled key is present
- regression tests for all three: availability-driven read behavior,
flat+flat migration, description advertising
- Reconcile inspect_image centrally from setModelWithProviderSessionReset
so retry-fallback model changes (turn-recovery.ts) that bypass
syncAfterModelChange cannot leave a stale tool set
- Apply persisted inspect_image.mode changes immediately from the
settings selector via a new handleSettingChange branch
- Refresh the read tool's advertised description during reconciliation,
before applyActiveToolsByName rebuilds the prompt, instead of only
lazily on the next image read
- Fix the flat (quoted-dotted) enabled->mode migration to write the
nested target form the resolver actually reads
- Add committed regression tests: tri-state x capability matrix,
override precedence, and enabled->mode migration (nested, flat, and
explicit-mode-wins)
Replace the inspect_image.enabled boolean with inspect_image.mode
(auto|on|off, default auto). In auto the tool is registered only when
the active model lacks native image input, so vision-capable models
(e.g. kimi-code/k3) read images inline with their own capabilities
instead of delegating to a separate vision model. on/off force
registration regardless of model capability.
- New utils/inspect-image-mode.ts resolves the effective state from the
/vision session override, the persisted setting, and model capability
- read tool re-evaluates the effective state per image read and
re-renders its description, so it returns decoded image blocks again
whenever inspect_image is hidden
- /vision [on|off|auto|status] slash command (modeled on /computer)
overrides the mode for the current session only
- Tool set is reconciled on model switch with a status notice when
inspect_image appears/disappears
- Legacy inspect_image.enabled true/false migrates to mode on/off
- Render device doc parameter schemas as TypeScript types instead of raw JSON schema dumps.
- Optimize marked streaming block rules and add pre-gates to reduce CPU overhead.
- Increase the markdown render cache entry budget from 32 KiB to 256 KiB.
- Added V8 `.cpuprofile` parser and bottleneck summary generation utilities.
- Integrated profile summary rendering into the read tool execution.
- Refactored profile rendering machinery into shared tree utilities.
- Added comprehensive unit and integration tests for cpuprofile parsing and read tool dispatch.
- Added a new parser and bottleneck summary renderer for macOS `/usr/bin/sample` reports with symbol demangling.
- Integrated automated summary parsing for sample reports into the ReadTool.
- Updated model configurations and pricing parameters across multiple providers.
- Added comprehensive unit and integration tests for sample profile parsing and ReadTool integration.
The read-selector-misfire guard (#6123/#6387) short-circuited whenever
`content` was non-empty, so a semicolon-joined list of read selectors
(`a.txt:1-2;b/c.txt:3-4`) passed as a write path with content fell through
to ordinary filesystem creation and silently built a nested directory tree
in the workspace. `read` accepts no such list, so this shape is always a
mis-dispatched multi-file read.
Refuse any target that splits on `;` into 2+ segments each carrying its own
read selector, regardless of `content` — the non-empty-content escape hatch
covers a lone selector-shaped filename, never a `;`-list.
Fixes#6809