zidane 5.4.0 → 5.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"presets-M8f6lDnW.js","names":[],"sources":["../src/presets/basic.ts","../src/presets/index.ts"],"sourcesContent":["import { definePreset } from '.'\nimport { edit, listFiles, multiEdit, readFile, shell, writeFile } from '../tools'\nimport { createSpawnTool } from '../tools/spawn'\n\n/**\n * Core tools available in every basic preset (without spawn).\n *\n * `edit` and `multi_edit` ship in the basic set because surgical edits are the\n * default modality for production agents — `write_file` is for full overwrites.\n * `glob` and `grep` are exported but opt-in: not every agent needs codebase\n * search, and shipping them by default would force `tool:gate` work onto\n * consumers that prefer the model to use `shell` + classic Unix tools.\n */\nexport const basicTools = { shell, readFile, writeFile, listFiles, edit, multiEdit }\n\nexport default definePreset({\n name: 'basic',\n system: 'You are a helpful assistant with access to shell, file reading, file writing, surgical and multi-edit tools, directory listing, and sub-agent spawning. Prefer `edit` / `multi_edit` for in-place changes and `write_file` for full file overwrites. Use them to accomplish tasks in the project directory.',\n // `persist: true` shares the parent's session with every child agent — child\n // turns land in `session.turns` tagged with their own `runId`, and the run\n // itself is recorded in `session.runs` with `parentRunId` + `depth`. That's\n // what lets a reloaded TUI session reconstruct the full subagent tree (see\n // `eventsFromTurns` in `tui/store.ts`). Hosts that want children in-memory\n // only can construct their own preset with `createSpawnTool()`.\n tools: { ...basicTools, spawn: createSpawnTool({ persist: true }) },\n})\n","import type { AgentHooks, AgentOptions } from '../agent'\n\nexport type { AgentHookMap } from '../agent'\n\n/**\n * A preset is a reusable slice of `AgentOptions` — spread it into `createAgent()`\n * to configure tools, a default system prompt, aliases, behavior defaults, and\n * agent-lifetime hooks.\n *\n * `provider`, `execution`, `session`, and internal fields are excluded so presets\n * remain shareable and composable.\n *\n * ```ts\n * import { basic } from 'zidane/presets'\n * createAgent({ ...basic, provider })\n * ```\n *\n * ### Composing multiple presets\n *\n * Bare `...spread` is shallow — `{ ...a, ...b }` overwrites every key `b`\n * defines, including `hooks`. Use {@link composePresets} when you want\n * field-aware merging (per-event hook concat, tools shallow-merge, etc.):\n *\n * ```ts\n * createAgent({ ...composePresets(basic, telemetry, mine), provider })\n * ```\n */\nexport type Preset = Omit<Partial<AgentOptions>, 'provider' | 'execution' | 'session' | 'mcpConnector'>\n\n/**\n * Identity helper for type inference when defining a preset.\n */\nexport function definePreset(config: Preset): Preset {\n return config\n}\n\n/**\n * Field-aware composition of presets. Right-most preset wins for scalar fields;\n * objects shallow-merge; arrays and hook handler lists concatenate. Designed so\n * stacking presets does the obvious thing without the spread-collision footgun:\n *\n * - `name`, `system`, `eager`, `skills` → last-defined wins\n * - `tools`, `toolAliases`, `behavior` → shallow-merge (later keys override)\n * - `behavior.dedupTools`, `behavior.toolBudgets` → **deep-merge** (per-tool-name; later wins on collision)\n * - `mcpServers` → concat with last-wins on `name` collision\n * - `hooks` → per-event concat; every handler fires\n *\n * `hooks` always emerges as `event → handler[]` so downstream registration\n * (in `createAgent`) sees a uniform shape. Order of handlers within an event\n * follows preset order: earlier presets register first.\n *\n * `mcpServers` is deduped by `name` because shipping two servers with the same\n * name would trip the connector at runtime — a later preset overriding an\n * earlier preset's `github` server is the practical intent.\n *\n * `behavior.dedupTools` and `behavior.toolBudgets` get the same per-key deep-merge\n * because they are tool-name-keyed records — a preset that ships a dedup hasher\n * for one tool should not erase a hasher another preset ships for a different\n * tool. Last-wins still applies on a per-tool collision so a downstream preset\n * can override an upstream preset's policy for one specific tool. Other\n * `behavior` fields keep last-wins semantics.\n */\nexport function composePresets(...presets: Preset[]): Preset {\n const out: Preset = {}\n const hooksByEvent: { [K in keyof AgentHooks]?: AgentHooks[K][] } = {}\n // Keep mcpServers in source-order on first sight, but allow later\n // declarations to override earlier ones with the same `name`. A `Map`\n // keyed by name gives O(1) override + stable iteration.\n const mcpByName = new Map<string, NonNullable<Preset['mcpServers']>[number]>()\n\n for (const p of presets) {\n if (p.name !== undefined)\n out.name = p.name\n if (p.system !== undefined)\n out.system = p.system\n if (p.eager !== undefined)\n out.eager = p.eager\n if (p.skills !== undefined)\n out.skills = p.skills\n if (p.tools)\n out.tools = { ...out.tools, ...p.tools }\n if (p.toolAliases)\n out.toolAliases = { ...out.toolAliases, ...p.toolAliases }\n if (p.behavior) {\n // Top-level shallow-merge first; then deep-merge the two tool-name-keyed\n // sub-records so per-tool entries from earlier presets aren't clobbered.\n const merged: NonNullable<Preset['behavior']> = { ...out.behavior, ...p.behavior }\n if (out.behavior?.dedupTools || p.behavior.dedupTools) {\n merged.dedupTools = { ...out.behavior?.dedupTools, ...p.behavior.dedupTools }\n }\n if (out.behavior?.toolBudgets || p.behavior.toolBudgets) {\n merged.toolBudgets = { ...out.behavior?.toolBudgets, ...p.behavior.toolBudgets }\n }\n out.behavior = merged\n }\n if (p.mcpServers) {\n for (const server of p.mcpServers)\n mcpByName.set(server.name, server)\n }\n if (p.hooks) {\n for (const [event, handler] of Object.entries(p.hooks)) {\n if (handler === undefined)\n continue\n const list = Array.isArray(handler) ? handler : [handler]\n const key = event as keyof AgentHooks\n // Safe cast: we read the loose `AgentHookMap` shape (handler-or-array)\n // and re-emit only as arrays. Each `list` element matches the event's\n // handler signature by construction (the input was typed `AgentHookMap`).\n const bucket = (hooksByEvent[key] ??= []) as unknown[]\n bucket.push(...(list as unknown[]))\n }\n }\n }\n\n if (mcpByName.size > 0)\n out.mcpServers = [...mcpByName.values()]\n\n if (Object.keys(hooksByEvent).length > 0)\n out.hooks = hooksByEvent\n\n return out\n}\n\nexport { default as basic, basicTools } from './basic'\n"],"mappings":";;;;;;;;;;;AAaA,MAAa,aAAa;CAAE;CAAO;CAAU;CAAW;CAAW;CAAM;CAAW;AAEpF,IAAA,gBAAe,aAAa;CAC1B,MAAM;CACN,QAAQ;CAOR,OAAO;EAAE,GAAG;EAAY,OAAO,gBAAgB,EAAE,SAAS,MAAM,CAAC;EAAE;CACpE,CAAC;;;;;;ACOF,SAAgB,aAAa,QAAwB;CACnD,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6BT,SAAgB,eAAe,GAAG,SAA2B;CAC3D,MAAM,MAAc,EAAE;CACtB,MAAM,eAA8D,EAAE;CAItE,MAAM,4BAAY,IAAI,KAAwD;CAE9E,KAAK,MAAM,KAAK,SAAS;EACvB,IAAI,EAAE,SAAS,KAAA,GACb,IAAI,OAAO,EAAE;EACf,IAAI,EAAE,WAAW,KAAA,GACf,IAAI,SAAS,EAAE;EACjB,IAAI,EAAE,UAAU,KAAA,GACd,IAAI,QAAQ,EAAE;EAChB,IAAI,EAAE,WAAW,KAAA,GACf,IAAI,SAAS,EAAE;EACjB,IAAI,EAAE,OACJ,IAAI,QAAQ;GAAE,GAAG,IAAI;GAAO,GAAG,EAAE;GAAO;EAC1C,IAAI,EAAE,aACJ,IAAI,cAAc;GAAE,GAAG,IAAI;GAAa,GAAG,EAAE;GAAa;EAC5D,IAAI,EAAE,UAAU;GAGd,MAAM,SAA0C;IAAE,GAAG,IAAI;IAAU,GAAG,EAAE;IAAU;GAClF,IAAI,IAAI,UAAU,cAAc,EAAE,SAAS,YACzC,OAAO,aAAa;IAAE,GAAG,IAAI,UAAU;IAAY,GAAG,EAAE,SAAS;IAAY;GAE/E,IAAI,IAAI,UAAU,eAAe,EAAE,SAAS,aAC1C,OAAO,cAAc;IAAE,GAAG,IAAI,UAAU;IAAa,GAAG,EAAE,SAAS;IAAa;GAElF,IAAI,WAAW;;EAEjB,IAAI,EAAE,YACJ,KAAK,MAAM,UAAU,EAAE,YACrB,UAAU,IAAI,OAAO,MAAM,OAAO;EAEtC,IAAI,EAAE,OACJ,KAAK,MAAM,CAAC,OAAO,YAAY,OAAO,QAAQ,EAAE,MAAM,EAAE;GACtD,IAAI,YAAY,KAAA,GACd;GACF,MAAM,OAAO,MAAM,QAAQ,QAAQ,GAAG,UAAU,CAAC,QAAQ;GACzD,MAAM,MAAM;GAKZ,CADgB,aAAa,SAAS,EAAE,EACjC,KAAK,GAAI,KAAmB;;;CAKzC,IAAI,UAAU,OAAO,GACnB,IAAI,aAAa,CAAC,GAAG,UAAU,QAAQ,CAAC;CAE1C,IAAI,OAAO,KAAK,aAAa,CAAC,SAAS,GACrC,IAAI,QAAQ;CAEd,OAAO"}
1
+ {"version":3,"file":"presets-Ck4VusTo.js","names":[],"sources":["../src/presets/basic.ts","../src/presets/index.ts"],"sourcesContent":["import { definePreset } from '.'\nimport { edit, listFiles, multiEdit, readFile, shell, writeFile } from '../tools'\nimport { createSpawnTool } from '../tools/spawn'\n\n/**\n * Core tools available in every basic preset (without spawn).\n *\n * `edit` and `multi_edit` ship in the basic set because surgical edits are the\n * default modality for production agents — `write_file` is for full overwrites.\n * `glob` and `grep` are exported but opt-in: not every agent needs codebase\n * search, and shipping them by default would force `tool:gate` work onto\n * consumers that prefer the model to use `shell` + classic Unix tools.\n */\nexport const basicTools = { shell, readFile, writeFile, listFiles, edit, multiEdit }\n\nexport default definePreset({\n name: 'basic',\n system: 'You are a helpful assistant with access to shell, file reading, file writing, surgical and multi-edit tools, directory listing, and sub-agent spawning. Prefer `edit` / `multi_edit` for in-place changes and `write_file` for full file overwrites. Use them to accomplish tasks in the project directory.',\n // `persist: true` shares the parent's session with every child agent — child\n // turns land in `session.turns` tagged with their own `runId`, and the run\n // itself is recorded in `session.runs` with `parentRunId` + `depth`. That's\n // what lets a reloaded TUI session reconstruct the full subagent tree (see\n // `eventsFromTurns` in `tui/store.ts`). Hosts that want children in-memory\n // only can construct their own preset with `createSpawnTool()`.\n tools: { ...basicTools, spawn: createSpawnTool({ persist: true }) },\n})\n","import type { AgentHooks, AgentOptions } from '../agent'\n\nexport type { AgentHookMap } from '../agent'\n\n/**\n * A preset is a reusable slice of `AgentOptions` — spread it into `createAgent()`\n * to configure tools, a default system prompt, aliases, behavior defaults, and\n * agent-lifetime hooks.\n *\n * `provider`, `execution`, `session`, and internal fields are excluded so presets\n * remain shareable and composable.\n *\n * ```ts\n * import { basic } from 'zidane/presets'\n * createAgent({ ...basic, provider })\n * ```\n *\n * ### Composing multiple presets\n *\n * Bare `...spread` is shallow — `{ ...a, ...b }` overwrites every key `b`\n * defines, including `hooks`. Use {@link composePresets} when you want\n * field-aware merging (per-event hook concat, tools shallow-merge, etc.):\n *\n * ```ts\n * createAgent({ ...composePresets(basic, telemetry, mine), provider })\n * ```\n */\nexport type Preset = Omit<Partial<AgentOptions>, 'provider' | 'execution' | 'session' | 'mcpConnector'>\n\n/**\n * Identity helper for type inference when defining a preset.\n */\nexport function definePreset(config: Preset): Preset {\n return config\n}\n\n/**\n * Field-aware composition of presets. Right-most preset wins for scalar fields;\n * objects shallow-merge; arrays and hook handler lists concatenate. Designed so\n * stacking presets does the obvious thing without the spread-collision footgun:\n *\n * - `name`, `system`, `eager`, `skills` → last-defined wins\n * - `tools`, `toolAliases`, `behavior` → shallow-merge (later keys override)\n * - `behavior.dedupTools`, `behavior.toolBudgets` → **deep-merge** (per-tool-name; later wins on collision)\n * - `mcpServers` → concat with last-wins on `name` collision\n * - `hooks` → per-event concat; every handler fires\n *\n * `hooks` always emerges as `event → handler[]` so downstream registration\n * (in `createAgent`) sees a uniform shape. Order of handlers within an event\n * follows preset order: earlier presets register first.\n *\n * `mcpServers` is deduped by `name` because shipping two servers with the same\n * name would trip the connector at runtime — a later preset overriding an\n * earlier preset's `github` server is the practical intent.\n *\n * `behavior.dedupTools` and `behavior.toolBudgets` get the same per-key deep-merge\n * because they are tool-name-keyed records — a preset that ships a dedup hasher\n * for one tool should not erase a hasher another preset ships for a different\n * tool. Last-wins still applies on a per-tool collision so a downstream preset\n * can override an upstream preset's policy for one specific tool. Other\n * `behavior` fields keep last-wins semantics.\n */\nexport function composePresets(...presets: Preset[]): Preset {\n const out: Preset = {}\n const hooksByEvent: { [K in keyof AgentHooks]?: AgentHooks[K][] } = {}\n // Keep mcpServers in source-order on first sight, but allow later\n // declarations to override earlier ones with the same `name`. A `Map`\n // keyed by name gives O(1) override + stable iteration.\n const mcpByName = new Map<string, NonNullable<Preset['mcpServers']>[number]>()\n\n for (const p of presets) {\n if (p.name !== undefined)\n out.name = p.name\n if (p.system !== undefined)\n out.system = p.system\n if (p.eager !== undefined)\n out.eager = p.eager\n if (p.skills !== undefined)\n out.skills = p.skills\n if (p.tools)\n out.tools = { ...out.tools, ...p.tools }\n if (p.toolAliases)\n out.toolAliases = { ...out.toolAliases, ...p.toolAliases }\n if (p.behavior) {\n // Top-level shallow-merge first; then deep-merge the two tool-name-keyed\n // sub-records so per-tool entries from earlier presets aren't clobbered.\n const merged: NonNullable<Preset['behavior']> = { ...out.behavior, ...p.behavior }\n if (out.behavior?.dedupTools || p.behavior.dedupTools) {\n merged.dedupTools = { ...out.behavior?.dedupTools, ...p.behavior.dedupTools }\n }\n if (out.behavior?.toolBudgets || p.behavior.toolBudgets) {\n merged.toolBudgets = { ...out.behavior?.toolBudgets, ...p.behavior.toolBudgets }\n }\n out.behavior = merged\n }\n if (p.mcpServers) {\n for (const server of p.mcpServers)\n mcpByName.set(server.name, server)\n }\n if (p.hooks) {\n for (const [event, handler] of Object.entries(p.hooks)) {\n if (handler === undefined)\n continue\n const list = Array.isArray(handler) ? handler : [handler]\n const key = event as keyof AgentHooks\n // Safe cast: we read the loose `AgentHookMap` shape (handler-or-array)\n // and re-emit only as arrays. Each `list` element matches the event's\n // handler signature by construction (the input was typed `AgentHookMap`).\n const bucket = (hooksByEvent[key] ??= []) as unknown[]\n bucket.push(...(list as unknown[]))\n }\n }\n }\n\n if (mcpByName.size > 0)\n out.mcpServers = [...mcpByName.values()]\n\n if (Object.keys(hooksByEvent).length > 0)\n out.hooks = hooksByEvent\n\n return out\n}\n\nexport { default as basic, basicTools } from './basic'\n"],"mappings":";;;;;;;;;;;AAaA,MAAa,aAAa;CAAE;CAAO;CAAU;CAAW;CAAW;CAAM;CAAW;AAEpF,IAAA,gBAAe,aAAa;CAC1B,MAAM;CACN,QAAQ;CAOR,OAAO;EAAE,GAAG;EAAY,OAAO,gBAAgB,EAAE,SAAS,MAAM,CAAC;EAAE;CACpE,CAAC;;;;;;ACOF,SAAgB,aAAa,QAAwB;CACnD,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6BT,SAAgB,eAAe,GAAG,SAA2B;CAC3D,MAAM,MAAc,EAAE;CACtB,MAAM,eAA8D,EAAE;CAItE,MAAM,4BAAY,IAAI,KAAwD;CAE9E,KAAK,MAAM,KAAK,SAAS;EACvB,IAAI,EAAE,SAAS,KAAA,GACb,IAAI,OAAO,EAAE;EACf,IAAI,EAAE,WAAW,KAAA,GACf,IAAI,SAAS,EAAE;EACjB,IAAI,EAAE,UAAU,KAAA,GACd,IAAI,QAAQ,EAAE;EAChB,IAAI,EAAE,WAAW,KAAA,GACf,IAAI,SAAS,EAAE;EACjB,IAAI,EAAE,OACJ,IAAI,QAAQ;GAAE,GAAG,IAAI;GAAO,GAAG,EAAE;GAAO;EAC1C,IAAI,EAAE,aACJ,IAAI,cAAc;GAAE,GAAG,IAAI;GAAa,GAAG,EAAE;GAAa;EAC5D,IAAI,EAAE,UAAU;GAGd,MAAM,SAA0C;IAAE,GAAG,IAAI;IAAU,GAAG,EAAE;IAAU;GAClF,IAAI,IAAI,UAAU,cAAc,EAAE,SAAS,YACzC,OAAO,aAAa;IAAE,GAAG,IAAI,UAAU;IAAY,GAAG,EAAE,SAAS;IAAY;GAE/E,IAAI,IAAI,UAAU,eAAe,EAAE,SAAS,aAC1C,OAAO,cAAc;IAAE,GAAG,IAAI,UAAU;IAAa,GAAG,EAAE,SAAS;IAAa;GAElF,IAAI,WAAW;;EAEjB,IAAI,EAAE,YACJ,KAAK,MAAM,UAAU,EAAE,YACrB,UAAU,IAAI,OAAO,MAAM,OAAO;EAEtC,IAAI,EAAE,OACJ,KAAK,MAAM,CAAC,OAAO,YAAY,OAAO,QAAQ,EAAE,MAAM,EAAE;GACtD,IAAI,YAAY,KAAA,GACd;GACF,MAAM,OAAO,MAAM,QAAQ,QAAQ,GAAG,UAAU,CAAC,QAAQ;GACzD,MAAM,MAAM;GAKZ,CADgB,aAAa,SAAS,EAAE,EACjC,KAAK,GAAI,KAAmB;;;CAKzC,IAAI,UAAU,OAAO,GACnB,IAAI,aAAa,CAAC,GAAG,UAAU,QAAQ,CAAC;CAE1C,IAAI,OAAO,KAAK,aAAa,CAAC,SAAS,GACrC,IAAI,QAAQ;CAEd,OAAO"}
package/dist/presets.js CHANGED
@@ -1,2 +1,2 @@
1
- import { i as basic_default, n as definePreset, r as basicTools, t as composePresets } from "./presets-M8f6lDnW.js";
1
+ import { i as basic_default, n as definePreset, r as basicTools, t as composePresets } from "./presets-Ck4VusTo.js";
2
2
  export { basic_default as basic, basicTools, composePresets, definePreset };
