@cydm/pie 1.0.45 → 1.0.47

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,7 +1,7 @@
1
1
  import { createRequire as __createRequire } from "node:module"; const require = __createRequire(import.meta.url);
2
2
  import {
3
3
  createAskUserCapability
4
- } from "../../../chunks/chunk-PKR3PO73.js";
4
+ } from "../../../chunks/chunk-IJMP3JTX.js";
5
5
  import "../../../chunks/chunk-FKAO25XH.js";
6
6
  import "../../../chunks/chunk-K6ZOCJ4V.js";
7
7
  import "../../../chunks/chunk-LZQXQOPJ.js";
@@ -7,7 +7,7 @@ import {
7
7
  isPlanModeSafeCommand,
8
8
  markCompletedPlanSteps,
9
9
  restoreExecutionState
10
- } from "../../../chunks/chunk-PKR3PO73.js";
10
+ } from "../../../chunks/chunk-IJMP3JTX.js";
11
11
  import "../../../chunks/chunk-FKAO25XH.js";
12
12
  import {
13
13
  Type
@@ -1,11 +1,11 @@
1
1
  import { createRequire as __createRequire } from "node:module"; const require = __createRequire(import.meta.url);
2
2
  import {
3
3
  createCliHostCapabilities
4
- } from "../../../chunks/chunk-75JWZPRK.js";
4
+ } from "../../../chunks/chunk-U4B6RJ6Z.js";
5
5
  import {
6
6
  createSharedFileSystemTools,
7
7
  createSubagentCapability
8
- } from "../../../chunks/chunk-PKR3PO73.js";
8
+ } from "../../../chunks/chunk-IJMP3JTX.js";
9
9
  import "../../../chunks/chunk-FKAO25XH.js";
10
10
  import "../../../chunks/chunk-K6ZOCJ4V.js";
11
11
  import "../../../chunks/chunk-LZQXQOPJ.js";
@@ -7,7 +7,7 @@ import {
7
7
  executeManageTodoList,
8
8
  executionStateToTodos,
9
9
  restoreExecutionState
10
- } from "../../../chunks/chunk-PKR3PO73.js";
10
+ } from "../../../chunks/chunk-IJMP3JTX.js";
11
11
  import "../../../chunks/chunk-FKAO25XH.js";
12
12
  import "../../../chunks/chunk-K6ZOCJ4V.js";
13
13
  import "../../../chunks/chunk-LZQXQOPJ.js";
@@ -3524,9 +3524,6 @@ var SessionManager = class {
3524
3524
  (e) => e.type === "message" && e.message
3525
3525
  ).length;
3526
3526
  if (this.activeSession.metadata.messageCount === 0) {
3527
- if (await this.store.exists(this.activeSession.id)) {
3528
- await this.store.delete(this.activeSession.id);
3529
- }
3530
3527
  this.activeSession.isDirty = false;
3531
3528
  return;
3532
3529
  }
@@ -4692,6 +4689,31 @@ function asTerminalFailure(failure, message = failure.message) {
4692
4689
  message
4693
4690
  };
4694
4691
  }
4692
+ function emitTurnEndPolicyFailure(emit, error) {
4693
+ const message = error instanceof Error ? error.message : String(error);
4694
+ emit({
4695
+ type: "turn_failed_terminal",
4696
+ failure: {
4697
+ kind: "internal_error",
4698
+ message: `Turn-end policy failed: ${message}`,
4699
+ retryable: false,
4700
+ compactable: false,
4701
+ userAbort: false,
4702
+ terminal: true
4703
+ }
4704
+ });
4705
+ emit({ type: "turn_settled" });
4706
+ }
4707
+ function handleTurnEndPolicyThrow(emit, error, context) {
4708
+ context.cancelPendingRecovery();
4709
+ if (context.recoveryWasActive) {
4710
+ context.markRecoveryTerminal();
4711
+ emitTurnEndPolicyFailure(emit, error);
4712
+ return;
4713
+ }
4714
+ const message = error instanceof Error ? error.message : String(error);
4715
+ emit({ type: "turn_policy_error", message: `Turn-end policy failed after settle: ${message}` });
4716
+ }
4695
4717
 
4696
4718
  // ../../packages/agent-framework/src/session/controller/retry-policy.ts
