zidane 5.8.0 → 5.8.3

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.
Files changed (58) hide show
  1. package/dist/{agent-DYghZGC8.d.ts → agent-CL4nT5Ti.d.ts} +155 -10
  2. package/dist/agent-CL4nT5Ti.d.ts.map +1 -0
  3. package/dist/chat/pure.d.ts +3 -3
  4. package/dist/chat.d.ts +6 -6
  5. package/dist/chat.js +2 -2
  6. package/dist/{headless-DnjYDckH.js → headless-BfEQtRfX.js} +4 -4
  7. package/dist/{headless-DnjYDckH.js.map → headless-BfEQtRfX.js.map} +1 -1
  8. package/dist/headless.d.ts +1 -1
  9. package/dist/headless.js +1 -1
  10. package/dist/{index-B2cMuK6S.d.ts → index-DdjlsREW.d.ts} +2 -2
  11. package/dist/{index-B2cMuK6S.d.ts.map → index-DdjlsREW.d.ts.map} +1 -1
  12. package/dist/{index-CVzpMtdq.d.ts → index-DtLfTUXt.d.ts} +2 -2
  13. package/dist/{index-CVzpMtdq.d.ts.map → index-DtLfTUXt.d.ts.map} +1 -1
  14. package/dist/index.d.ts +4 -4
  15. package/dist/index.js +5 -296
  16. package/dist/index.js.map +1 -1
  17. package/dist/{login-D-3A5CK7.js → login-Ck6dmcMH.js} +301 -4
  18. package/dist/login-Ck6dmcMH.js.map +1 -0
  19. package/dist/{mcp-Kqzz-Rs_.js → mcp-Br3b1Xm3.js} +50 -8
  20. package/dist/mcp-Br3b1Xm3.js.map +1 -0
  21. package/dist/mcp.d.ts +2 -2
  22. package/dist/mcp.js +2 -2
  23. package/dist/{presets-B1gWui0v.js → presets-DIweYdlN.js} +2 -2
  24. package/dist/{presets-B1gWui0v.js.map → presets-DIweYdlN.js.map} +1 -1
  25. package/dist/presets.d.ts +2 -2
  26. package/dist/presets.js +1 -1
  27. package/dist/providers.d.ts +1 -1
  28. package/dist/restate.d.ts +1 -1
  29. package/dist/session/sqlite.d.ts +1 -1
  30. package/dist/session.d.ts +1 -1
  31. package/dist/skills.d.ts +2 -2
  32. package/dist/{tool-formatters-0FEGRd6w.d.ts → tool-formatters-D-aWpu2N.d.ts} +2 -2
  33. package/dist/{tool-formatters-0FEGRd6w.d.ts.map → tool-formatters-D-aWpu2N.d.ts.map} +1 -1
  34. package/dist/tools/fetch-url.d.ts +1 -1
  35. package/dist/tools/web-search.d.ts +1 -1
  36. package/dist/{tools-BmPeBGU1.js → tools-DLRIhCl1.js} +109 -36
  37. package/dist/tools-DLRIhCl1.js.map +1 -0
  38. package/dist/tools.d.ts +2 -2
  39. package/dist/tools.js +1 -1
  40. package/dist/{transcript-anchors-D2erm5iS.d.ts → transcript-anchors-BJpOyLPm.d.ts} +5 -5
  41. package/dist/{transcript-anchors-D2erm5iS.d.ts.map → transcript-anchors-BJpOyLPm.d.ts.map} +1 -1
  42. package/dist/{transcript-anchors-Bp5bV0Bv.js → transcript-anchors-Dc0WlxnA.js} +18 -5
  43. package/dist/transcript-anchors-Dc0WlxnA.js.map +1 -0
  44. package/dist/tui.d.ts +14 -4
  45. package/dist/tui.d.ts.map +1 -1
  46. package/dist/tui.js +282 -76
  47. package/dist/tui.js.map +1 -1
  48. package/dist/{turn-operations-CeUrtsaM.d.ts → turn-operations-D-fypW6K.d.ts} +3 -3
  49. package/dist/{turn-operations-CeUrtsaM.d.ts.map → turn-operations-D-fypW6K.d.ts.map} +1 -1
  50. package/dist/types-BPw_i5vb.js.map +1 -1
  51. package/dist/types.d.ts +3 -3
  52. package/docs/ARCHITECTURE.md +2 -1
  53. package/package.json +1 -1
  54. package/dist/agent-DYghZGC8.d.ts.map +0 -1
  55. package/dist/login-D-3A5CK7.js.map +0 -1
  56. package/dist/mcp-Kqzz-Rs_.js.map +0 -1
  57. package/dist/tools-BmPeBGU1.js.map +0 -1
  58. package/dist/transcript-anchors-Bp5bV0Bv.js.map +0 -1