@@ -1071,6 +1071,8 @@ function applyStaleReadElision(messages) {
1071
1071
  messages,
1072
1072
  elidedPaths: []
1073
1073
  };
1074
+ const pathsWithFreshRead = /* @__PURE__ */ new Set();
1075
+ for (const [callId, info] of readCallInfo) if (!staleCallIds.has(callId)) pathsWithFreshRead.add(info.path);
1074
1076
  let changed = false;
1075
1077
  const out = messages.slice();
1076
1078
  for (let i = 0; i < out.length; i++) {
@@ -1094,7 +1096,7 @@ function applyStaleReadElision(messages) {
1094
1096
  }
1095
1097
  return {
1096
1098
  messages: changed ? out : messages,
1097
- elidedPaths: [...elidedPathSet]
1099
+ elidedPaths: [...elidedPathSet].filter((p) => !pathsWithFreshRead.has(p))
1098
1100
  };
1099
1101
  }
1100
1102
  /**
@@ -3770,7 +3772,7 @@ const edit = {
3770
3772
  if (readState) {
3771
3773
  const absKey = readStateKey(ctx.handle.cwd, target);
3772
3774
  const prior = readState.get(absKey);
3773
- if (!prior) return `Edit error: ${target} has not been read in this session. Call read_file first so the edit applies against the current contents. (Reads inside a \`spawn\` subagent with \`persist: false\` and without \`shareReadState: true\` do NOT propagate to the parent — re-read in the calling context.)`;
3775
+ if (!prior) return `Edit error: ${target} has not been read in this session. Call read_file first so the edit applies against the current contents.`;
3774
3776
  if (prior.contentHash !== hashContent(original)) return `Edit error: ${target} has changed on disk since the last read. Re-read the file before editing.`;
3775
3777
  }
3776
3778
  }
@@ -4193,10 +4195,33 @@ const listFiles = {
4193
4195
  };
4194
4196
  //#endregion
4195
4197
  //#region src/tools/multi-edit.ts
4198
+ /**
4199
+ * Inline annotation builder — kept local to avoid importing from
4200
+ * `chat/edit-approval.ts` (that's a renderer-side module; tools live
4201
+ * one layer below). Line shape matches `parseEditOutcomesFromResult`'s
4202
+ * regex so the round-trip is lossless.
4203
+ *
4204
+ * Newlines in `reason` are folded to spaces because the parser is line-
4205
+ * scoped (`body.split('\n')`); a multi-line reason would split into a
4206
+ * "trailing prose" line and trip the malformed-block guard, losing every
4207
+ * outcome below it. Static reasons in this file are single-line; the
4208
+ * sanitize is a guard against a pathological `target` (file path
4209
+ * containing a newline) leaking into `old_string not found in <target>`.
4210
+ */
4211
+ function annotationFor(outcomes) {
4212
+ const lines = ["<edit-outcomes>"];
4213
+ for (let i = 0; i < outcomes.length; i++) {
4214
+ const o = outcomes[i];
4215
+ const reason = o.reason ? `: ${o.reason.replace(/\r?\n/g, " ")}` : "";
4216
+ lines.push(`#${i + 1} ${o.kind}${reason}`);
4217
+ }
4218
+ lines.push("</edit-outcomes>");
4219
+ return lines.join("\n");
4220
+ }
4196
4221
  const multiEdit = {
4197
4222
  spec: {
4198
4223
  name: "multi_edit",
4199
- description: "Apply a sequential list of edits to a file. Each edit operates on the result of the previous edit. Prefer this over multiple `edit` calls when several non-overlapping changes are needed in the same file. The batch is atomic if any edit fails to apply, none are written. Each step tolerates `read_file` line-number prefixes (`<N>\\t…`, `<N>|…`, or `<N>→…`) in `old_string` / `new_string`.",
4224
+ description: "Apply a sequential list of edits to a file. Each edit operates on the result of the previous APPLIED edit. Prefer this over multiple `edit` calls when several non-overlapping changes are needed in the same file. Edits run **best-effort**: a per-step failure (`old_string` not found, ambiguous match without `replace_all`, identical strings) is reported in the result but does NOT block the remaining steps. The file is written iff at least one step applied. The result lists per-hunk outcomes (`applied` / `failed`) so the model can re-issue just the failures without resending the whole batch. Each step tolerates `read_file` line-number prefixes (`<N>\\t…`, `<N>|…`, or `<N>→…`) in `old_string` / `new_string`.",
4200
4225
  inputSchema: {
4201
4226
  type: "object",
4202
4227
  properties: {
@@ -4206,7 +4231,7 @@ const multiEdit = {
4206
4231
  },
4207
4232
  edits: {
4208
4233
  type: "array",
4209
- description: "List of edits applied in order; each operates on the previous edit's output.",
4234
+ description: "List of edits applied in order; each operates on the previous applied edit's output.",
4210
4235
  items: {
4211
4236
  type: "object",
4212
4237
  properties: {
@@ -4236,40 +4261,88 @@ const multiEdit = {
4236
4261
  if (readState) {
4237
4262
  const absKey = readStateKey(ctx.handle.cwd, target);
4238
4263
  const prior = readState.get(absKey);
4239
- if (!prior) return `multi_edit error: ${target} has not been read in this session. Call read_file first so the edits apply against the current contents. (Reads inside a \`spawn\` subagent with \`persist: false\` and without \`shareReadState: true\` do NOT propagate to the parent — re-read in the calling context.)`;
4264
+ if (!prior) return `multi_edit error: ${target} has not been read in this session. Call read_file first so the edits apply against the current contents.`;
4240
4265
  if (prior.contentHash !== hashContent(current)) return `multi_edit error: ${target} has changed on disk since the last read. Re-read the file before editing.`;
4241
4266
  }
4242
4267
  }
4268
+ const outcomes = [];
4243
4269
  let totalReplacements = 0;
4244
4270
  for (let i = 0; i < steps.length; i++) {
4245
4271
  const step = steps[i];
4246
4272
  const find = step.old_string;
4247
4273
  const replacement = step.new_string;
4248
4274
  const replaceAll = step.replace_all === true;
4249
- if (typeof find !== "string" || typeof replacement !== "string") return `multi_edit error: edit #${i + 1} is missing old_string or new_string.`;
4250
- if (find.length === 0) return `multi_edit error: edit #${i + 1} has empty old_string. Use write_file to fully replace a file.`;
4251
- if (find === replacement) return `multi_edit error: edit #${i + 1} old_string and new_string are identical.`;
4275
+ if (typeof find !== "string" || typeof replacement !== "string") {
4276
+ outcomes.push({
4277
+ kind: "failed",
4278
+ reason: "missing old_string or new_string"
4279
+ });
4280
+ continue;
4281
+ }
4282
+ if (find.length === 0) {
4283
+ outcomes.push({
4284
+ kind: "failed",
4285
+ reason: "empty old_string (use write_file to fully replace a file)"
4286
+ });
4287
+ continue;
4288
+ }
4289
+ if (find === replacement) {
4290
+ outcomes.push({
4291
+ kind: "failed",
4292
+ reason: "old_string and new_string are identical"
4293
+ });
4294
+ continue;
4295
+ }
4252
4296
  const match = resolveOldString(current, find);
4253
- if (!match) return `multi_edit error: edit #${i + 1} old_string not found in ${target}.`;
4297
+ if (!match) {
4298
+ outcomes.push({
4299
+ kind: "failed",
4300
+ reason: `old_string not found in ${target}`
4301
+ });
4302
+ continue;
4303
+ }
4254
4304
  const { actual, occurrences, via } = match;
4255
- if (occurrences > 1 && !replaceAll) return `multi_edit error: edit #${i + 1} old_string appears ${occurrences} times. Pass replace_all=true on this edit or expand old_string for uniqueness.`;
4305
+ if (occurrences > 1 && !replaceAll) {
4306
+ outcomes.push({
4307
+ kind: "failed",
4308
+ reason: `old_string appears ${occurrences} times — pass replace_all=true on this edit or expand old_string for uniqueness`
4309
+ });
4310
+ continue;
4311
+ }
4256
4312
  const styledReplacement = styleReplacementForVia(replacement, via, actual);
4257
4313
  current = replaceAll ? current.split(actual).join(styledReplacement) : current.replace(actual, styledReplacement);
4258
4314
  totalReplacements += occurrences;
4315
+ outcomes.push({ kind: "applied" });
4259
4316
  }
4260
- await ctx.execution.writeFile(ctx.handle, target, current);
4261
- const readState = resolveReadStateMap(ctx);
4262
- if (readState) {
4263
- const absKey = readStateKey(ctx.handle.cwd, target);
4264
- const prior = readState.get(absKey);
4265
- if (prior) readState.set(absKey, {
4266
- ...prior,
4267
- contentHash: hashContent(current),
4268
- mtimeMs: Date.now()
4269
- });
4317
+ const appliedCount = outcomes.reduce((n, o) => o.kind === "applied" ? n + 1 : n, 0);
4318
+ const failedCount = outcomes.length - appliedCount;
4319
+ if (appliedCount > 0) {
4320
+ await ctx.execution.writeFile(ctx.handle, target, current);
4321
+ const readState = resolveReadStateMap(ctx);
4322
+ if (readState) {
4323
+ const absKey = readStateKey(ctx.handle.cwd, target);
4324
+ const prior = readState.get(absKey);
4325
+ if (prior) readState.set(absKey, {
4326
+ ...prior,
4327
+ contentHash: hashContent(current),
4328
+ mtimeMs: Date.now()
4329
+ });
4330
+ }
4270
4331
  }
4271
4332
  const n = steps.length;
4272
- return `Edited ${target}: applied ${n} edit${n === 1 ? "" : "s"} (${totalReplacements} replacement${totalReplacements === 1 ? "" : "s"}).`;
4333
+ let header;
4334
+ if (appliedCount === n) header = `Edited ${target}: applied ${n} edit${n === 1 ? "" : "s"} (${totalReplacements} replacement${totalReplacements === 1 ? "" : "s"}).`;
4335
+ else if (appliedCount > 0) header = `Edited ${target}: applied ${appliedCount} of ${n} edits (${totalReplacements} replacement${totalReplacements === 1 ? "" : "s"}).`;
4336
+ else header = `multi_edit error: no edits applied to ${target} (${n} attempted).`;
4337
+ const failureLines = [];
4338
+ for (let i = 0; i < outcomes.length; i++) {
4339
+ const o = outcomes[i];
4340
+ if (o.kind === "failed") failureLines.push(`edit #${i + 1} failed: ${o.reason}`);
4341
+ }
4342
+ const parts = [header];
4343
+ if (failureLines.length > 0) parts.push(failureLines.join("\n"));
4344
+ if (failedCount > 0) parts.push(annotationFor(outcomes));
4345
+ return parts.join("\n\n");
4273
4346
  }
4274
4347
  };
4275
4348
  //#endregion
@@ -5165,4 +5238,4 @@ const writeFile$1 = {
5165
5238
  //#endregion
5166
5239
  export { readStateKey as A, PERSISTENCE_PREVIEW_BYTES as C, resolvePersistDir as D, maybePersistToolResult as E, getReadState as O, PERSISTED_STUB_PREFIX as S, cleanupPersistedSession as T, createSkillsReadTool as _, multiEdit as a, TOOL_USE_SKIPPED_MESSAGE as b, grep as c, resolveOldString as d, styleReplacementForVia as f, createSkillsRunScriptTool as g, createSkillsUseTool as h, readFile$1 as i, resolveReadStateMap as j, hashContent as k, glob as l, createToolSearchTool as m, createSpawnTool as n, listFiles as o, createAgent as p, shell as r, createInteractionTool as s, writeFile$1 as t, edit as u, INTERRUPT_MESSAGE_FOR_TOOL_USE as v, buildPersistedStub as w, validateToolArgs as x, SHELL_CASCADE_CANCEL_MESSAGE as y };
5167
5240
 
5168
- //# sourceMappingURL=tools-DKdyPoUf.js.map
5241
+ //# sourceMappingURL=tools-PQH1Ge4M.js.map