4697
4719
  function defaultSetTimeout4(callback, delay) {
@@ -4728,6 +4750,7 @@ var AgentSessionRetryController = class {
4728
4750
  }
4729
4751
  async handleTurnEnd(message, failure) {
4730
4752
  if (!this.retry || !message || message.role !== "assistant") {
4753
+ this.settleStrandedRetry(failure);
4731
4754
  return;
4732
4755
  }
4733
4756
  if (!failure) {
@@ -4790,6 +4813,33 @@ var AgentSessionRetryController = class {
4790
4813
  });
4791
4814
  }, delayMs);
4792
4815
  }
4816
+ /**
4817
+ * A retried turn can end without an assistant failure message (for example
4818
+ * the request dies before any content arrives, or a host classifier throws).
4819
+ * Without this net the pending recovery promise never resolves:
4820
+ * waitForRetry()/waitForSettled() hang forever, queued input never
4821
+ * dispatches, and the whole task freezes with no turn_settled.
4822
+ */
4823
+ settleStrandedRetry(failure) {
4824
+ if (this.retryTimer !== void 0 || this.pendingRetry === void 0) {
4825
+ return;
4826
+ }
4827
+ const terminalFailure = asTerminalFailure(failure ?? {
4828
+ kind: "internal_error",
4829
+ message: "Retried turn ended without an assistant message; recovery settled to avoid a hang",
4830
+ retryable: false,
4831
+ compactable: false,
4832
+ userAbort: false,
4833
+ terminal: true
4834
+ });
4835
+ const maxRetries = this.retry?.maxRetries ?? 3;
4836
+ this.emit({ type: "retry_exhausted", errorMessage: terminalFailure.message, maxRetries });
4837
+ this.emit({ type: "turn_failed_terminal", failure: terminalFailure });
4838
+ this.emit({ type: "turn_settled" });
4839
+ this.retryAttempt = 0;
4840
+ this.setRecoveryState("terminal");
4841
+ this.resolvePending();
4842
+ }
4793
4843
  cancel() {
4794
4844
  this.cancelTimerOnly();
4795
4845
  this.retryAttempt = 0;
@@ -4822,6 +4872,8 @@ var AgentSessionRetryController = class {
4822
4872
  // ../../packages/agent-framework/src/session/controller/runtime-guard.ts
4823
4873
  var RUNAWAY_TOOLCALL_WHITESPACE_DELTA_CHARS = 16384;
4824
4874
  var RUNAWAY_TOOLCALL_WHITESPACE_DELTA_EVENTS = 40;
4875
+ var TOOL_FAILURE_ABORT_THRESHOLD = 4;
4876
+ var TOOL_FAILURE_ADVISORY_THRESHOLD = 2;
4825
4877
  var TOOL_FAILURE_SIGNATURE_CHARS = 1e3;
4826
4878
  var TOOL_FAILURE_SIGNATURE_CONTEXT_LINES = 5;
4827
4879
  var FAILURE_DIAGNOSTIC_LINE_PATTERN = /\b(E2E_FAILED|AssertionError|TimeoutError|SyntaxError|ReferenceError|TypeError|Error):|(^|\b)(failed|failure|timed out|timeout|exit code|command failed|npm ERR!)/i;
@@ -4874,10 +4926,12 @@ function isStateChangingSuccess(event) {
4874
4926
  return event.toolName === "bash" && details?.commandClass === "filesystem_write";
4875
4927
  }
4876
4928
  var AgentSessionRuntimeGuard = class {
4877
- constructor(trigger) {
4929
+ constructor(trigger, advise) {
4878
4930
  this.trigger = trigger;
4931
+ this.advise = advise;
4879
4932
  }
4880
4933
  trigger;
4934
+ advise;
4881
4935
  repeatedToolFailures = /* @__PURE__ */ new Map();
4882
4936
  streamGuard = {
4883
4937
  whitespaceToolCallDeltaChars: 0,
@@ -4947,7 +5001,18 @@ var AgentSessionRuntimeGuard = class {
4947
5001
  const key = `${event.toolName}:${reason}:${stableStringify(event.args)}:${resultSummary}`;
4948
5002
  const repeatCount = (this.repeatedToolFailures.get(key) ?? 0) + 1;
4949
5003
  this.repeatedToolFailures.set(key, repeatCount);
4950
- if (repeatCount < 3 || this.isTriggered) {
5004
+ if (this.isTriggered) {
5005
+ return;
5006
+ }
5007
+ if (repeatCount >= TOOL_FAILURE_ADVISORY_THRESHOLD && repeatCount < TOOL_FAILURE_ABORT_THRESHOLD && this.advise) {
5008
+ this.advise(
5009
+ event.toolName,
5010
+ repeatCount,
5011
+ reason,
5012
+ `Tool ${event.toolName} failed ${repeatCount} times in a row with the identical error and identical arguments. Re-read the tool error above: it usually names the exact recovery step or alternative tool. Retrying the same call unchanged will fail again; change the approach.`
5013
+ );
5014
+ }
5015
+ if (repeatCount < TOOL_FAILURE_ABORT_THRESHOLD) {
4951
5016
  return;
4952
5017
  }
4953
5018
  this.isTriggered = true;
@@ -5281,9 +5346,14 @@ var AgentSessionController = class {
5281
5346
  if (options.onEvent) {
5282
5347
  this.listeners.add(options.onEvent);
5283
5348
  }
5284
- this.runtimeGuard = new AgentSessionRuntimeGuard((toolName, repeatCount, reason) => {
5285
- this.triggerRuntimeGuard(toolName, repeatCount, reason);
5286
- });
5349
+ this.runtimeGuard = new AgentSessionRuntimeGuard(
5350
+ (toolName, repeatCount, reason) => {
5351
+ this.triggerRuntimeGuard(toolName, repeatCount, reason);
5352
+ },
5353
+ (toolName, repeatCount, reason, guidance) => {
5354
+ this.emit({ type: "runtime_guard_advisory", toolName, repeatCount, reason, guidance });
5355
+ }
5356
+ );
5287
5357
  this.queue = new AgentSessionQueue({
5288
5358
  agent: this.agent,
5289
5359
  getIsStreaming: () => this.isStreaming,
@@ -5569,7 +5639,16 @@ var AgentSessionController = class {
5569
5639
  if (event.type === "turn_end") {
5570
5640
  void this.syncNewAgentMessages();
5571
5641
  this.recordLifecycle({ type: "turn_end_success" });
5572
- void this.handleTurnEndRuntimePolicies(event.message, event.toolResults);
5642
+ void this.handleTurnEndRuntimePolicies(event.message, event.toolResults).catch((error) => handleTurnEndPolicyThrow((event2) => this.emit(event2), error, {
5643
+ recoveryWasActive: this.isRecoveryActive(),
5644
+ cancelPendingRecovery: () => {
5645
+ this.retryController.cancel();
5646
+ this.autoCompact.cancel();
5647
+ },
5648
+ markRecoveryTerminal: () => {
5649
+ this.recoveryState = "terminal";
5650
+ }
5651
+ }));
5573
5652
  }
5574
5653
  if (event.type === "agent_end") {
5575
5654
  this.recordLifecycle({ type: "agent_end" });
@@ -5789,46 +5868,52 @@ function isExtensionEnabled(identity, ...layers) {
5789
5868
  }
5790
5869
  return true;
5791
5870
  }
5792
- function extractExtensionSettings(parsed) {
5871
+ function extractEnablementMap(parsed, key) {
5793
5872
  if (!parsed || typeof parsed !== "object") {
5794
5873
  return {};
5795
5874
  }
5796
- if (!hasOwn(parsed, "extensions")) {
5875
+ if (!hasOwn(parsed, key)) {
5797
5876
  return {};
5798
5877
  }
5799
- const extensions = parsed.extensions;
5800
- if (!extensions || typeof extensions !== "object" || Array.isArray(extensions)) {
5878
+ const map = parsed[key];
5879
+ if (!map || typeof map !== "object" || Array.isArray(map)) {
5801
5880
  return {};
5802
5881
  }
5803
- return extensions;
5882
+ return map;
5804
5883
  }
5805
- function setExtensionEnabledInSettings(settingsObject, identity, enabled) {
5806
- const extensions = cloneOwnProperties(extractExtensionSettings(settingsObject));
5807
- const current = hasOwn(extensions, identity) ? extensions[identity] : void 0;
5884
+ function extractExtensionSettings(parsed) {
5885
+ return extractEnablementMap(parsed, "extensions");
5886
+ }
5887
+ function setEnablementInMap(settingsObject, key, identity, enabled) {
5888
+ const map = cloneOwnProperties(extractEnablementMap(settingsObject, key));
5889
+ const current = hasOwn(map, identity) ? map[identity] : void 0;
5808
5890
  if (enabled === void 0) {
5809
5891
  if (current && typeof current === "object" && !Array.isArray(current)) {
5810
5892
  const next = cloneOwnProperties(current);
5811
5893
  delete next.enabled;
5812
5894
  if (Object.keys(next).length === 0) {
5813
- delete extensions[identity];
5895
+ delete map[identity];
5814
5896
  } else {
5815
- defineOwnDataProperty(extensions, identity, next);
5897
+ defineOwnDataProperty(map, identity, next);
5816
5898
  }
5817
5899
  } else {
5818
- delete extensions[identity];
5900
+ delete map[identity];
5819
5901
  }
5820
5902
  } else {
5821
5903
  const next = current && typeof current === "object" && !Array.isArray(current) ? cloneOwnProperties(current) : {};
5822
5904
  defineOwnDataProperty(next, "enabled", enabled);
5823
- defineOwnDataProperty(extensions, identity, next);
5905
+ defineOwnDataProperty(map, identity, next);
5824
5906
  }
5825
- if (Object.keys(extensions).length === 0) {
5826
- delete settingsObject.extensions;
5907
+ if (Object.keys(map).length === 0) {
5908
+ delete settingsObject[key];
5827
5909
  } else {
5828
- defineOwnDataProperty(settingsObject, "extensions", extensions);
5910
+ defineOwnDataProperty(settingsObject, key, map);
5829
5911
  }
5830
5912
  return settingsObject;
5831
5913
  }
5914
+ function setExtensionEnabledInSettings(settingsObject, identity, enabled) {
5915
+ return setEnablementInMap(settingsObject, "extensions", identity, enabled);
5916
+ }
5832
5917
 
5833
5918
  // ../../packages/agent-framework/src/interaction.ts
5834
5919
  var InteractionUnavailableError = class extends Error {
@@ -6807,6 +6892,19 @@ function snapshotFromStatAndContent(stat, content) {
6807
6892
  contentHash: hashText(content)
6808
6893
  };
6809
6894
  }
6895
+ function mergeLineRanges(ranges) {
6896
+ const sorted = [...ranges].sort((left, right) => left.startLine - right.startLine || left.endLine - right.endLine);
6897
+ const merged = [];
6898
+ for (const range2 of sorted) {
6899
+ const last = merged[merged.length - 1];
6900
+ if (last && range2.startLine <= last.endLine + 1) {
6901
+ last.endLine = Math.max(last.endLine, range2.endLine);
6902
+ } else {
6903
+ merged.push({ startLine: range2.startLine, endLine: range2.endLine });
6904
+ }
6905
+ }
6906
+ return merged;
6907
+ }
6810
6908
  function createFileObservationState(options = {}) {
6811
6909
  const files = /* @__PURE__ */ new Map();
6812
6910
  const maxFiles = Math.max(1, Math.floor(options.maxFiles ?? 100));
@@ -6833,6 +6931,33 @@ function createFileObservationState(options = {}) {
6833
6931
  readRanges.delete(oldest);
6834
6932
  }
6835
6933
  }
6934
+ function coverageSpansFile(coveredRanges, totalLines) {
6935
+ if (totalLines <= 0) return true;
6936
+ const merged = mergeLineRanges(coveredRanges);
6937
+ return merged.length > 0 && merged[0].startLine <= 1 && merged[merged.length - 1].endLine >= totalLines;
6938
+ }
6939
+ function summarize(observed) {
6940
+ const merged = mergeLineRanges(observed.coveredRanges);
6941
+ const coveredLines = merged.reduce((total, range2) => total + (range2.endLine - range2.startLine + 1), 0);
6942
+ let firstUnseenLine;
6943
+ if (observed.coveredTotalLines > 0 && coveredLines < observed.coveredTotalLines) {
6944
+ firstUnseenLine = merged.length > 0 && merged[0].startLine <= 1 ? merged[merged.length - 1].endLine + 1 : 1;
6945
+ }
6946
+ let lastRange;
6947
+ for (const range2 of observed.readRanges.values()) {
6948
+ if (range2.coverage) {
6949
+ lastRange = range2.coverage;
6950
+ }
6951
+ }
6952
+ return {
6953
+ readCount: observed.readRanges.size,
6954
+ coveredLines,
6955
+ totalLines: observed.coveredTotalLines,
6956
+ completeTextKnown: observed.completeTextKnown,
6957
+ ...lastRange ? { lastRange } : {},
6958
+ ...firstUnseenLine ? { firstUnseenLine } : {}
6959
+ };
6960
+ }
6836
6961
  return {
6837
6962
  isUnchangedReadRange(absolutePath, rangeKey, snapshot, selectedContentHash) {
6838
6963
  const observed = files.get(absolutePath);
@@ -6845,16 +6970,32 @@ function createFileObservationState(options = {}) {
6845
6970
  touchFile(absolutePath, observed);
6846
6971
  return unchanged;
6847
6972
  },
6848
- recordTextRead(absolutePath, rangeKey, snapshot, selectedContentHash, completeTextKnown) {
6849
- const observed = files.get(absolutePath) ?? {
6973
+ recordTextRead(absolutePath, rangeKey, snapshot, selectedContentHash, completeTextKnown, coverage) {
6974
+ const existing = files.get(absolutePath);
6975
+ const contentMatches = !!existing && snapshotsHaveSameContent(existing.snapshot, snapshot);
6976
+ const observed = existing ?? {
6850
6977
  snapshot,
6851
6978
  completeTextKnown: false,
6852
- readRanges: /* @__PURE__ */ new Map()
6979
+ readRanges: /* @__PURE__ */ new Map(),
6980
+ coveredRanges: [],
6981
+ coveredTotalLines: 0
6853
6982
  };
6854
- const preservesFreshCompleteRead = observed.completeTextKnown && snapshotsHaveSameContent(observed.snapshot, snapshot);
6983
+ const preservesFreshCompleteRead = observed.completeTextKnown && contentMatches;
6984
+ if (!contentMatches || coverage && observed.coveredTotalLines !== coverage.totalLines) {
6985
+ observed.coveredRanges = [];
6986
+ observed.coveredTotalLines = coverage?.totalLines ?? observed.coveredTotalLines;
6987
+ }
6855
6988
  observed.snapshot = snapshot;
6856
- observed.completeTextKnown = completeTextKnown || preservesFreshCompleteRead;
6857
- touchRange(observed.readRanges, rangeKey, { snapshot, selectedContentHash });
6989
+ if (coverage) {
6990
+ observed.coveredRanges = mergeLineRanges([...observed.coveredRanges, { startLine: coverage.startLine, endLine: coverage.endLine }]);
6991
+ observed.coveredTotalLines = coverage.totalLines;
6992
+ }
6993
+ observed.completeTextKnown = completeTextKnown || preservesFreshCompleteRead || observed.coveredTotalLines > 0 && coverageSpansFile(observed.coveredRanges, observed.coveredTotalLines);
6994
+ touchRange(observed.readRanges, rangeKey, {
6995
+ snapshot,
6996
+ selectedContentHash,
6997
+ ...coverage ? { coverage: { startLine: coverage.startLine, endLine: coverage.endLine } } : {}
6998
+ });
6858
6999
  touchFile(absolutePath, observed);
6859
7000
  },
6860
7001
  getCompleteTextSnapshot(absolutePath) {
@@ -6865,11 +7006,20 @@ function createFileObservationState(options = {}) {
6865
7006
  touchFile(absolutePath, observed);
6866
7007
  return observed.snapshot;
6867
7008
  },
7009
+ describeObservation(absolutePath) {
7010
+ const observed = files.get(absolutePath);
7011
+ if (!observed) {
7012
+ return void 0;
7013
+ }
7014
+ return summarize(observed);
7015
+ },
6868
7016
  recordCompleteTextWrite(absolutePath, snapshot) {
6869
7017
  touchFile(absolutePath, {
6870
7018
  snapshot,
6871
7019
  completeTextKnown: true,
6872
- readRanges: /* @__PURE__ */ new Map()
7020
+ readRanges: /* @__PURE__ */ new Map(),
7021
+ coveredRanges: [],
7022
+ coveredTotalLines: 0
6873
7023
  });
6874
7024
  },
6875
7025
  invalidate(absolutePath) {
@@ -7137,7 +7287,7 @@ function createReadTool(cwd, options) {
7137
7287
  resolve3({
7138
7288
  content: [{
7139
7289
  type: "text",
7140
- text: "File unchanged since previous read of this range. Use a different offset/limit if you need another section."
7290
+ text: `File unchanged since previous read of this range (offset=${startLineDisplay}${limit === void 0 ? ", no limit" : `, limit=${limit}`}). Earlier reads still count toward write_file coverage of this file. Use a different offset/limit if you need another section.`
7141
7291
  }],
7142
7292
  details: {
7143
7293
  unchangedSincePreviousRead: true,
@@ -7187,7 +7337,15 @@ Base directory: ${nodePath.dirname(absolutePath)}
7187
7337
  rangeKey,
7188
7338
  snapshot,
7189
7339
  range2.selectedContentHash,
7190
- isCompleteTextKnown
7340
+ isCompleteTextKnown,
7341
+ {
7342
+ // firstLineExceedsLimit yields an intentionally empty range
7343
+ // (start - 1): zero lines were actually shown, so it must
7344
+ // contribute nothing to write coverage.
7345
+ startLine: startLineDisplay,
7346
+ endLine: startLineDisplay + (truncation.firstLineExceedsLimit ? 0 : truncation.truncated ? truncation.outputLines : range2.selectedLines) - 1,
7347
+ totalLines: range2.totalLines
7348
+ }
7191
7349
  );
7192
7350
  outputText = sourceHeader + outputText;
7193
7351
  const textContent = [{ type: "text", text: outputText }];
@@ -7548,8 +7706,10 @@ function createWriteTool(cwd, options) {
7548
7706
  const currentSnapshot = snapshotFromStatAndContent(currentStat, currentContent);
7549
7707
  const observedSnapshot = fileObservationState.getCompleteTextSnapshot(absolutePath);
7550
7708
  if (!observedSnapshot) {
7709
+ const summary = fileObservationState.describeObservation(absolutePath);
7710
+ const coverageHint = summary && summary.totalLines > 0 ? ` Previous reads cover ${summary.coveredLines}/${summary.totalLines} lines${summary.lastRange ? ` (last read: lines ${summary.lastRange.startLine}-${summary.lastRange.endLine})` : ""}${summary.firstUnseenLine ? `; read the rest with read_file offset=${summary.firstUnseenLine}` : "; read the full file with read_file (no offset/limit)"}.` : " Read the current full file with read_file (no offset/limit) first.";
7551
7711
  throw new Error(
7552
- usesExternalFileObservationState ? "Read the current full file with read_file before overwriting it, or use edit_file for targeted changes." : "Existing-file overwrite requires write_file to share a fileObservationState with read_file; use createSharedFileSystemTools or pass the same fileObservationState to both tools."
7712
+ usesExternalFileObservationState ? `write_file blocked: overwriting requires the file to be fully observed by read_file.${coverageHint} Alternatively, use edit_file for targeted changes.` : "Existing-file overwrite requires write_file to share a fileObservationState with read_file; use createSharedFileSystemTools or pass the same fileObservationState to both tools."
7553
7713
  );
7554
7714
  }
7555
7715
  if (!snapshotsHaveSameContent(observedSnapshot, currentSnapshot)) {
@@ -14799,6 +14959,7 @@ export {
14799
14959
  getExtensionEnablementEntry,
14800
14960
  isExtensionEnabled,
14801
14961
  extractExtensionSettings,
14962
+ setEnablementInMap,
14802
14963
  setExtensionEnabledInSettings,
14803
14964
  registerInteractionHandler,
14804
14965
  hasInteractionHandler,
@@ -8,7 +8,7 @@ import {
8
8
  createSharedWebSearchTool,
9
9
  interpretShellExit,
10
10
  requestInteraction
11
- } from "./chunk-PKR3PO73.js";
11
+ } from "./chunk-IJMP3JTX.js";
12
12
  import {
13
13
  Type
14
14
  } from "./chunk-K6ZOCJ4V.js";
@@ -90,7 +90,7 @@ function createTestStreamFnFromSequenceEnv(value = process.env.PIE_TEST_MOCK_STR
90
90
  }
91
91
  const steps = parseMockStreamSequence(value);
92
92
  let callIndex = 0;
93
- return async (model, context) => {
93
+ return async (model, context, options) => {
94
94
  const step = steps[Math.min(callIndex, steps.length - 1)];
95
95
  assertSystemPromptIncludes(context.systemPrompt, callIndex);
96
96
  callIndex += 1;
@@ -143,7 +143,14 @@ function createTestStreamFnFromSequenceEnv(value = process.env.PIE_TEST_MOCK_STR
143
143
  stream.push({ type: "done", reason: "stop", message });
144
144
  };
145
145
  if (step?.delayMs) {
146
- setTimeout(emit, step.delayMs);
146
+ const timer = setTimeout(emit, step.delayMs);
147
+ options?.signal?.addEventListener("abort", () => {
148
+ clearTimeout(timer);
149
+ const reason = options.signal?.reason;
150
+ const message = reason instanceof Error ? reason.message : String(reason ?? "aborted");
151
+ const error = createAssistantMessage(model, "", "error", message);
152
+ stream.push({ type: "error", reason: "error", error });
153
+ }, { once: true });
147
154
  } else {
148
155
  queueMicrotask(emit);
149
156
  }