@@ -6,7 +6,7 @@ import { t as reconcileImageMediaType } from "./image-sniff-B7uFSNO1.js";
6
6
  import { n as createProcessContext } from "./contexts-BD2U_xpi.js";
7
7
  import { n as toolOutputByteLength, t as DEFAULT_AGENT_CLOCK } from "./types-BPw_i5vb.js";
8
8
  import { b as escapeXml, f as installAllowedToolsGate, g as validateResourcePath, n as resolveSkills, t as interpolateShellCommands, u as buildCatalog, y as createSkillActivationState } from "./interpolate-TySiqKzc.js";
9
- import { t as connectMcpServers } from "./mcp-Kqzz-Rs_.js";
9
+ import { n as connectMcpServers } from "./mcp-Br3b1Xm3.js";
10
10
  import { n as flattenTurns, r as formatTokenUsage, t as effectiveInputFromTurn } from "./stats-DAKBEKjc.js";
11
11
  import { dirname, isAbsolute, join, resolve } from "node:path";
12
12
  import { createHooks } from "hookable";
@@ -1382,6 +1382,18 @@ function resolveTasksDir(opts) {
1382
1382
  return join(opts.userDir, opts.sessionId, "tasks");
1383
1383
  }
1384
1384
  /**
1385
+ * Resolve the per-session MCP diagnostic directory under
1386
+ * `<userDir>/<sessionId>/mcp-warnings/`.
1387
+ *
1388
+ * Warning logs are cache/session artifacts like background task logs:
1389
+ * useful for debugging, safe to delete, and cleaned up with the session.
1390
+ */
1391
+ function resolveMcpWarningsDir(opts) {
1392
+ if (!isAbsolute(opts.userDir)) throw new Error(`resolveMcpWarningsDir: userDir must be absolute, got "${opts.userDir}"`);
1393
+ if (!opts.sessionId) throw new Error("resolveMcpWarningsDir: sessionId must be a non-empty string");
1394
+ return join(opts.userDir, opts.sessionId, "mcp-warnings");
1395
+ }
1396
+ /**
1385
1397
  * Decide-and-persist for a single tool result. Pure decision + filesystem
1386
1398
  * side-effect; returns the new wire-level `output` string when substitution
1387
1399
  * happened, otherwise tells the caller to leave the result alone.
@@ -1976,6 +1988,16 @@ const TOOL_USE_SKIPPED_MESSAGE = "[Tool use skipped — superseded by user messa
1976
1988
  * calls in the batch continue running, in contrast with a full-run abort.
1977
1989
  */
1978
1990
  const TOOL_USE_CANCELLED_MESSAGE = "[Tool call cancelled by user]";
1991
+ /** Reason surfaced on `siblingAbort.signal` when a shell error cancels its fleet. */
1992
+ const SHELL_CASCADE_REASON = "sibling-shell-error";
1993
+ /**
1994
+ * Canonical `tool_result.content` text emitted to siblings that were
1995
+ * cancelled by a `shell` error in the same batch. Distinct from
1996
+ * {@link INTERRUPT_MESSAGE_FOR_TOOL_USE} (user-issued abort) and
1997
+ * {@link TOOL_USE_SKIPPED_MESSAGE} (steered) so consumers can split
1998
+ * the three causes by string-match.
1999
+ */
2000
+ const SHELL_CASCADE_CANCEL_MESSAGE = "Cancelled: a sibling `shell` call in the same batch errored; re-run independently if still needed.";
1979
2001
  /**
1980
2002
  * Sentinel message rejected from the per-call cancellation promise inside
1981
2003
  * {@link executeSingleTool}'s race. Plain-string and module-private — only
@@ -2335,6 +2357,19 @@ function sanitizeStoredToolResults(provider, messages) {
2335
2357
  } : msg;
2336
2358
  });
2337
2359
  }
2360
+ /**
2361
+ * Build the `agent:abort` ctx, attaching the run id and (when present) the
2362
+ * string reason carried on `signal.reason` — lets telemetry correlate the
2363
+ * abort with its run and distinguish a reasoned abort (e.g. a guard) from a
2364
+ * bare `agent.abort()`.
2365
+ */
2366
+ function buildAbortCtx(ctx) {
2367
+ const reason = ctx.signal.reason;
2368
+ return {
2369
+ ...ctx.runId ? { runId: ctx.runId } : {},
2370
+ ...typeof reason === "string" && reason.length > 0 ? { reason } : {}
2371
+ };
2372
+ }
2338
2373
  async function runLoop(ctx) {
2339
2374
  let totalIn = 0;
2340
2375
  let totalOut = 0;
@@ -2356,7 +2391,7 @@ async function runLoop(ctx) {
2356
2391
  try {
2357
2392
  for (let turn = 0; turn < maxTurns; turn++) {
2358
2393
  if (ctx.signal.aborted) {
2359
- await ctx.hooks.callHook("agent:abort", {});
2394
+ await ctx.hooks.callHook("agent:abort", buildAbortCtx(ctx));
2360
2395
  break;
2361
2396
  }
2362
2397
  const result = await executeTurn(ctx, turn, {
@@ -2385,7 +2420,7 @@ async function runLoop(ctx) {
2385
2420
  if (pauseCap > 0 && consecutiveEmptyPauseTurns >= pauseCap) break;
2386
2421
  } else consecutiveEmptyPauseTurns = 0;
2387
2422
  if (ctx.signal.aborted) {
2388
- await ctx.hooks.callHook("agent:abort", {});
2423
+ await ctx.hooks.callHook("agent:abort", buildAbortCtx(ctx));
2389
2424
  break;
2390
2425
  }
2391
2426
  const breach = checkRunBudget({
@@ -2841,6 +2876,7 @@ async function executeTurn(ctx, turn, priorUsage) {
2841
2876
  turnId,
2842
2877
  usage: errorUsage,
2843
2878
  message: errorTurn,
2879
+ aborted: wasAborted,
2844
2880
  toolCounts: {
2845
2881
  turn: Object.freeze({}),
2846
2882
  run: Object.freeze({ ...ctx.runToolCounts })
@@ -2880,6 +2916,7 @@ async function executeTurn(ctx, turn, priorUsage) {
2880
2916
  turnId,
2881
2917
  usage: result.usage,
2882
2918
  message: assistantTurn,
2919
+ aborted: ctx.signal.aborted,
2883
2920
  toolCounts: {
2884
2921
  turn: Object.freeze(turnCounts),
2885
2922
  run: Object.freeze({ ...ctx.runToolCounts })
@@ -3216,6 +3253,7 @@ async function runSingleToolDispatch(ctx, call, turnId, fixed) {
3216
3253
  let output = "";
3217
3254
  let isError = false;
3218
3255
  let cancelledByUser = false;
3256
+ let reportedOutcome;
3219
3257
  const childSignal = typeof AbortSignal.any === "function" ? AbortSignal.any([ctx.signal, perCallAbort.signal]) : ctx.signal;
3220
3258
  try {
3221
3259
  const toolCtx = {
@@ -3238,7 +3276,10 @@ async function runSingleToolDispatch(ctx, call, turnId, fixed) {
3238
3276
  ...ctx.session ? { session: ctx.session } : {},
3239
3277
  ...ctx.readState ? { readState: ctx.readState } : {},
3240
3278
  ...typeof ctx.depth === "number" ? { depth: ctx.depth } : {},
3241
- clock: ctx.clock
3279
+ clock: ctx.clock,
3280
+ reportOutcome: (outcome) => {
3281
+ reportedOutcome = outcome;
3282
+ }
3242
3283
  };
3243
3284
  const bodyPromise = toolDef.execute(effectiveInput, toolCtx);
3244
3285
  bodyPromise.catch(() => {});
@@ -3261,7 +3302,10 @@ async function runSingleToolDispatch(ctx, call, turnId, fixed) {
3261
3302
  const isOurSentinel = err instanceof Error && err.message === CANCELLED_BY_USER_SENTINEL;
3262
3303
  const isAbortError = err instanceof Error && err.name === "AbortError";
3263
3304
  if (isOurSentinel || isAbortError && perCallAbort.signal.aborted) cancelledByUser = true;
3264
- else {
3305
+ else if (isAbortError && ctx.signal.aborted) {
3306
+ output = ctx.signal.reason === SHELL_CASCADE_REASON ? SHELL_CASCADE_CANCEL_MESSAGE : INTERRUPT_MESSAGE_FOR_TOOL_USE;
3307
+ isError = true;
3308
+ } else {
3265
3309
  const error = err instanceof Error ? err : new Error(String(err));
3266
3310
  const errorCtx = {
3267
3311
  ...buildToolHookBase(ctx, turnId, callId, call.name, displayName, effectiveInput),
@@ -3294,7 +3338,8 @@ async function runSingleToolDispatch(ctx, call, turnId, fixed) {
3294
3338
  output,
3295
3339
  isError,
3296
3340
  runToolCounts,
3297
- ...coercions ? { coercions } : {}
3341
+ ...coercions ? { coercions } : {},
3342
+ ...reportedOutcome ? { outcome: reportedOutcome } : {}
3298
3343
  });
3299
3344
  return { result: {
3300
3345
  id: callId,
@@ -3304,12 +3349,14 @@ async function runSingleToolDispatch(ctx, call, turnId, fixed) {
3304
3349
  }
3305
3350
  /**
3306
3351
  * Fire `tool:dispatched` with the resolved path discriminator. Every code
3307
- * path in {@link executeSingleTool} that produces a tool_result must call
3308
- * this exactly once — that contract is what makes `tool:dispatched`
3309
- * `tool:after` a guaranteed symmetric pairing for live-event consumers
3310
- * (the chat layer, SDK consumers reconstructing wire history from events,
3311
- * tracing spans that want to record refused calls separately from
3312
- * executed ones).
3352
+ * path in {@link executeSingleTool} that produces a tool_result calls this
3353
+ * exactly once — so a `tool:dispatched` precedes every terminal event. The
3354
+ * terminal that closes a dispatch depends on `outcome`: `execute` closes with
3355
+ * `tool:after` OR `tool:cancelled` (in-flight cancel), `gate-substitute` with
3356
+ * `tool:after`, and `gate-block`/`invalid-input`/`unknown` carry their result
3357
+ * on the dispatch path itself (no `tool:after`). See the `tool:dispatched`
3358
+ * hook doc in `agent.ts` for the full per-outcome pairing. The strict
3359
+ * direction holds unconditionally: no `tool:after` fires without a dispatch.
3313
3360
  *
3314
3361
  * Helper exists to centralize the optional-field plumbing for `reason`
3315
3362
  * (only set on `gate-block`) without sprinkling spread-conditional logic
@@ -3337,7 +3384,7 @@ async function fireDispatched(ctx, params) {
3337
3384
  * on the wire.
3338
3385
  */
3339
3386
  async function emitToolResult(ctx, params) {
3340
- const { turnId, callId, name, displayName, input, runToolCounts, coercions } = params;
3387
+ const { turnId, callId, name, displayName, input, runToolCounts, coercions, outcome } = params;
3341
3388
  let output = params.output;
3342
3389
  let isError = params.isError;
3343
3390
  const transformCtx = {
@@ -3350,6 +3397,7 @@ async function emitToolResult(ctx, params) {
3350
3397
  await ctx.hooks.callHook("tool:transform", transformCtx);
3351
3398
  output = transformCtx.result;
3352
3399
  isError = transformCtx.isError;
3400
+ const finalOutcome = isError && outcome && outcome !== "failed" ? "failed" : outcome;
3353
3401
  const threshold = ctx.persistThreshold;
3354
3402
  const persistDir = ctx.persistDir;
3355
3403
  if (threshold && threshold > 0 && persistDir) {
@@ -3371,7 +3419,8 @@ async function emitToolResult(ctx, params) {
3371
3419
  result: output,
3372
3420
  outputBytes: toolOutputByteLength(output),
3373
3421
  runToolCounts,
3374
- ...coercions ? { coercions } : {}
3422
+ ...coercions ? { coercions } : {},
3423
+ ...finalOutcome ? { outcome: finalOutcome } : {}
3375
3424
  });
3376
3425
  return {
3377
3426
  output,
@@ -3382,16 +3431,6 @@ async function emitToolResult(ctx, params) {
3382
3431
  const DEFAULT_MAX_CONCURRENT_TOOLS = 10;
3383
3432
  /** Canonical name of the shell tool — referenced for cascade-cancel semantics. */
3384
3433
  const SHELL_TOOL_NAME = "shell";
3385
- /** Reason surfaced on `siblingAbort.signal` when a shell error cancels its fleet. */
3386
- const SHELL_CASCADE_REASON = "sibling-shell-error";
3387
- /**
3388
- * Canonical `tool_result.content` text emitted to siblings that were
3389
- * cancelled by a `shell` error in the same batch. Distinct from
3390
- * {@link INTERRUPT_MESSAGE_FOR_TOOL_USE} (user-issued abort) and
3391
- * {@link TOOL_USE_SKIPPED_MESSAGE} (steered) so consumers can split
3392
- * the three causes by string-match.
3393
- */
3394
- const SHELL_CASCADE_CANCEL_MESSAGE = "Cancelled: a sibling `shell` call in the same batch errored; re-run independently if still needed.";
3395
3434
  /**
3396
3435
  * Resolve a tool's concurrency-safety verdict for a specific call.
3397
3436
  *
@@ -4967,6 +5006,7 @@ const HOOK_EVENT_SET = new Set([
4967
5006
  "child:turn:after",
4968
5007
  "mcp:connect",
4969
5008
  "mcp:error",
5009
+ "mcp:warn",
4970
5010
  "mcp:close",
4971
5011
  "mcp:bootstrap:start",
4972
5012
  "mcp:bootstrap:end",
@@ -5420,9 +5460,9 @@ function createAgent({ provider, name: agentName, system: agentSystem, tools: ag
5420
5460
  startedAt: runStartedAt,
5421
5461
  ...options.tracingContext ? { tracingContext: Object.freeze({ ...options.tracingContext }) } : {}
5422
5462
  });
5423
- if (externalSignal) if (externalSignal.aborted) abortController.abort();
5463
+ if (externalSignal) if (externalSignal.aborted) abortController.abort(externalSignal.reason);
5424
5464
  else {
5425
- externalAbortListener = () => abortController?.abort();
5465
+ externalAbortListener = () => abortController?.abort(externalSignal.reason);
5426
5466
  externalSignal.addEventListener("abort", externalAbortListener, { once: true });
5427
5467
  }
5428
5468
  idlePromise = new Promise((resolve) => {
@@ -5823,6 +5863,11 @@ function createAgent({ provider, name: agentName, system: agentSystem, tools: ag
5823
5863
  } catch (err) {
5824
5864
  await flushTurns({ failureFallback: true });
5825
5865
  if (abortController.signal.aborted) {
5866
+ const abortReason = abortController.signal.reason;
5867
+ await hooks.callHook("agent:abort", {
5868
+ ...runId ? { runId } : {},
5869
+ ...typeof abortReason === "string" && abortReason.length > 0 ? { reason: abortReason } : {}
5870
+ });
5826
5871
  session?.abortRun(runId);
5827
5872
  await finalizeSession("aborted");
5828
5873
  const stats = {
@@ -5887,10 +5932,14 @@ function createAgent({ provider, name: agentName, system: agentSystem, tools: ag
5887
5932
  return await executionContext.killBackground(executionHandle, taskId) !== null;
5888
5933
  }
5889
5934
  function steer(message) {
5935
+ if (abortController?.signal.aborted) return false;
5890
5936
  steeringQueue.push(message);
5937
+ return true;
5891
5938
  }
5892
5939
  function followUpFn(message) {
5940
+ if (abortController?.signal.aborted) return false;
5893
5941
  followUpQueue.push(message);
5942
+ return true;
5894
5943
  }
5895
5944
  function waitForIdle() {
5896
5945
  return idlePromise ?? Promise.resolve();
@@ -6009,6 +6058,7 @@ function createAgent({ provider, name: agentName, system: agentSystem, tools: ag
6009
6058
  if (destroyed) return;
6010
6059
  destroyed = true;
6011
6060
  pendingTaskNotifications.clear();
6061
+ if (abortController && !abortController.signal.aborted) abortController.abort("agent-destroyed");
6012
6062
  for (const controller of pendingToolCancels.values()) if (!controller.signal.aborted) controller.abort("agent-destroyed");
6013
6063
  pendingToolCancels.clear();
6014
6064
  if (mcpWarmupPromise) try {
@@ -6301,7 +6351,9 @@ const edit = {
6301
6351
  try {
6302
6352
  original = await ctx.execution.readFile(ctx.handle, target);
6303
6353
  } catch {
6304
- return `Edit error: file not found: ${target}.${await suggestionFor(ctx.execution, ctx.handle, target)}`;
6354
+ const hint = await suggestionFor(ctx.execution, ctx.handle, target);
6355
+ ctx.reportOutcome?.("failed");
6356
+ return `Edit error: file not found: ${target}.${hint}`;
6305
6357
  }
6306
6358
  if (ctx.behavior?.requireReadBeforeEdit) {
6307
6359
  const readState = resolveReadStateMap(ctx);
@@ -6315,13 +6367,17 @@ const edit = {
6315
6367
  const match = resolveOldString(original, find);
6316
6368
  if (!match) {
6317
6369
  const preview = nearestMatchPreview(original, find);
6370
+ ctx.reportOutcome?.("failed");
6318
6371
  return preview ? `Edit error: old_string not found in ${target}. Closest match in the file: ${preview}` : `Edit error: old_string not found in ${target}.`;
6319
6372
  }
6320
6373
  const { actual, occurrences, via } = match;
6321
6374
  if (occurrences > 1 && !replaceAll) return `Edit error: old_string appears ${occurrences} times in ${target}. Pass replace_all=true or expand old_string for uniqueness.`;
6322
6375
  const styledReplacement = styleReplacementForVia(replacement, via, actual);
6323
6376
  const updated = replaceAll ? original.split(actual).join(styledReplacement) : original.replace(actual, styledReplacement);
6324
- if (updated === original) return `Edit error: replacement produced no change in ${target}.`;
6377
+ if (updated === original) {
6378
+ ctx.reportOutcome?.("noop");
6379
+ return `Edit error: replacement produced no change in ${target}.`;
6380
+ }
6325
6381
  await ctx.execution.writeFile(ctx.handle, target, updated);
6326
6382
  const readState = resolveReadStateMap(ctx);
6327
6383
  if (readState) {
@@ -6333,6 +6389,7 @@ const edit = {
6333
6389
  mtimeMs: Date.now()
6334
6390
  });
6335
6391
  }
6392
+ ctx.reportOutcome?.("edited");
6336
6393
  return `Edited ${target}: replaced ${occurrences} occurrence${occurrences === 1 ? "" : "s"}.`;
6337
6394
  }
6338
6395
  };
@@ -6795,20 +6852,31 @@ const multiEdit = {
6795
6852
  async execute({ path, edits }, ctx) {
6796
6853
  const target = path;
6797
6854
  const steps = edits;
6798
- if (!Array.isArray(steps) || steps.length === 0) return `multi_edit error: edits must be a non-empty array.`;
6855
+ if (!Array.isArray(steps) || steps.length === 0) {
6856
+ ctx.reportOutcome?.("failed");
6857
+ return `multi_edit error: edits must be a non-empty array.`;
6858
+ }
6799
6859
  let current;
6800
6860
  try {
6801
6861
  current = await ctx.execution.readFile(ctx.handle, target);
6802
6862
  } catch {
6803
- return `multi_edit error: file not found: ${target}.${await suggestionFor(ctx.execution, ctx.handle, target)}`;
6863
+ const hint = await suggestionFor(ctx.execution, ctx.handle, target);
6864
+ ctx.reportOutcome?.("failed");
6865
+ return `multi_edit error: file not found: ${target}.${hint}`;
6804
6866
  }
6805
6867
  if (ctx.behavior?.requireReadBeforeEdit) {
6806
6868
  const readState = resolveReadStateMap(ctx);
6807
6869
  if (readState) {
6808
6870
  const absKey = readStateKey(ctx.handle.cwd, target);
6809
6871
  const prior = readState.get(absKey);
6810
- 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.`;
6811
- if (prior.contentHash !== hashContent(current)) return `multi_edit error: ${target} has changed on disk since the last read. Re-read the file before editing.`;
6872
+ if (!prior) {
6873
+ ctx.reportOutcome?.("failed");
6874
+ return `multi_edit error: ${target} has not been read in this session. Call read_file first so the edits apply against the current contents.`;
6875
+ }
6876
+ if (prior.contentHash !== hashContent(current)) {
6877
+ ctx.reportOutcome?.("failed");
6878
+ return `multi_edit error: ${target} has changed on disk since the last read. Re-read the file before editing.`;
6879
+ }
6812
6880
  }
6813
6881
  }
6814
6882
  const outcomes = [];
@@ -6864,6 +6932,7 @@ const multiEdit = {
6864
6932
  const failedCount = outcomes.length - appliedCount;
6865
6933
  if (appliedCount > 0) {
6866
6934
  await ctx.execution.writeFile(ctx.handle, target, current);
6935
+ ctx.reportOutcome?.("edited");
6867
6936
  const readState = resolveReadStateMap(ctx);
6868
6937
  if (readState) {
6869
6938
  const absKey = readStateKey(ctx.handle.cwd, target);
@@ -6874,7 +6943,7 @@ const multiEdit = {
6874
6943
  mtimeMs: Date.now()
6875
6944
  });
6876
6945
  }
6877
- }
6946
+ } else ctx.reportOutcome?.("failed");
6878
6947
  const n = steps.length;
6879
6948
  let header;
6880
6949
  if (appliedCount === n) header = `Edited ${target}: applied ${n} edit${n === 1 ? "" : "s"} (${totalReplacements} replacement${totalReplacements === 1 ? "" : "s"}).`;
@@ -7663,7 +7732,10 @@ const writeFile$1 = {
7663
7732
  existing = await ctx.execution.readFile(ctx.handle, targetPath);
7664
7733
  } catch {}
7665
7734
  const bytes = Buffer.byteLength(targetContent);
7666
- if (existing === targetContent) return `No change needed: ${targetPath} already at target state (${bytes} bytes).`;
7735
+ if (existing === targetContent) {
7736
+ ctx.reportOutcome?.("noop");
7737
+ return `No change needed: ${targetPath} already at target state (${bytes} bytes).`;
7738
+ }
7667
7739
  await ctx.execution.writeFile(ctx.handle, targetPath, targetContent);
7668
7740
  const readState = resolveReadStateMap(ctx);
7669
7741
  if (readState) readState.set(readStateKey(ctx.handle.cwd, targetPath), {
@@ -7673,10 +7745,11 @@ const writeFile$1 = {
7673
7745
  maxBytes: Number.POSITIVE_INFINITY,
7674
7746
  mtimeMs: Date.now()
7675
7747
  });
7748
+ ctx.reportOutcome?.(existing === void 0 ? "created" : "updated");
7676
7749
  return existing === void 0 ? `Created ${targetPath} (${bytes} bytes).` : `Updated ${targetPath} (${bytes} bytes).`;
7677
7750
  }
7678
7751
  };
7679
7752
  //#endregion
7680
- export { getReadState as A, enabledModelOptions as B, PERSISTED_STUB_PREFIX as C, maybePersistToolResult as D, cleanupPersistedSession as E, OUTPUT_RESERVE_TOKENS as F, modelSupportsReasoning as G, getModelInfo as H, anthropicDescriptor as I, openrouterDescriptor as J, modelsForDescriptor as K, cerebrasDescriptor as L, readStateKey as M, resolveReadStateMap as N, resolvePersistDir as O, BUILTIN_PROVIDERS as P, credKeyOf as R, validateToolArgs as S, buildPersistedStub as T, localDescriptor as U, getContextWindow as V, modelOptionsFor as W, restoreModelOptions as X, piIdOf as Y, shell as _, multiEdit as a, TOOL_USE_CANCELLED_MESSAGE as b, grep as c, createAgent as d, createToolSearchTool as f, createShellTool as g, createSkillsReadTool as h, readFile$1 as i, hashContent as j, resolveTasksDir as k, glob$1 as l, createSkillsRunScriptTool as m, createSpawnTool as n, listFiles as o, createSkillsUseTool as p, openaiDescriptor as q, shellKill as r, createInteractionTool as s, writeFile$1 as t, edit as u, INTERRUPT_MESSAGE_FOR_TOOL_USE as v, PERSISTENCE_PREVIEW_BYTES as w, TOOL_USE_SKIPPED_MESSAGE as x, SHELL_CASCADE_CANCEL_MESSAGE as y, effectiveContextWindow as z };
7753
+ export { resolveTasksDir as A, effectiveContextWindow as B, PERSISTED_STUB_PREFIX as C, maybePersistToolResult as D, cleanupPersistedSession as E, BUILTIN_PROVIDERS as F, modelOptionsFor as G, getContextWindow as H, OUTPUT_RESERVE_TOKENS as I, openaiDescriptor as J, modelSupportsReasoning as K, anthropicDescriptor as L, hashContent as M, readStateKey as N, resolveMcpWarningsDir as O, resolveReadStateMap as P, cerebrasDescriptor as R, validateToolArgs as S, buildPersistedStub as T, getModelInfo as U, enabledModelOptions as V, localDescriptor as W, piIdOf as X, openrouterDescriptor as Y, restoreModelOptions as Z, shell as _, multiEdit as a, TOOL_USE_CANCELLED_MESSAGE as b, grep as c, createAgent as d, createToolSearchTool as f, createShellTool as g, createSkillsReadTool as h, readFile$1 as i, getReadState as j, resolvePersistDir as k, glob$1 as l, createSkillsRunScriptTool as m, createSpawnTool as n, listFiles as o, createSkillsUseTool as p, modelsForDescriptor as q, shellKill as r, createInteractionTool as s, writeFile$1 as t, edit as u, INTERRUPT_MESSAGE_FOR_TOOL_USE as v, PERSISTENCE_PREVIEW_BYTES as w, TOOL_USE_SKIPPED_MESSAGE as x, SHELL_CASCADE_CANCEL_MESSAGE as y, credKeyOf as z };
7681
7754
 
7682
- //# sourceMappingURL=tools-BmPeBGU1.js.map
7755
+ //# sourceMappingURL=tools-DLRIhCl1.js.map