@cydm/pie 1.0.15 → 1.0.17

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.
@@ -1471,36 +1471,66 @@ var DEFAULT_COMPACTION_CONFIG = {
1471
1471
  compactPrompt: COMPACT_PROMPT,
1472
1472
  summaryPrefix: SUMMARY_PREFIX
1473
1473
  };
1474
+ var APPROX_BYTES_PER_TOKEN = 4;
1475
+ var RESIZED_IMAGE_BYTES_ESTIMATE = 7373;
1474
1476
  function estimateTokens(message) {
1475
- if (message.role === "assistant") {
1476
- let chars = 0;
1477
- for (const block of message.content) {
1478
- if (block.type === "text") {
1479
- chars += block.text.length;
1480
- } else if (block.type === "thinking") {
1481
- chars += block.thinking.length;
1482
- } else if (block.type === "toolCall") {
1483
- chars += block.name.length + JSON.stringify(block.arguments).length;
1484
- }
1485
- }
1486
- return Math.ceil(chars / 4);
1477
+ return approximateBytesAsTokens(estimateModelVisibleBytes(message));
1478
+ }
1479
+ function calculateTotalTokens(messages) {
1480
+ return messages.reduce((sum, m) => sum + estimateTokens(m), 0);
1481
+ }
1482
+ function calculateContextTokens(usage) {
1483
+ return usage.totalTokens || usage.input + usage.output + usage.cacheRead + usage.cacheWrite;
1484
+ }
1485
+ function getAssistantUsage(message) {
1486
+ if (message.role !== "assistant") {
1487
+ return void 0;
1487
1488
  }
1488
- if (message.role === "toolResult") {
1489
- let chars = 0;
1490
- for (const block of message.content) {
1491
- if (block.type === "text") {
1492
- chars += block.text.length;
1493
- } else if (block.type === "image") {
1494
- chars += 4800;
1495
- }
1489
+ if (message.stopReason === "aborted" || message.stopReason === "error") {
1490
+ return void 0;
1491
+ }
1492
+ if (calculateContextTokens(message.usage) <= 0) {
1493
+ return void 0;
1494
+ }
1495
+ return message.usage;
1496
+ }
1497
+ function estimateContextTokens(messages) {
1498
+ const localTokens = calculateTotalTokens(messages);
1499
+ for (let i = messages.length - 1; i >= 0; i--) {
1500
+ const usage = getAssistantUsage(messages[i]);
1501
+ if (!usage) {
1502
+ continue;
1496
1503
  }
1497
- return Math.ceil(chars / 4);
1504
+ const providerTokens = calculateContextTokens(usage);
1505
+ const trailingTokens = calculateTotalTokens(messages.slice(i + 1));
1506
+ const usageTokens = providerTokens;
1507
+ return {
1508
+ tokens: Math.max(providerTokens + trailingTokens, localTokens),
1509
+ usageTokens,
1510
+ providerTokens,
1511
+ localTokens,
1512
+ trailingTokens,
1513
+ lastUsageIndex: i
1514
+ };
1498
1515
  }
1499
- const text = getMessageText(message);
1500
- return Math.ceil(text.length / 4);
1516
+ return {
1517
+ tokens: localTokens,
1518
+ usageTokens: 0,
1519
+ providerTokens: 0,
1520
+ localTokens,
1521
+ trailingTokens: localTokens,
1522
+ lastUsageIndex: null
1523
+ };
1501
1524
  }
1502
- function calculateTotalTokens(messages) {
1503
- return messages.reduce((sum, m) => sum + estimateTokens(m), 0);
1525
+ function getAutoCompactTokenLimit(contextWindow, reserveTokens, autoCompactTokenLimit) {
1526
+ const explicitLimit = autoCompactTokenLimit !== void 0 && autoCompactTokenLimit > 0 ? Math.floor(autoCompactTokenLimit) : void 0;
1527
+ if (!contextWindow || contextWindow <= 0) {
1528
+ return explicitLimit;
1529
+ }
1530
+ const reserveLimit = reserveTokens >= contextWindow ? Math.floor(contextWindow * 0.8) : contextWindow - reserveTokens;
1531
+ const windowLimit = Math.floor(contextWindow * 0.9);
1532
+ const defaultLimit = Math.max(1, Math.min(reserveLimit, windowLimit));
1533
+ return explicitLimit !== void 0 ? Math.min(defaultLimit, explicitLimit) : defaultLimit;
1504
1534
  }
1505
1535
  function isCompactSummaryMessage(message, summaryPrefix = SUMMARY_PREFIX) {
1506
1536
  return message.startsWith(`${summaryPrefix}
@@ -1603,10 +1633,17 @@ ${sections.join("\n\n")}` : "";
1603
1633
  }
1604
1634
  async function createCompactionSummary(messages, options = {}) {
1605
1635
  const { model, apiKey, systemPrompt, compactPrompt, signal } = options;
1636
+ const throwIfAborted = () => {
1637
+ if (signal?.aborted) {
1638
+ throw new Error("Compaction aborted");
1639
+ }
1640
+ };
1606
1641
  if (!model) {
1642
+ throwIfAborted();
1607
1643
  return appendFileOperationsToSummary(createSimpleSummary(messages), messages);
1608
1644
  }
1609
1645
  try {
1646
+ throwIfAborted();
1610
1647
  const response = await streamSimple(
1611
1648
  model,
1612
1649
  {
@@ -1625,8 +1662,10 @@ async function createCompactionSummary(messages, options = {}) {
1625
1662
  signal
1626
1663
  }
1627
1664
  ).result();
1665
+ throwIfAborted();
1628
1666
  return appendFileOperationsToSummary(getAssistantText(response) || createSimpleSummary(messages), messages);
1629
1667
  } catch (error) {
1668
+ throwIfAborted();
1630
1669
  console.warn("[Compaction] Failed to create Codex-style summary:", error);
1631
1670
  return appendFileOperationsToSummary(createSimpleSummary(messages), messages);
1632
1671
  }
@@ -1692,6 +1731,138 @@ function appendFileOperationsToSummary(summary, messages) {
1692
1731
  const { readFiles, modifiedFiles } = computeFileLists(fileOps);
1693
1732
  return `${summary}${formatFileOperations(readFiles, modifiedFiles)}`;
1694
1733
  }
1734
+ function estimateModelVisibleBytes(message) {
1735
+ const visibleMessage = normalizeModelVisibleMessage(message);
1736
+ const wrapperBytes = utf8ByteLength(safeStringify(visibleMessage));
1737
+ return wrapperBytes + estimateMessageMediaBytes(message);
1738
+ }
1739
+ function normalizeModelVisibleMessage(message) {
1740
+ if (message.role === "user") {
1741
+ return {
1742
+ role: "user",
1743
+ content: typeof message.content === "string" ? message.content : message.content.map(normalizeUserContentBlock)
1744
+ };
1745
+ }
1746
+ if (message.role === "assistant") {
1747
+ return {
1748
+ role: "assistant",
1749
+ content: message.content.map((block) => {
1750
+ if (block.type === "text") {
1751
+ return {
1752
+ type: "text",
1753
+ text: block.text,
1754
+ ...block.textSignature ? { textSignature: block.textSignature } : {}
1755
+ };
1756
+ }
1757
+ if (block.type === "thinking") {
1758
+ return {
1759
+ type: "thinking",
1760
+ thinking: block.thinking,
1761
+ ...block.thinkingSignature ? { thinkingSignature: block.thinkingSignature } : {}
1762
+ };
1763
+ }
1764
+ if (block.type === "toolCall") {
1765
+ return {
1766
+ type: "toolCall",
1767
+ id: block.id,
1768
+ name: block.name,
1769
+ arguments: block.arguments ?? {},
1770
+ ...block.thoughtSignature ? { thoughtSignature: block.thoughtSignature } : {}
1771
+ };
1772
+ }
1773
+ return block;
1774
+ })
1775
+ };
1776
+ }
1777
+ if (message.role === "toolResult") {
1778
+ return {
1779
+ role: "toolResult",
1780
+ toolCallId: message.toolCallId,
1781
+ toolName: message.toolName,
1782
+ isError: message.isError,
1783
+ content: message.content.map(normalizeUserContentBlock)
1784
+ };
1785
+ }
1786
+ return {
1787
+ role: String(message.role ?? "unknown"),
1788
+ content: getMessageText(message)
1789
+ };
1790
+ }
1791
+ function normalizeUserContentBlock(block) {
1792
+ if (block.type === "text") {
1793
+ return { type: "text", text: block.text };
1794
+ }
1795
+ if (block.type === "image") {
1796
+ return { type: "image", mimeType: block.mimeType };
1797
+ }
1798
+ if (block.type === "audio") {
1799
+ return { type: "audio", mimeType: block.mimeType };
1800
+ }
1801
+ if (block.type === "video") {
1802
+ return { type: "video", mimeType: block.mimeType };
1803
+ }
1804
+ if (block.type === "fileRef") {
1805
+ return {
1806
+ type: "fileRef",
1807
+ url: block.url,
1808
+ mimeType: block.mimeType,
1809
+ modality: block.modality
1810
+ };
1811
+ }
1812
+ return block;
1813
+ }
1814
+ function estimateMessageMediaBytes(message) {
1815
+ if (message.role === "user" && Array.isArray(message.content)) {
1816
+ return message.content.reduce((sum, block) => sum + estimateUserContentBlockMediaBytes(block), 0);
1817
+ }
1818
+ if (message.role === "toolResult") {
1819
+ return message.content.reduce((sum, block) => sum + estimateUserContentBlockMediaBytes(block), 0);
1820
+ }
1821
+ return 0;
1822
+ }
1823
+ function estimateUserContentBlockMediaBytes(block) {
1824
+ if (block.type === "image") {
1825
+ return estimateImageContentBytes(block);
1826
+ }
1827
+ if (block.type === "audio") {
1828
+ return estimateInlineDataBytes(block);
1829
+ }
1830
+ if (block.type === "video") {
1831
+ return estimateInlineDataBytes(block);
1832
+ }
1833
+ return 0;
1834
+ }
1835
+ function estimateImageContentBytes(block) {
1836
+ return Math.max(RESIZED_IMAGE_BYTES_ESTIMATE, estimateInlineDataBytes(block));
1837
+ }
1838
+ function estimateInlineDataBytes(block) {
1839
+ return utf8ByteLength(block.data || "");
1840
+ }
1841
+ function approximateBytesAsTokens(bytes) {
1842
+ return Math.ceil(bytes / APPROX_BYTES_PER_TOKEN);
1843
+ }
1844
+ function safeStringify(value) {
1845
+ try {
1846
+ return JSON.stringify(value) ?? "";
1847
+ } catch {
1848
+ return String(value ?? "");
1849
+ }
1850
+ }
1851
+ var utf8Encoder = typeof globalThis.TextEncoder === "function" ? new globalThis.TextEncoder() : void 0;
1852
+ function utf8ByteLength(text) {
1853
+ if (utf8Encoder) {
1854
+ return utf8Encoder.encode(text).length;
1855
+ }
1856
+ let bytes = 0;
1857
+ for (const char of text) {
1858
+ const codePoint = char.codePointAt(0) ?? 0;
1859
+ if (codePoint <= 127) bytes += 1;
1860
+ else if (codePoint <= 2047) bytes += 2;
1861
+ else if (codePoint <= 65535) bytes += 3;
1862
+ else bytes += 4;
1863
+ }
1864
+ return bytes;
1865
+ }
1695
1866
  function isSyntheticUserEnvelope(text) {
1696
1867
  const trimmed = text.trimStart();
1697
1868
  return trimmed.startsWith("<environment_context>") || trimmed.startsWith("<turn_aborted>") || trimmed.startsWith("# AGENTS.md instructions") || trimmed.startsWith("## Project Context\nProject-specific instructions from AGENTS.md");
@@ -2807,13 +2978,21 @@ var AgentSessionAutoCompactScheduler = class {
2807
2978
  options;
2808
2979
  autoCompactTimer;
2809
2980
  pendingAutoCompact;
2981
+ runningAutoCompact;
2982
+ runningAbortController;
2983
+ pendingWait;
2984
+ resolvePendingWait;
2810
2985
  get hasPendingAutoCompact() {
2811
- return this.autoCompactTimer !== void 0;
2986
+ return this.autoCompactTimer !== void 0 || this.pendingAutoCompact !== void 0 || this.runningAutoCompact !== void 0;
2987
+ }
2988
+ waitForAutoCompact() {
2989
+ return this.pendingWait ?? Promise.resolve();
2812
2990
  }
2813
2991
  schedule(decision, delayMs = 0) {
2814
2992
  if (this.options.getRuntimeGuardTriggered()) {
2815
2993
  return;
2816
2994
  }
2995
+ this.ensurePendingWait();
2817
2996
  this.pendingAutoCompact = decision;
2818
2997
  if (this.autoCompactTimer !== void 0) {
2819
2998
  return;
@@ -2824,6 +3003,7 @@ var AgentSessionAutoCompactScheduler = class {
2824
3003
  const pending = this.pendingAutoCompact;
2825
3004
  this.pendingAutoCompact = void 0;
2826
3005
  if (!pending || this.options.getDisposed()) {
3006
+ this.resolvePending();
2827
3007
  return;
2828
3008
  }
2829
3009
  if (this.options.isStreaming()) {
@@ -2831,17 +3011,42 @@ var AgentSessionAutoCompactScheduler = class {
2831
3011
  this.schedule(pending, 100);
2832
3012
  return;
2833
3013
  }
2834
- void this.options.runAutoCompact(pending);
3014
+ const abortController = new AbortController();
3015
+ this.runningAbortController = abortController;
3016
+ const running = this.options.runAutoCompact(pending, abortController.signal).catch(() => {
3017
+ }).finally(() => {
3018
+ if (this.runningAutoCompact === running) {
3019
+ this.runningAutoCompact = void 0;
3020
+ this.runningAbortController = void 0;
3021
+ this.resolvePending();
3022
+ }
3023
+ });
3024
+ this.runningAutoCompact = running;
2835
3025
  }, delayMs);
2836
3026
  }
2837
3027
  cancel() {
2838
- if (this.autoCompactTimer === void 0) {
2839
- return;
3028
+ if (this.autoCompactTimer !== void 0) {
3029
+ const clearTimer = this.options.retry?.clearTimeout ?? defaultClearTimeout;
3030
+ clearTimer(this.autoCompactTimer);
3031
+ this.autoCompactTimer = void 0;
2840
3032
  }
2841
- const clearTimer = this.options.retry?.clearTimeout ?? defaultClearTimeout;
2842
- clearTimer(this.autoCompactTimer);
2843
- this.autoCompactTimer = void 0;
2844
3033
  this.pendingAutoCompact = void 0;
3034
+ this.runningAbortController?.abort();
3035
+ this.runningAbortController = void 0;
3036
+ this.runningAutoCompact = void 0;
3037
+ this.resolvePending();
3038
+ }
3039
+ ensurePendingWait() {
3040
+ if (this.pendingWait) return;
3041
+ this.pendingWait = new Promise((resolve2) => {
3042
+ this.resolvePendingWait = resolve2;
3043
+ });
3044
+ }
3045
+ resolvePending() {
3046
+ const resolve2 = this.resolvePendingWait;
3047
+ this.pendingWait = void 0;
3048
+ this.resolvePendingWait = void 0;
3049
+ resolve2?.();
2845
3050
  }
2846
3051
  };
2847
3052
 
@@ -2858,15 +3063,24 @@ var AgentSessionAutoContinueController = class {
2858
3063
  }
2859
3064
  options;
2860
3065
  autoContinueTimer;
3066
+ pendingAutoContinue;
3067
+ resolvePendingAutoContinue;
3068
+ get hasPendingAutoContinue() {
3069
+ return this.autoContinueTimer !== void 0 || this.pendingAutoContinue !== void 0;
3070
+ }
3071
+ waitForAutoContinue() {
3072
+ return this.pendingAutoContinue ?? Promise.resolve();
3073
+ }
2861
3074
  schedule(messages) {
2862
3075
  if (!this.options.getAutoContinueMessage || this.options.getDisposed() || this.autoContinueTimer !== void 0) {
2863
3076
  return;
2864
3077
  }
3078
+ this.ensurePending();
2865
3079
  const setTimer = this.options.retry?.setTimeout ?? defaultSetTimeout2;
2866
3080
  const snapshot = messages.slice();
2867
3081
  this.autoContinueTimer = setTimer(() => {
2868
3082
  this.autoContinueTimer = void 0;
2869
- void this.maybeScheduleAutoContinue(snapshot);
3083
+ void this.maybeScheduleAutoContinue(snapshot).finally(() => this.resolvePending());
2870
3084
  }, 0);
2871
3085
  }
2872
3086
  cancel() {
@@ -2876,9 +3090,10 @@ var AgentSessionAutoContinueController = class {
2876
3090
  const clearTimer = this.options.retry?.clearTimeout ?? defaultClearTimeout2;
2877
3091
  clearTimer(this.autoContinueTimer);
2878
3092
  this.autoContinueTimer = void 0;
3093
+ this.resolvePending();
2879
3094
  }
2880
3095
  async maybeScheduleAutoContinue(messages) {
2881
- if (!this.options.getAutoContinueMessage || this.options.getDisposed() || this.options.getRuntimeGuardTriggered() || this.options.hasQueuedMessages() || this.options.isStreaming()) {
3096
+ if (!this.options.getAutoContinueMessage || this.options.getDisposed() || this.options.getRuntimeGuardTriggered() || this.options.isBlockedByRuntimePolicy() || this.options.hasQueuedMessages() || this.options.isStreaming()) {
2882
3097
  if (this.options.hasQueuedMessages()) {
2883
3098
  this.options.emit({ type: "auto_continue_skipped", reason: "queued_messages" });
2884
3099
  }
@@ -2902,21 +3117,36 @@ var AgentSessionAutoContinueController = class {
2902
3117
  this.options.emit({ type: "auto_continue_skipped", reason: reason ?? "no_message" });
2903
3118
  return;
2904
3119
  }
2905
- if (this.options.hasQueuedMessages() || this.options.isStreaming()) {
3120
+ if (this.options.hasQueuedMessages() || this.options.isStreaming() || this.options.isBlockedByRuntimePolicy()) {
2906
3121
  this.options.emit({ type: "auto_continue_skipped", reason: "runtime_busy" });
2907
3122
  return;
2908
3123
  }
2909
3124
  this.options.emit({ type: "auto_continue_scheduled", reason });
2910
3125
  this.options.agent.followUp(message);
2911
3126
  const setTimer = this.options.retry?.setTimeout ?? defaultSetTimeout2;
2912
- this.autoContinueTimer = setTimer(() => {
2913
- this.autoContinueTimer = void 0;
2914
- if (this.options.getDisposed()) {
2915
- return;
2916
- }
2917
- void this.options.continueTurn().catch(() => {
2918
- });
2919
- }, 0);
3127
+ await new Promise((resolve2) => {
3128
+ this.autoContinueTimer = setTimer(() => {
3129
+ this.autoContinueTimer = void 0;
3130
+ if (this.options.getDisposed()) {
3131
+ resolve2();
3132
+ return;
3133
+ }
3134
+ void this.options.continueTurn().catch(() => {
3135
+ }).finally(resolve2);
3136
+ }, 0);
3137
+ });
3138
+ }
3139
+ ensurePending() {
3140
+ if (this.pendingAutoContinue) return;
3141
+ this.pendingAutoContinue = new Promise((resolve2) => {
3142
+ this.resolvePendingAutoContinue = resolve2;
3143
+ });
3144
+ }
3145
+ resolvePending() {
3146
+ const resolve2 = this.resolvePendingAutoContinue;
3147
+ this.pendingAutoContinue = void 0;
3148
+ this.resolvePendingAutoContinue = void 0;
3149
+ resolve2?.();
2920
3150
  }
2921
3151
  };
2922
3152
 
@@ -2932,9 +3162,16 @@ async function runAgentSessionCompaction(params) {
2932
3162
  if (!params.compaction) {
2933
3163
  throw new Error("No compaction handler configured");
2934
3164
  }
3165
+ const abortedResult = () => ({ aborted: true });
3166
+ const isAborted = () => params.signal?.aborted === true;
2935
3167
  params.emit({ type: "auto_compaction_start", reason: params.reason, willRetry: params.willRetry });
2936
3168
  try {
2937
- if (params.reason === "overflow") {
3169
+ if (isAborted()) {
3170
+ const result2 = abortedResult();
3171
+ params.emit({ type: "auto_compaction_end", reason: params.reason, willRetry: params.willRetry, result: result2 });
3172
+ return result2;
3173
+ }
3174
+ if (params.willRetry) {
2938
3175
  params.removeTrailingAssistantError();
2939
3176
  }
2940
3177
  const result = await params.compaction.compact({
@@ -2943,22 +3180,43 @@ async function runAgentSessionCompaction(params) {
2943
3180
  agent: params.agent,
2944
3181
  sessionManager: params.sessionManager,
2945
3182
  messages: params.agent.state.messages.slice(),
2946
- cwd: params.cwd
3183
+ cwd: params.cwd,
3184
+ signal: params.signal
2947
3185
  });
3186
+ if (isAborted() || result?.aborted) {
3187
+ const aborted = { ...result, aborted: true };
3188
+ params.emit({ type: "auto_compaction_end", reason: params.reason, willRetry: params.willRetry, result: aborted });
3189
+ return aborted;
3190
+ }
2948
3191
  if (result?.summary && result.firstKeptEntryId) {
2949
3192
  params.sessionManager.appendCompaction(result.summary, result.firstKeptEntryId, result.tokensBefore);
2950
3193
  params.replaceMessagesAndMarkSynced(params.sessionManager.getMessages());
2951
3194
  } else if (result?.messages) {
2952
3195
  params.replaceMessagesAndMarkSynced(result.messages, { updateSession: true });
2953
3196
  }
3197
+ if (isAborted()) {
3198
+ const aborted = { ...result, aborted: true };
3199
+ params.emit({ type: "auto_compaction_end", reason: params.reason, willRetry: params.willRetry, result: aborted });
3200
+ return aborted;
3201
+ }
2954
3202
  params.markSyncedToAgentState();
2955
3203
  await params.sessionManager.save();
3204
+ if (isAborted()) {
3205
+ const aborted = { ...result, aborted: true };
3206
+ params.emit({ type: "auto_compaction_end", reason: params.reason, willRetry: params.willRetry, result: aborted });
3207
+ return aborted;
3208
+ }
2956
3209
  if (params.willRetry && !result?.aborted) {
2957
3210
  await params.continueTurn();
2958
3211
  }
2959
3212
  params.emit({ type: "auto_compaction_end", reason: params.reason, willRetry: params.willRetry, result });
2960
3213
  return result;
2961
3214
  } catch (error) {
3215
+ if (isAborted()) {
3216
+ const result = abortedResult();
3217
+ params.emit({ type: "auto_compaction_end", reason: params.reason, willRetry: params.willRetry, result });
3218
+ return result;
3219
+ }
2962
3220
  const errorMessage = error instanceof Error ? error.message : String(error);
2963
3221
  params.emit({ type: "auto_compaction_end", reason: params.reason, willRetry: params.willRetry, errorMessage });
2964
3222
  throw error;
@@ -3028,6 +3286,27 @@ async function preparePromptMessages(params) {
3028
3286
  };
3029
3287
  }
3030
3288
 
3289
+ // ../../packages/agent-framework/src/session/controller/pre-request-policy.ts
3290
+ async function runPreRequestAutoCompact(params) {
3291
+ if (params.disposed || params.runtimeGuardTriggered || params.isStreaming || params.recoveryActive || params.agent.state.messages.length === 0) {
3292
+ return;
3293
+ }
3294
+ const decision = params.getAutoCompactDecision?.({
3295
+ agent: params.agent,
3296
+ sessionManager: params.sessionManager,
3297
+ cwd: params.cwd,
3298
+ messages: params.agent.state.messages.slice()
3299
+ });
3300
+ if (decision?.reason !== "threshold") {
3301
+ return;
3302
+ }
3303
+ try {
3304
+ await params.runAutoCompact();
3305
+ } catch {
3306
+ params.setRecoveryState("none");
3307
+ }
3308
+ }
3309
+
3031
3310
  // ../../packages/agent-framework/src/session/controller/queue.ts
3032
3311
  function cloneQueuedInput(input) {
3033
3312
  return {
@@ -3186,14 +3465,23 @@ var AgentSessionQueueDispatcher = class {
3186
3465
  }
3187
3466
  options;
3188
3467
  dispatchTimer;
3468
+ pendingDispatch;
3469
+ resolvePendingDispatch;
3470
+ get hasPendingDispatch() {
3471
+ return this.dispatchTimer !== void 0 || this.pendingDispatch !== void 0;
3472
+ }
3473
+ waitForDispatch() {
3474
+ return this.pendingDispatch ?? Promise.resolve();
3475
+ }
3189
3476
  schedule(delayMs = 0) {
3190
3477
  if (this.options.getDisposed() || this.options.getRuntimeGuardTriggered() || this.dispatchTimer !== void 0) {
3191
3478
  return;
3192
3479
  }
3480
+ this.ensurePending();
3193
3481
  const setTimer = this.options.retry?.setTimeout ?? defaultSetTimeout3;
3194
3482
  this.dispatchTimer = setTimer(() => {
3195
3483
  this.dispatchTimer = void 0;
3196
- void this.dispatchNext();
3484
+ void this.dispatchNext().finally(() => this.resolvePending());
3197
3485
  }, delayMs);
3198
3486
  }
3199
3487
  cancel() {
@@ -3203,6 +3491,7 @@ var AgentSessionQueueDispatcher = class {
3203
3491
  const clearTimer = this.options.retry?.clearTimeout ?? defaultClearTimeout3;
3204
3492
  clearTimer(this.dispatchTimer);
3205
3493
  this.dispatchTimer = void 0;
3494
+ this.resolvePending();
3206
3495
  }
3207
3496
  async dispatchNext() {
3208
3497
  if (this.options.getDisposed() || this.options.isStreaming() || this.options.isBlockedByRuntimePolicy()) {
@@ -3230,6 +3519,11 @@ var AgentSessionQueueDispatcher = class {
3230
3519
  }
3231
3520
  return false;
3232
3521
  }
3522
+ await this.options.runPreRequestAutoCompact();
3523
+ if (this.options.getDisposed() || this.options.isStreaming() || this.options.isBlockedByRuntimePolicy()) {
3524
+ this.schedule(100);
3525
+ return false;
3526
+ }
3233
3527
  const dispatched = this.options.queue.shift();
3234
3528
  if (!dispatched) {
3235
3529
  return false;
@@ -3259,8 +3553,56 @@ var AgentSessionQueueDispatcher = class {
3259
3553
  }
3260
3554
  return true;
3261
3555
  }
3556
+ ensurePending() {
3557
+ if (this.pendingDispatch) return;
3558
+ this.pendingDispatch = new Promise((resolve2) => {
3559
+ this.resolvePendingDispatch = resolve2;
3560
+ });
3561
+ }
3562
+ resolvePending() {
3563
+ const resolve2 = this.resolvePendingDispatch;
3564
+ this.pendingDispatch = void 0;
3565
+ this.resolvePendingDispatch = void 0;
3566
+ resolve2?.();
3567
+ }
3262
3568
  };
3263
3569
 
3570
+ // ../../packages/agent-framework/src/session/controller/failure-classification.ts
3571
+ function isAssistantFailureMessage(message) {
3572
+ return message?.role === "assistant" && (message.stopReason === "error" || message.stopReason === "aborted");
3573
+ }
3574
+ function isAssistantTerminalCompletion(message) {
3575
+ if (message?.role !== "assistant") return false;
3576
+ if (message.stopReason === "error" || message.stopReason === "aborted") return false;
3577
+ if (message.stopReason === "toolUse") return false;
3578
+ return !message.content.some((block) => block.type === "toolCall");
3579
+ }
3580
+ function createFailureClassification(context, retry, classifyFailure) {
3581
+ const hostClassification = classifyFailure?.(context);
3582
+ if (hostClassification) {
3583
+ return hostClassification;
3584
+ }
3585
+ const retryable = retry?.isRetryableError(context.errorMessage) === true;
3586
+ const userAbort = context.message?.role === "assistant" && context.message.stopReason === "aborted";
3587
+ return {
3588
+ kind: userAbort ? "user_abort" : retryable ? "provider_transient" : "unknown",
3589
+ retryable: retryable && !userAbort,
3590
+ compactable: false,
3591
+ userAbort,
3592
+ terminal: !retryable || userAbort,
3593
+ message: context.errorMessage
3594
+ };
3595
+ }
3596
+ function asTerminalFailure(failure, message = failure.message) {
3597
+ return {
3598
+ ...failure,
3599
+ retryable: false,
3600
+ compactable: false,
3601
+ terminal: true,
3602
+ message
3603
+ };
3604
+ }
3605
+
3264
3606
  // ../../packages/agent-framework/src/session/controller/retry-policy.ts
3265
3607
  function defaultSetTimeout4(callback, delay) {
3266
3608
  return setTimeout(callback, delay);
@@ -3269,42 +3611,49 @@ function defaultClearTimeout4(handle) {
3269
3611
  clearTimeout(handle);
3270
3612
  }
3271
3613
  var AgentSessionRetryController = class {
3272
- constructor(retry, emit, removeTrailingAssistantError2, continueTurn) {
3614
+ constructor(retry, emit, removeTrailingAssistantError2, continueTurn, setRecoveryState) {
3273
3615
  this.retry = retry;
3274
3616
  this.emit = emit;
3275
3617
  this.removeTrailingAssistantError = removeTrailingAssistantError2;
3276
3618
  this.continueTurn = continueTurn;
3619
+ this.setRecoveryState = setRecoveryState;
3277
3620
  }
3278
3621
  retry;
3279
3622
  emit;
3280
3623
  removeTrailingAssistantError;
3281
3624
  continueTurn;
3625
+ setRecoveryState;
3282
3626
  retryAttempt = 0;
3283
3627
  retryTimer;
3284
3628
  pendingRetry;
3285
3629
  resolvePendingRetry;
3630
+ get policy() {
3631
+ return this.retry;
3632
+ }
3286
3633
  get hasPendingRetry() {
3287
3634
  return this.retryTimer !== void 0 || this.pendingRetry !== void 0;
3288
3635
  }
3289
3636
  waitForRetry() {
3290
3637
  return this.pendingRetry ?? Promise.resolve();
3291
3638
  }
3292
- async handleTurnEnd(message) {
3639
+ async handleTurnEnd(message, failure) {
3293
3640
  if (!this.retry || !message || message.role !== "assistant") {
3294
3641
  return;
3295
3642
  }
3296
- const errorMessage = message.stopReason === "error" ? message.errorMessage : void 0;
3297
- if (!errorMessage) {
3643
+ if (!failure) {
3298
3644
  if (this.retryAttempt > 0) {
3299
3645
  this.emit({ type: "retry_succeeded", attempts: this.retryAttempt });
3646
+ this.emit({ type: "turn_retry_succeeded", attempts: this.retryAttempt });
3300
3647
  this.retryAttempt = 0;
3648
+ this.setRecoveryState("none");
3301
3649
  this.resolvePending();
3302
3650
  }
3303
3651
  return;
3304
3652
  }
3305
- if (!this.retry.isRetryableError(errorMessage)) {
3653
+ if (!failure.retryable) {
3306
3654
  if (this.retryAttempt > 0) {
3307
3655
  this.retryAttempt = 0;
3656
+ this.setRecoveryState("none");
3308
3657
  this.resolvePending();
3309
3658
  }
3310
3659
  return;
@@ -3312,28 +3661,41 @@ var AgentSessionRetryController = class {
3312
3661
  const maxRetries = this.retry.maxRetries ?? 3;
3313
3662
  this.retryAttempt += 1;
3314
3663
  if (this.retryAttempt > maxRetries) {
3315
- this.emit({ type: "retry_exhausted", errorMessage, maxRetries });
3664
+ const terminalFailure = asTerminalFailure(failure);
3665
+ this.emit({ type: "retry_exhausted", errorMessage: failure.message, maxRetries });
3666
+ this.emit({ type: "turn_failed_terminal", failure: terminalFailure, message });
3667
+ this.emit({ type: "turn_settled" });
3316
3668
  this.retryAttempt = 0;
3669
+ this.setRecoveryState("terminal");
3317
3670
  this.resolvePending();
3318
3671
  return;
3319
3672
  }
3320
3673
  const attempt = this.retryAttempt;
3321
3674
  const delayMs = (this.retry.baseDelayMs ?? 2e3) * 2 ** (attempt - 1);
3322
3675
  this.ensurePending();
3323
- this.emit({ type: "retry_scheduled", errorMessage, attempt, maxRetries, delayMs });
3676
+ this.setRecoveryState("retry_pending");
3677
+ this.emit({ type: "turn_recovery_pending", recovery: "retry", failure, attempt, maxRetries, delayMs });
3678
+ this.emit({ type: "retry_scheduled", errorMessage: failure.message, attempt, maxRetries, delayMs });
3324
3679
  this.cancelTimerOnly();
3325
3680
  const setTimer = this.retry.setTimeout ?? defaultSetTimeout4;
3326
3681
  this.retryTimer = setTimer(() => {
3327
3682
  this.retryTimer = void 0;
3683
+ this.setRecoveryState("retrying");
3328
3684
  this.emit({ type: "retry_start", attempt, maxRetries });
3685
+ this.emit({ type: "turn_retry_started", attempt, maxRetries, failure });
3329
3686
  this.removeTrailingAssistantError();
3330
3687
  void this.continueTurn().catch((error) => {
3688
+ const errorMessage = error instanceof Error ? error.message : String(error);
3689
+ const terminalFailure = asTerminalFailure(failure, errorMessage);
3331
3690
  this.emit({
3332
3691
  type: "retry_exhausted",
3333
- errorMessage: error instanceof Error ? error.message : String(error),
3692
+ errorMessage,
3334
3693
  maxRetries
3335
3694
  });
3695
+ this.emit({ type: "turn_failed_terminal", failure: terminalFailure, message });
3696
+ this.emit({ type: "turn_settled" });
3336
3697
  this.retryAttempt = 0;
3698
+ this.setRecoveryState("terminal");
3337
3699
  this.resolvePending();
3338
3700
  });
3339
3701
  }, delayMs);
@@ -3370,6 +3732,9 @@ var AgentSessionRetryController = class {
3370
3732
  // ../../packages/agent-framework/src/session/controller/runtime-guard.ts
3371
3733
  var RUNAWAY_TOOLCALL_WHITESPACE_DELTA_CHARS = 16384;
3372
3734
  var RUNAWAY_TOOLCALL_WHITESPACE_DELTA_EVENTS = 40;
3735
+ var TOOL_FAILURE_SIGNATURE_CHARS = 1e3;
3736
+ var TOOL_FAILURE_SIGNATURE_CONTEXT_LINES = 5;
3737
+ 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;
3373
3738
  function stableStringify(value) {
3374
3739
  if (value === null || typeof value !== "object") {
3375
3740
  return JSON.stringify(value) ?? String(value);
@@ -3388,6 +3753,20 @@ function summarizeToolResultForGuard(result) {
3388
3753
  const text = content.filter((block) => !!block && typeof block === "object" && "type" in block).filter((block) => block.type === "text").map((block) => block.text || "").join("\n").trim();
3389
3754
  return text || stableStringify(result);
3390
3755
  }
3756
+ function summarizeToolFailureSignature(result) {
3757
+ const summary = summarizeToolResultForGuard(result).replace(/\r\n/g, "\n").replace(/\r/g, "\n").trim();
3758
+ if (!summary) {
3759
+ return "";
3760
+ }
3761
+ const lines = summary.split("\n");
3762
+ for (let index = lines.length - 1; index >= 0; index--) {
3763
+ if (!FAILURE_DIAGNOSTIC_LINE_PATTERN.test(lines[index] ?? "")) {
3764
+ continue;
3765
+ }
3766
+ return lines.slice(index, Math.min(lines.length, index + TOOL_FAILURE_SIGNATURE_CONTEXT_LINES)).join("\n").slice(0, TOOL_FAILURE_SIGNATURE_CHARS);
3767
+ }
3768
+ return summary.slice(-TOOL_FAILURE_SIGNATURE_CHARS);
3769
+ }
3391
3770
  var AgentSessionRuntimeGuard = class {
3392
3771
  constructor(trigger) {
3393
3772
  this.trigger = trigger;
@@ -3452,7 +3831,7 @@ var AgentSessionRuntimeGuard = class {
3452
3831
  }
3453
3832
  return;
3454
3833
  }
3455
- const resultSummary = summarizeToolResultForGuard(event.result).slice(0, 500);
3834
+ const resultSummary = summarizeToolFailureSignature(event.result);
3456
3835
  const key = `${event.toolName}:${stableStringify(event.args)}:${resultSummary}`;
3457
3836
  const repeatCount = (this.repeatedToolFailures.get(key) ?? 0) + 1;
3458
3837
  this.repeatedToolFailures.set(key, repeatCount);
@@ -3464,6 +3843,33 @@ var AgentSessionRuntimeGuard = class {
3464
3843
  }
3465
3844
  };
3466
3845
 
3846
+ // ../../packages/agent-framework/src/session/controller/runtime-phase-policy.ts
3847
+ function classifyControllerRuntimeError(params) {
3848
+ const errorMessage = params.error instanceof Error ? params.error.message : String(params.error);
3849
+ return createFailureClassification({ errorMessage, phase: params.phase }, params.retry, params.classifyFailure);
3850
+ }
3851
+ async function runAgentSessionRuntimePhase(params) {
3852
+ try {
3853
+ return await params.action();
3854
+ } catch (error) {
3855
+ const failure = classifyControllerRuntimeError({
3856
+ error,
3857
+ phase: params.phase,
3858
+ retry: params.retry,
3859
+ classifyFailure: params.classifyFailure
3860
+ });
3861
+ if (failure.userAbort) {
3862
+ params.setRecoveryState("cancelled");
3863
+ params.emit({ type: "turn_cancelled", reason: failure.message });
3864
+ } else {
3865
+ params.setRecoveryState("terminal");
3866
+ params.emit({ type: "turn_failed_terminal", failure: asTerminalFailure(failure) });
3867
+ }
3868
+ params.emit({ type: "turn_settled" });
3869
+ throw error;
3870
+ }
3871
+ }
3872
+
3467
3873
  // ../../packages/agent-framework/src/session/controller/session-sync.ts
3468
3874
  function getConversationMessages(messages) {
3469
3875
  return messages.filter(
@@ -3568,6 +3974,163 @@ function reduceAgentSessionLifecycle(state, input) {
3568
3974
  }
3569
3975
  }
3570
3976
 
3977
+ // ../../packages/agent-framework/src/session/controller/turn-lifecycle-policy.ts
3978
+ function isAgentSessionRecoveryActive(state) {
3979
+ return state === "retry_pending" || state === "retrying" || state === "compacting";
3980
+ }
3981
+ function createRuntimeGuardFailure(reason) {
3982
+ return {
3983
+ kind: "runtime_guard",
3984
+ retryable: false,
3985
+ compactable: false,
3986
+ userAbort: false,
3987
+ terminal: true,
3988
+ message: reason
3989
+ };
3990
+ }
3991
+ function classifyControllerTurnFailure(params) {
3992
+ if (!isAssistantFailureMessage(params.message)) {
3993
+ return void 0;
3994
+ }
3995
+ const errorMessage = params.message.errorMessage || (params.message.stopReason === "aborted" ? "Request was aborted" : "assistant response failed");
3996
+ return createFailureClassification(
3997
+ { errorMessage, message: params.message, phase: "stream" },
3998
+ params.retry,
3999
+ params.classifyFailure
4000
+ );
4001
+ }
4002
+ async function handleAgentSessionTurnEndRuntimePolicies(params) {
4003
+ if (params.disposed || params.runtimeGuardTriggered) {
4004
+ return;
4005
+ }
4006
+ const failure = classifyControllerTurnFailure({
4007
+ message: params.message,
4008
+ retry: params.retry,
4009
+ classifyFailure: params.classifyFailure
4010
+ });
4011
+ if (failure?.userAbort) {
4012
+ params.cancelRetry();
4013
+ params.setRecoveryState("cancelled");
4014
+ params.emit({ type: "turn_cancelled", reason: failure.message, message: params.message });
4015
+ params.emit({ type: "turn_settled" });
4016
+ return;
4017
+ }
4018
+ const compactDecision = params.getAutoCompactDecision?.({
4019
+ agent: params.agent,
4020
+ sessionManager: params.sessionManager,
4021
+ cwd: params.cwd,
4022
+ message: params.message,
4023
+ messages: params.agent.state.messages.slice()
4024
+ });
4025
+ if (failure?.compactable) {
4026
+ params.scheduleAutoCompact(compactDecision ?? { reason: "overflow", willRetry: true }, failure);
4027
+ return;
4028
+ }
4029
+ if (failure && compactDecision?.reason === "overflow") {
4030
+ params.scheduleAutoCompact(compactDecision, failure);
4031
+ return;
4032
+ }
4033
+ if (failure?.retryable && compactDecision?.reason === "threshold") {
4034
+ params.scheduleAutoCompact({ reason: "threshold", willRetry: true }, failure);
4035
+ return;
4036
+ }
4037
+ if (failure) {
4038
+ if (failure.retryable) {
4039
+ await params.handleRetryTurnEnd(params.message, failure);
4040
+ return;
4041
+ }
4042
+ await params.handleRetryTurnEnd(params.message, failure);
4043
+ params.setRecoveryState("terminal");
4044
+ params.emit({ type: "turn_failed_terminal", failure: asTerminalFailure(failure), message: params.message });
4045
+ params.emit({ type: "turn_settled" });
4046
+ return;
4047
+ }
4048
+ await params.handleRetryTurnEnd(params.message);
4049
+ if (isAssistantTerminalCompletion(params.message)) {
4050
+ params.emit({ type: "turn_completed", message: params.message, toolResults: params.toolResults });
4051
+ const postTurnCompactDecision = compactDecision ?? params.getAutoCompactDecision?.({
4052
+ agent: params.agent,
4053
+ sessionManager: params.sessionManager,
4054
+ cwd: params.cwd,
4055
+ message: params.message,
4056
+ messages: params.agent.state.messages.slice()
4057
+ });
4058
+ if (postTurnCompactDecision?.reason === "threshold") {
4059
+ params.scheduleAutoCompact(postTurnCompactDecision);
4060
+ return;
4061
+ }
4062
+ params.setRecoveryState("none");
4063
+ params.emit({ type: "turn_settled" });
4064
+ }
4065
+ }
4066
+ async function runAgentSessionControllerAutoCompact(params) {
4067
+ const emitSettled = params.emitSettled ?? true;
4068
+ const runAfterAgentEndHook = params.runAfterAgentEndHook ?? true;
4069
+ const cancelOnAbort = params.cancelOnAbort ?? true;
4070
+ params.setRecoveryState("compacting");
4071
+ params.emit({ type: "turn_compaction_started", reason: params.reason, willRetry: params.willRetry });
4072
+ try {
4073
+ const result = await runAgentSessionCompaction({
4074
+ reason: params.reason,
4075
+ willRetry: params.willRetry,
4076
+ signal: params.signal,
4077
+ compaction: params.compaction,
4078
+ agent: params.agent,
4079
+ sessionManager: params.sessionManager,
4080
+ cwd: params.cwd,
4081
+ emit: params.emit,
4082
+ removeTrailingAssistantError: params.removeTrailingAssistantError,
4083
+ replaceMessagesAndMarkSynced: params.replaceMessagesAndMarkSynced,
4084
+ markSyncedToAgentState: params.markSyncedToAgentState,
4085
+ continueTurn: params.continueTurn
4086
+ });
4087
+ if (!params.willRetry || result?.aborted) {
4088
+ const alreadyCancelled = params.getRecoveryState?.() === "cancelled" || params.getDisposed?.() === true;
4089
+ params.setRecoveryState(result?.aborted && cancelOnAbort ? "cancelled" : "none");
4090
+ if (result?.aborted) {
4091
+ params.cancelRetry();
4092
+ if (cancelOnAbort && !alreadyCancelled) {
4093
+ params.emit({ type: "turn_cancelled", reason: "compaction_aborted" });
4094
+ }
4095
+ }
4096
+ if (emitSettled && !alreadyCancelled) {
4097
+ params.emit({ type: "turn_settled" });
4098
+ }
4099
+ if (!result?.aborted && runAfterAgentEndHook) {
4100
+ void params.handleAgentEndHook(params.agent.state.messages.slice());
4101
+ }
4102
+ }
4103
+ return result;
4104
+ } catch (error) {
4105
+ const errorMessage = error instanceof Error ? error.message : String(error);
4106
+ if (!params.willRetry && params.reason !== "overflow") {
4107
+ params.setRecoveryState("none");
4108
+ if (emitSettled) {
4109
+ params.emit({ type: "turn_settled" });
4110
+ }
4111
+ if (runAfterAgentEndHook) {
4112
+ void params.handleAgentEndHook(params.agent.state.messages.slice());
4113
+ }
4114
+ throw error;
4115
+ }
4116
+ const failure = asTerminalFailure(params.pendingFailure ?? {
4117
+ kind: "internal_error",
4118
+ retryable: false,
4119
+ compactable: false,
4120
+ userAbort: false,
4121
+ terminal: true,
4122
+ message: errorMessage
4123
+ }, errorMessage);
4124
+ params.cancelRetry();
4125
+ params.setRecoveryState("terminal");
4126
+ params.emit({ type: "turn_failed_terminal", failure });
4127
+ params.emit({ type: "turn_settled" });
4128
+ throw error;
4129
+ } finally {
4130
+ params.clearPendingFailure();
4131
+ }
4132
+ }
4133
+
3571
4134
  // ../../packages/agent-framework/src/session/controller.ts
3572
4135
  var AgentSessionController = class {
3573
4136
  agent;
@@ -3589,6 +4152,9 @@ var AgentSessionController = class {
3589
4152
  prepareTurn;
3590
4153
  afterAgentEnd;
3591
4154
  getAutoCompactDecision;
4155
+ classifyFailure;
4156
+ recoveryState = "none";
4157
+ pendingCompactionFailure;
3592
4158
  lifecycleState = createInitialAgentSessionLifecycleState();
3593
4159
  constructor(options) {
3594
4160
  this.agent = options.agent;
@@ -3599,6 +4165,7 @@ var AgentSessionController = class {
3599
4165
  this.prepareTurn = options.prepareTurn;
3600
4166
  this.afterAgentEnd = options.afterAgentEnd;
3601
4167
  this.getAutoCompactDecision = options.getAutoCompactDecision;
4168
+ this.classifyFailure = options.classifyFailure;
3602
4169
  if (options.onEvent) {
3603
4170
  this.listeners.add(options.onEvent);
3604
4171
  }
@@ -3614,15 +4181,18 @@ var AgentSessionController = class {
3614
4181
  options.retry,
3615
4182
  (event) => this.emit(event),
3616
4183
  () => this.removeTrailingAssistantError(),
3617
- () => this.continue()
4184
+ () => this.continue(),
4185
+ (state) => {
4186
+ this.recoveryState = state;
4187
+ }
3618
4188
  );
3619
4189
  this.autoCompact = new AgentSessionAutoCompactScheduler({
3620
4190
  retry: options.retry,
3621
4191
  getDisposed: () => this.disposed,
3622
4192
  getRuntimeGuardTriggered: () => this.runtimeGuard.triggered,
3623
4193
  isStreaming: () => this.isStreaming,
3624
- runAutoCompact: async (decision) => {
3625
- await this.runAutoCompact(decision.reason, decision.willRetry);
4194
+ runAutoCompact: async (decision, signal) => {
4195
+ await this.runAutoCompact(decision.reason, decision.willRetry, { signal });
3626
4196
  }
3627
4197
  });
3628
4198
  this.autoContinue = new AgentSessionAutoContinueController({
@@ -3634,6 +4204,7 @@ var AgentSessionController = class {
3634
4204
  emit: (event) => this.emit(event),
3635
4205
  getDisposed: () => this.disposed,
3636
4206
  getRuntimeGuardTriggered: () => this.runtimeGuard.triggered,
4207
+ isBlockedByRuntimePolicy: () => this.isBlockedByRuntimePolicy(),
3637
4208
  hasQueuedMessages: () => this.hasQueuedMessages,
3638
4209
  isStreaming: () => this.isStreaming,
3639
4210
  continueTurn: () => this.continue()
@@ -3649,7 +4220,8 @@ var AgentSessionController = class {
3649
4220
  getDisposed: () => this.disposed,
3650
4221
  getRuntimeGuardTriggered: () => this.runtimeGuard.triggered,
3651
4222
  isStreaming: () => this.isStreaming,
3652
- isBlockedByRuntimePolicy: () => this.retryController.hasPendingRetry || this.autoCompact.hasPendingAutoCompact,
4223
+ isBlockedByRuntimePolicy: () => this.isBlockedByRuntimePolicy(),
4224
+ runPreRequestAutoCompact: () => this.runPreRequestAutoCompactIfNeeded(),
3653
4225
  preparePromptMessages: (messages, promptOptions) => this.preparePromptMessages(messages, promptOptions),
3654
4226
  continueTurn: () => this.continue(),
3655
4227
  syncNewAgentMessages: () => this.syncNewAgentMessages()
@@ -3672,6 +4244,9 @@ var AgentSessionController = class {
3672
4244
  get queuedInputsSnapshot() {
3673
4245
  return this.queue.snapshot;
3674
4246
  }
4247
+ get turnRecoveryState() {
4248
+ return this.recoveryState;
4249
+ }
3675
4250
  get activeSessionMetadata() {
3676
4251
  const metadata = this.sessionManager.getActiveSession()?.metadata;
3677
4252
  return metadata ? { ...metadata } : void 0;
@@ -3680,10 +4255,7 @@ var AgentSessionController = class {
3680
4255
  return this.lastSyncedMessageCount;
3681
4256
  }
3682
4257
  get runtimeContext() {
3683
- return {
3684
- systemPrompt: this.agent.state.systemPrompt,
3685
- tools: this.agent.state.tools.slice()
3686
- };
4258
+ return { systemPrompt: this.agent.state.systemPrompt, tools: this.agent.state.tools.slice() };
3687
4259
  }
3688
4260
  getRuntimeSnapshot() {
3689
4261
  return {
@@ -3730,6 +4302,17 @@ var AgentSessionController = class {
3730
4302
  }
3731
4303
  async prompt(input, options) {
3732
4304
  const messages = normalizePromptInput(input);
4305
+ if (this.isRecoveryActive()) {
4306
+ this.queue.enqueue(messages, options?.streamingBehavior ?? "followUp", {
4307
+ ...options,
4308
+ streamingBehavior: options?.streamingBehavior ?? "followUp"
4309
+ });
4310
+ this.recordLifecycle({ type: "queued_input_added" });
4311
+ return;
4312
+ }
4313
+ if (this.recoveryState === "terminal" || this.recoveryState === "cancelled") {
4314
+ this.recoveryState = "none";
4315
+ }
3733
4316
  if (options?.streamingBehavior) {
3734
4317
  this.queue.enqueue(messages, options.streamingBehavior, options);
3735
4318
  this.recordLifecycle({ type: "queued_input_added" });
@@ -3741,7 +4324,8 @@ var AgentSessionController = class {
3741
4324
  if (this.isStreaming) {
3742
4325
  throw new Error("Agent is already processing. Specify streamingBehavior ('steer' or 'followUp') to queue the message.");
3743
4326
  }
3744
- const prepared = await this.preparePromptMessages(messages, options);
4327
+ await this.runPreRequestAutoCompactIfNeeded();
4328
+ const prepared = await this.runRuntimePhase("prepare", () => this.preparePromptMessages(messages, options));
3745
4329
  if (prepared.handled) {
3746
4330
  return;
3747
4331
  }
@@ -3749,23 +4333,30 @@ var AgentSessionController = class {
3749
4333
  if (prepared.systemPrompt !== void 0) {
3750
4334
  this.agent.setSystemPrompt(prepared.systemPrompt);
3751
4335
  }
3752
- await this.agent.prompt(prepared.messages);
3753
- await this.syncNewAgentMessages();
4336
+ await this.runRuntimePhase("request", () => this.agent.prompt(prepared.messages));
4337
+ await this.runRuntimePhase("post_turn", () => this.syncNewAgentMessages());
3754
4338
  }
3755
4339
  async continue() {
3756
4340
  if (this.isStreaming) {
3757
4341
  throw new Error("Agent is already processing. Wait for completion before continuing.");
3758
4342
  }
4343
+ await this.runPreRequestAutoCompactIfNeeded();
3759
4344
  this.recordLifecycle({ type: "prompt_started" });
3760
- await this.agent.continue();
3761
- await this.syncNewAgentMessages();
4345
+ await this.runRuntimePhase("request", () => this.agent.continue());
4346
+ await this.runRuntimePhase("post_turn", () => this.syncNewAgentMessages());
3762
4347
  }
3763
4348
  abort() {
3764
4349
  this.recordLifecycle({ type: "abort" });
4350
+ const cancelPendingRecovery = this.isRecoveryActive() && !this.isStreaming;
3765
4351
  this.retryController.cancel();
3766
4352
  this.autoCompact.cancel();
3767
4353
  this.queueDispatcher.cancel();
3768
4354
  this.autoContinue.cancel();
4355
+ if (cancelPendingRecovery) {
4356
+ this.recoveryState = "cancelled";
4357
+ this.emit({ type: "turn_cancelled", reason: "user_abort" });
4358
+ this.emit({ type: "turn_settled" });
4359
+ }
3769
4360
  this.agent.abort();
3770
4361
  }
3771
4362
  waitForIdle() {
@@ -3774,25 +4365,40 @@ var AgentSessionController = class {
3774
4365
  async waitForSettled() {
3775
4366
  while (true) {
3776
4367
  await this.agent.waitForIdle();
3777
- if (!this.retryController.hasPendingRetry) {
4368
+ if (!this.hasPendingRuntimePolicy()) {
3778
4369
  return;
3779
4370
  }
3780
- await this.retryController.waitForRetry();
4371
+ await Promise.all([this.retryController.waitForRetry(), this.autoCompact.waitForAutoCompact(), this.autoContinue.waitForAutoContinue(), this.queueDispatcher.waitForDispatch()]);
3781
4372
  }
3782
4373
  }
3783
- async runAutoCompact(reason, willRetry = false) {
3784
- return runAgentSessionCompaction({
4374
+ async runAutoCompact(reason, willRetry = false, options) {
4375
+ return runAgentSessionControllerAutoCompact({
3785
4376
  reason,
3786
4377
  willRetry,
4378
+ signal: options?.signal,
3787
4379
  compaction: this.compaction,
3788
4380
  agent: this.agent,
3789
4381
  sessionManager: this.sessionManager,
3790
4382
  cwd: this.cwd,
4383
+ pendingFailure: this.pendingCompactionFailure,
3791
4384
  emit: (event) => this.emit(event),
4385
+ setRecoveryState: (state) => {
4386
+ this.recoveryState = state;
4387
+ },
4388
+ getRecoveryState: () => this.recoveryState,
4389
+ getDisposed: () => this.disposed,
4390
+ cancelRetry: () => this.retryController.cancel(),
4391
+ clearPendingFailure: () => {
4392
+ this.pendingCompactionFailure = void 0;
4393
+ },
3792
4394
  removeTrailingAssistantError: () => this.removeTrailingAssistantError(),
3793
- replaceMessagesAndMarkSynced: (messages, options) => this.replaceMessagesAndMarkSynced(messages, options),
4395
+ replaceMessagesAndMarkSynced: (messages, options2) => this.replaceMessagesAndMarkSynced(messages, options2),
3794
4396
  markSyncedToAgentState: () => this.markSyncedToAgentState(),
3795
- continueTurn: () => this.continue()
4397
+ continueTurn: () => this.continue(),
4398
+ handleAgentEndHook: (messages) => this.handleAgentEndHook(messages),
4399
+ emitSettled: options?.emitSettled,
4400
+ runAfterAgentEndHook: options?.runAfterAgentEndHook,
4401
+ cancelOnAbort: options?.cancelOnAbort
3796
4402
  });
3797
4403
  }
3798
4404
  dispose() {
@@ -3814,9 +4420,23 @@ var AgentSessionController = class {
3814
4420
  this.queue.replaceText(search, replacement);
3815
4421
  }
3816
4422
  requestQueueDispatch() {
3817
- if (this.queue.length > 0) {
3818
- this.queueDispatcher.schedule();
3819
- }
4423
+ if (this.queue.length > 0) this.queueDispatcher.schedule();
4424
+ }
4425
+ async runPreRequestAutoCompactIfNeeded() {
4426
+ return runPreRequestAutoCompact({
4427
+ disposed: this.disposed,
4428
+ runtimeGuardTriggered: this.runtimeGuard.triggered,
4429
+ isStreaming: this.isStreaming,
4430
+ recoveryActive: this.isRecoveryActive(),
4431
+ agent: this.agent,
4432
+ sessionManager: this.sessionManager,
4433
+ cwd: this.cwd,
4434
+ getAutoCompactDecision: this.getAutoCompactDecision,
4435
+ runAutoCompact: () => this.runAutoCompact("threshold", false, { emitSettled: false, runAfterAgentEndHook: false, cancelOnAbort: false }).then(() => void 0),
4436
+ setRecoveryState: (state) => {
4437
+ this.recoveryState = state;
4438
+ }
4439
+ });
3820
4440
  }
3821
4441
  handleAgentEvent(event) {
3822
4442
  this.emit({ type: "agent_event", event });
@@ -3837,7 +4457,7 @@ var AgentSessionController = class {
3837
4457
  if (event.type === "turn_end") {
3838
4458
  void this.syncNewAgentMessages();
3839
4459
  this.recordLifecycle({ type: "turn_end_success" });
3840
- void this.handleTurnEndRuntimePolicies(event.message);
4460
+ void this.handleTurnEndRuntimePolicies(event.message, event.toolResults);
3841
4461
  }
3842
4462
  if (event.type === "agent_end") {
3843
4463
  this.recordLifecycle({ type: "agent_end" });
@@ -3874,31 +4494,29 @@ var AgentSessionController = class {
3874
4494
  replaceMessagesAndMarkSynced: (messages, options) => this.replaceMessagesAndMarkSynced(messages, options)
3875
4495
  });
3876
4496
  }
3877
- async handleTurnEndRuntimePolicies(message) {
3878
- if (this.disposed || this.runtimeGuard.triggered) {
3879
- return;
3880
- }
3881
- const assistantError = message?.role === "assistant" && message.stopReason === "error" && Boolean(message.errorMessage);
3882
- if (!assistantError) {
3883
- await this.retryController.handleTurnEnd(message);
3884
- }
3885
- const compactDecision = this.getAutoCompactDecision?.({
4497
+ async handleTurnEndRuntimePolicies(message, toolResults = []) {
4498
+ return handleAgentSessionTurnEndRuntimePolicies({
4499
+ disposed: this.disposed,
4500
+ runtimeGuardTriggered: this.runtimeGuard.triggered,
4501
+ message,
4502
+ toolResults,
3886
4503
  agent: this.agent,
3887
4504
  sessionManager: this.sessionManager,
3888
4505
  cwd: this.cwd,
3889
- message,
3890
- messages: this.agent.state.messages.slice()
4506
+ getAutoCompactDecision: this.getAutoCompactDecision,
4507
+ classifyFailure: this.classifyFailure,
4508
+ retry: this.retryController.policy,
4509
+ emit: (event) => this.emit(event),
4510
+ setRecoveryState: (state) => {
4511
+ this.recoveryState = state;
4512
+ },
4513
+ handleRetryTurnEnd: (turnMessage, failure) => this.retryController.handleTurnEnd(turnMessage, failure),
4514
+ cancelRetry: () => this.retryController.cancel(),
4515
+ scheduleAutoCompact: (decision, failure) => this.scheduleAutoCompact(decision, failure)
3891
4516
  });
3892
- if (compactDecision) {
3893
- this.scheduleAutoCompact(compactDecision);
3894
- return;
3895
- }
3896
- if (assistantError) {
3897
- await this.retryController.handleTurnEnd(message);
3898
- }
3899
4517
  }
3900
4518
  async handleAgentEndHook(messages) {
3901
- if (this.disposed || this.runtimeGuard.triggered) {
4519
+ if (this.disposed || this.runtimeGuard.triggered || this.isBlockedByRuntimePolicy()) {
3902
4520
  return;
3903
4521
  }
3904
4522
  if (this.afterAgentEnd) {
@@ -3933,8 +4551,36 @@ var AgentSessionController = class {
3933
4551
  recordToolExecutionEndForGuard(event) {
3934
4552
  this.runtimeGuard.recordToolExecutionEnd(event);
3935
4553
  }
3936
- scheduleAutoCompact(decision) {
4554
+ async runRuntimePhase(phase, action) {
4555
+ return runAgentSessionRuntimePhase({
4556
+ phase,
4557
+ action,
4558
+ retry: this.retryController.policy,
4559
+ classifyFailure: this.classifyFailure,
4560
+ emit: (event) => this.emit(event),
4561
+ setRecoveryState: (state) => {
4562
+ this.recoveryState = state;
4563
+ }
4564
+ });
4565
+ }
4566
+ isRecoveryActive() {
4567
+ return isAgentSessionRecoveryActive(this.recoveryState);
4568
+ }
4569
+ isBlockedByRuntimePolicy() {
4570
+ return this.recoveryState !== "none" || this.retryController.hasPendingRetry || this.autoCompact.hasPendingAutoCompact;
4571
+ }
4572
+ hasPendingRuntimePolicy() {
4573
+ return this.retryController.hasPendingRetry || this.autoCompact.hasPendingAutoCompact || this.autoContinue.hasPendingAutoContinue || this.queueDispatcher.hasPendingDispatch;
4574
+ }
4575
+ scheduleAutoCompact(decision, failure) {
3937
4576
  this.recordLifecycle({ type: "auto_compact_scheduled" });
4577
+ if (failure) {
4578
+ this.pendingCompactionFailure = failure;
4579
+ this.recoveryState = "compacting";
4580
+ this.emit({ type: "turn_recovery_pending", recovery: "compaction", failure });
4581
+ } else if (decision.reason === "threshold") {
4582
+ this.recoveryState = "compacting";
4583
+ }
3938
4584
  this.autoCompact.schedule(decision);
3939
4585
  }
3940
4586
  triggerRuntimeGuard(toolName, repeatCount, reason) {
@@ -3946,7 +4592,13 @@ var AgentSessionController = class {
3946
4592
  this.autoCompact.cancel();
3947
4593
  this.queueDispatcher.cancel();
3948
4594
  this.autoContinue.cancel();
4595
+ this.recoveryState = "terminal";
3949
4596
  this.emit({ type: "runtime_guard_triggered", toolName, repeatCount, reason });
4597
+ this.emit({
4598
+ type: "turn_failed_terminal",
4599
+ failure: createRuntimeGuardFailure(reason)
4600
+ });
4601
+ this.emit({ type: "turn_settled" });
3950
4602
  this.agent.abort();
3951
4603
  }
3952
4604
  emit(event) {
@@ -5155,7 +5807,49 @@ function restoreLineEndings(text, ending) {
5155
5807
  return ending === "\r\n" ? text.replace(/\n/g, "\r\n") : text;
5156
5808
  }
5157
5809
  function normalizeForFuzzyMatch(text) {
5158
- return text.split("\n").map((line) => line.trimEnd()).join("\n").replace(/[\u2018\u2019\u201A\u201B]/g, "'").replace(/[\u201C\u201D\u201E\u201F]/g, '"').replace(/[\u2010\u2011\u2012\u2013\u2014\u2015\u2212]/g, "-").replace(/[\u00A0\u2002-\u200A\u202F\u205F\u3000]/g, " ");
5810
+ return buildFuzzyIndexMap(text).normalized;
5811
+ }
5812
+ function normalizeCharForFuzzyMatch(char) {
5813
+ if (/[\u2018\u2019\u201A\u201B]/.test(char)) return "'";
5814
+ if (/[\u201C\u201D\u201E\u201F]/.test(char)) return '"';
5815
+ if (/[\u2010\u2011\u2012\u2013\u2014\u2015\u2212]/.test(char)) return "-";
5816
+ if (/[\u00A0\u2002-\u200A\u202F\u205F\u3000]/.test(char)) return " ";
5817
+ return char;
5818
+ }
5819
+ function buildFuzzyIndexMap(text) {
5820
+ const lines = text.split("\n");
5821
+ let normalized = "";
5822
+ const originalStartByNormalizedIndex = [];
5823
+ const originalEndByNormalizedIndex = [];
5824
+ let originalLineStart = 0;
5825
+ for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) {
5826
+ const line = lines[lineIndex] ?? "";
5827
+ const trimmedLine = line.trimEnd();
5828
+ for (let index = 0; index < trimmedLine.length; index++) {
5829
+ normalized += normalizeCharForFuzzyMatch(trimmedLine[index]);
5830
+ originalStartByNormalizedIndex.push(originalLineStart + index);
5831
+ originalEndByNormalizedIndex.push(originalLineStart + index + 1);
5832
+ }
5833
+ if (lineIndex < lines.length - 1) {
5834
+ normalized += "\n";
5835
+ originalStartByNormalizedIndex.push(originalLineStart + line.length);
5836
+ originalEndByNormalizedIndex.push(originalLineStart + line.length + 1);
5837
+ }
5838
+ originalLineStart += line.length + (lineIndex < lines.length - 1 ? 1 : 0);
5839
+ }
5840
+ return { normalized, originalStartByNormalizedIndex, originalEndByNormalizedIndex };
5841
+ }
5842
+ function countOccurrences(content, needle) {
5843
+ if (needle.length === 0) return 0;
5844
+ let count = 0;
5845
+ let index = 0;
5846
+ while (true) {
5847
+ const nextIndex = content.indexOf(needle, index);
5848
+ if (nextIndex === -1) break;
5849
+ count++;
5850
+ index = nextIndex + needle.length;
5851
+ }
5852
+ return count;
5159
5853
  }
5160
5854
  function fuzzyFindText(content, oldText) {
5161
5855
  const exactIndex = content.indexOf(oldText);
@@ -5165,11 +5859,25 @@ function fuzzyFindText(content, oldText) {
5165
5859
  index: exactIndex,
5166
5860
  matchLength: oldText.length,
5167
5861
  usedFuzzyMatch: false,
5168
- contentForReplacement: content
5862
+ contentForReplacement: content,
5863
+ occurrences: countOccurrences(content, oldText),
5864
+ matchKind: "exact"
5169
5865
  };
5170
5866
  }
5171
- const fuzzyContent = normalizeForFuzzyMatch(content);
5867
+ const fuzzyMap = buildFuzzyIndexMap(content);
5868
+ const fuzzyContent = fuzzyMap.normalized;
5172
5869
  const fuzzyOldText = normalizeForFuzzyMatch(oldText);
5870
+ if (fuzzyOldText.length === 0) {
5871
+ return {
5872
+ found: false,
5873
+ index: -1,
5874
+ matchLength: 0,
5875
+ usedFuzzyMatch: false,
5876
+ contentForReplacement: content,
5877
+ occurrences: 0,
5878
+ matchKind: "none"
5879
+ };
5880
+ }
5173
5881
  const fuzzyIndex = fuzzyContent.indexOf(fuzzyOldText);
5174
5882
  if (fuzzyIndex === -1) {
5175
5883
  return {
@@ -5177,35 +5885,65 @@ function fuzzyFindText(content, oldText) {
5177
5885
  index: -1,
5178
5886
  matchLength: 0,
5179
5887
  usedFuzzyMatch: false,
5180
- contentForReplacement: content
5888
+ contentForReplacement: content,
5889
+ occurrences: 0,
5890
+ matchKind: "none"
5181
5891
  };
5182
5892
  }
5893
+ const endFuzzyIndex = fuzzyIndex + fuzzyOldText.length - 1;
5894
+ const originalStart = fuzzyMap.originalStartByNormalizedIndex[fuzzyIndex];
5895
+ const originalEnd = fuzzyMap.originalEndByNormalizedIndex[endFuzzyIndex];
5183
5896
  return {
5184
5897
  found: true,
5185
- index: fuzzyIndex,
5186
- matchLength: fuzzyOldText.length,
5898
+ index: originalStart,
5899
+ matchLength: originalEnd - originalStart,
5187
5900
  usedFuzzyMatch: true,
5188
- contentForReplacement: fuzzyContent
5901
+ contentForReplacement: content,
5902
+ occurrences: countOccurrences(fuzzyContent, fuzzyOldText),
5903
+ matchKind: "fuzzy"
5189
5904
  };
5190
5905
  }
5191
5906
  function stripBom(content) {
5192
5907
  return content.startsWith("\uFEFF") ? { bom: "\uFEFF", text: content.slice(1) } : { bom: "", text: content };
5193
5908
  }
5194
- function generateDiffString(oldContent, newContent, _contextLines = 4) {
5195
- const oldLines = oldContent.split("\n");
5196
- const newLines = newContent.split("\n");
5197
- let firstChangedLine;
5198
- for (let i = 0; i < Math.max(oldLines.length, newLines.length); i++) {
5199
- if (oldLines[i] !== newLines[i]) {
5200
- firstChangedLine = i + 1;
5201
- break;
5202
- }
5203
- }
5204
- const diff = `--- old
5205
- +++ new
5206
- @@ -1,${oldLines.length} +1,${newLines.length} @@
5207
- [Diff: ${oldLines.length} lines -> ${newLines.length} lines]`;
5208
- return { diff, firstChangedLine };
5909
+ function generateDiffString(oldContent, newContent, contextLines = 4) {
5910
+ const oldLines = normalizeToLF(stripBom(oldContent).text).split("\n");
5911
+ const newLines = normalizeToLF(stripBom(newContent).text).split("\n");
5912
+ let prefixLength = 0;
5913
+ while (prefixLength < oldLines.length && prefixLength < newLines.length && oldLines[prefixLength] === newLines[prefixLength]) {
5914
+ prefixLength++;
5915
+ }
5916
+ if (prefixLength === oldLines.length && prefixLength === newLines.length) {
5917
+ return { diff: "--- old\n+++ new\n", firstChangedLine: void 0 };
5918
+ }
5919
+ let oldSuffixIndex = oldLines.length - 1;
5920
+ let newSuffixIndex = newLines.length - 1;
5921
+ while (oldSuffixIndex >= prefixLength && newSuffixIndex >= prefixLength && oldLines[oldSuffixIndex] === newLines[newSuffixIndex]) {
5922
+ oldSuffixIndex--;
5923
+ newSuffixIndex--;
5924
+ }
5925
+ const beforeContext = Math.min(contextLines, prefixLength);
5926
+ const removedCount = Math.max(0, oldSuffixIndex - prefixLength + 1);
5927
+ const addedCount = Math.max(0, newSuffixIndex - prefixLength + 1);
5928
+ const afterContext = Math.min(contextLines, oldLines.length - oldSuffixIndex - 1, newLines.length - newSuffixIndex - 1);
5929
+ const hunkOldStart = Math.max(0, prefixLength - beforeContext);
5930
+ const hunkNewStart = Math.max(0, prefixLength - beforeContext);
5931
+ const hunkOldCount = beforeContext + removedCount + afterContext;
5932
+ const hunkNewCount = beforeContext + addedCount + afterContext;
5933
+ const lines = ["--- old", "+++ new", `@@ -${hunkOldStart + 1},${hunkOldCount} +${hunkNewStart + 1},${hunkNewCount} @@`];
5934
+ for (let index = prefixLength - beforeContext; index < prefixLength; index++) {
5935
+ lines.push(` ${oldLines[index] ?? ""}`);
5936
+ }
5937
+ for (let index = prefixLength; index <= oldSuffixIndex; index++) {
5938
+ lines.push(`-${oldLines[index] ?? ""}`);
5939
+ }
5940
+ for (let index = prefixLength; index <= newSuffixIndex; index++) {
5941
+ lines.push(`+${newLines[index] ?? ""}`);
5942
+ }
5943
+ for (let offset = 1; offset <= afterContext; offset++) {
5944
+ lines.push(` ${oldLines[oldSuffixIndex + offset] ?? ""}`);
5945
+ }
5946
+ return { diff: lines.join("\n"), firstChangedLine: prefixLength + 1 };
5209
5947
  }
5210
5948
 
5211
5949
  // ../../packages/shared-headless-capabilities/src/builtin/fs/edit.ts
@@ -5227,7 +5965,7 @@ function validateEditArgs(path3, oldText, newText) {
5227
5965
  throw new Error("Invalid edit_file arguments: path, oldText, and newText must be real edit values, not tool-call markup.");
5228
5966
  }
5229
5967
  }
5230
- function countOccurrences(content, needle) {
5968
+ function countOccurrences2(content, needle) {
5231
5969
  if (needle.length === 0) return 0;
5232
5970
  let count = 0;
5233
5971
  let index = 0;
@@ -5239,6 +5977,29 @@ function countOccurrences(content, needle) {
5239
5977
  }
5240
5978
  return count;
5241
5979
  }
5980
+ function createEditErrorResult(params) {
5981
+ const detailLines = [
5982
+ params.message,
5983
+ params.recoveryHint,
5984
+ params.alreadyAppliedCandidate ? "Diagnostic: newText already appears in the current file, but edit_file did not treat that as success because it may be an unrelated occurrence." : ""
5985
+ ].filter(Boolean);
5986
+ return {
5987
+ content: [{ type: "text", text: detailLines.join("\n") }],
5988
+ details: {
5989
+ status: "error",
5990
+ matchKind: params.matchKind,
5991
+ changed: false,
5992
+ diff: "",
5993
+ oldTextOccurrences: params.oldTextOccurrences,
5994
+ newTextOccurrences: params.newTextOccurrences,
5995
+ errorReason: params.errorReason,
5996
+ recoveryHint: params.recoveryHint,
5997
+ staleBaseLikely: params.staleBaseLikely,
5998
+ alreadyAppliedCandidate: params.alreadyAppliedCandidate
5999
+ },
6000
+ isError: true
6001
+ };
6002
+ }
5242
6003
  function createEditTool(cwd, options) {
5243
6004
  const fs2 = getFileSystem();
5244
6005
  const rootSchema = buildRootParameterSchema(cwd, options);
@@ -5288,37 +6049,53 @@ function createEditTool(cwd, options) {
5288
6049
  const normalizedContent = normalizeToLF(content);
5289
6050
  const normalizedOldText = normalizeToLF(oldText);
5290
6051
  const normalizedNewText = normalizeToLF(newText);
6052
+ const newTextOccurrences = countOccurrences2(normalizedContent, normalizedNewText);
5291
6053
  const matchResult = fuzzyFindText(normalizedContent, normalizedOldText);
5292
6054
  if (!matchResult.found) {
5293
6055
  if (signal) signal.removeEventListener("abort", onAbort);
5294
- reject(
5295
- new Error(
5296
- `Could not find the exact text in ${path3}. The old text must match exactly including all whitespace and newlines.`
5297
- )
6056
+ resolve2(
6057
+ createEditErrorResult({
6058
+ message: `Could not find oldText in ${path3}.`,
6059
+ matchKind: "none",
6060
+ errorReason: "old_text_not_found",
6061
+ oldTextOccurrences: 0,
6062
+ newTextOccurrences,
6063
+ staleBaseLikely: newTextOccurrences > 0,
6064
+ alreadyAppliedCandidate: newTextOccurrences > 0,
6065
+ recoveryHint: newTextOccurrences > 0 ? "The replacement text already appears in the current file. Read the current file before retrying; this may be a stale oldText from an earlier edit in the same turn." : "Read the current file and retry with a larger exact block copied from the latest file contents."
6066
+ })
5298
6067
  );
5299
6068
  return;
5300
6069
  }
5301
- const fuzzyContent = normalizeForFuzzyMatch(normalizedContent);
5302
- const fuzzyOldText = normalizeForFuzzyMatch(normalizedOldText);
5303
- const occurrences = countOccurrences(fuzzyContent, fuzzyOldText);
5304
- if (occurrences > 1) {
6070
+ if (matchResult.occurrences > 1) {
5305
6071
  if (signal) signal.removeEventListener("abort", onAbort);
5306
- reject(
5307
- new Error(
5308
- `Found ${occurrences} occurrences of the text in ${path3}. The text must be unique. Please provide more context to make it unique.`
5309
- )
6072
+ resolve2(
6073
+ createEditErrorResult({
6074
+ message: `Found ${matchResult.occurrences} occurrences of oldText in ${path3}. The text must be unique.`,
6075
+ matchKind: matchResult.matchKind,
6076
+ errorReason: "old_text_not_unique",
6077
+ oldTextOccurrences: matchResult.occurrences,
6078
+ newTextOccurrences,
6079
+ recoveryHint: "Read the current file and retry with a larger exact block that uniquely identifies the intended location."
6080
+ })
5310
6081
  );
5311
6082
  return;
5312
6083
  }
5313
6084
  if (aborted) return;
5314
- const baseContent = matchResult.contentForReplacement;
6085
+ const baseContent = normalizedContent;
5315
6086
  const newContent = baseContent.substring(0, matchResult.index) + normalizedNewText + baseContent.substring(matchResult.index + matchResult.matchLength);
5316
6087
  if (baseContent === newContent) {
5317
6088
  if (signal) signal.removeEventListener("abort", onAbort);
5318
- reject(
5319
- new Error(
5320
- `No changes made to ${path3}. The replacement produced identical content.`
5321
- )
6089
+ resolve2(
6090
+ createEditErrorResult({
6091
+ message: `No changes made to ${path3}. The replacement produced identical content.`,
6092
+ matchKind: matchResult.matchKind,
6093
+ errorReason: "replacement_no_change",
6094
+ oldTextOccurrences: matchResult.occurrences,
6095
+ newTextOccurrences,
6096
+ recoveryHint: "Read the current file and verify whether the intended edit is already present or whether oldText/newText need a larger exact block.",
6097
+ alreadyAppliedCandidate: newTextOccurrences > 0
6098
+ })
5322
6099
  );
5323
6100
  return;
5324
6101
  }
@@ -5328,16 +6105,24 @@ function createEditTool(cwd, options) {
5328
6105
  if (signal) {
5329
6106
  signal.removeEventListener("abort", onAbort);
5330
6107
  }
5331
- const diffResult = generateDiffString(content, finalContent);
6108
+ const diffResult = generateDiffString(content, restoreLineEndings(newContent, originalEnding));
5332
6109
  resolve2({
5333
6110
  content: [
5334
6111
  {
5335
6112
  type: "text",
5336
- text: `Successfully replaced text in ${path3}.`
6113
+ text: `Successfully replaced text in ${path3} using ${matchResult.matchKind} match.`
5337
6114
  },
5338
6115
  { type: "text", text: diffResult.diff }
5339
6116
  ],
5340
- details: { diff: diffResult.diff, firstChangedLine: diffResult.firstChangedLine }
6117
+ details: {
6118
+ status: "success",
6119
+ matchKind: matchResult.matchKind,
6120
+ changed: true,
6121
+ diff: diffResult.diff,
6122
+ firstChangedLine: diffResult.firstChangedLine,
6123
+ oldTextOccurrences: matchResult.occurrences,
6124
+ newTextOccurrences
6125
+ }
5341
6126
  });
5342
6127
  } catch (error) {
5343
6128
  if (signal) {
@@ -6391,6 +7176,81 @@ function createEmptyRuntimeSummary() {
6391
7176
  toolErrors: 0
6392
7177
  };
6393
7178
  }
7179
+ function summarizeString(value, maxLength = 300) {
7180
+ return value.length > maxLength ? `${value.slice(0, maxLength)}...` : value;
7181
+ }
7182
+ function summarizeMessage(message) {
7183
+ if (!isRecord(message)) return typeof message;
7184
+ if ("contentBlocks" in message && !("content" in message)) return message;
7185
+ const content = Array.isArray(message.content) ? message.content : [];
7186
+ const blockTypes = content.filter(isRecord).map((block) => String(block.type || "unknown")).slice(0, 10);
7187
+ const text = getTextFromMessageContent(content);
7188
+ return {
7189
+ role: message.role,
7190
+ stopReason: message.stopReason,
7191
+ errorMessage: typeof message.errorMessage === "string" ? summarizeString(message.errorMessage) : void 0,
7192
+ contentBlocks: content.length,
7193
+ blockTypes,
7194
+ textLength: text.length
7195
+ };
7196
+ }
7197
+ function summarizeToolResults(value) {
7198
+ if (isRecord(value) && typeof value.count === "number" && !Array.isArray(value)) return value;
7199
+ if (!Array.isArray(value)) return typeof value;
7200
+ let errors = 0;
7201
+ const toolNames = [];
7202
+ for (const result of value) {
7203
+ if (!isRecord(result)) continue;
7204
+ if (result.isError === true) errors++;
7205
+ const toolName = typeof result.toolName === "string" ? result.toolName : typeof result.name === "string" ? result.name : void 0;
7206
+ if (toolName && toolNames.length < 10) toolNames.push(toolName);
7207
+ }
7208
+ return { count: value.length, errors, toolNames };
7209
+ }
7210
+ function summarizeFailure(value) {
7211
+ if (!isRecord(value)) return typeof value;
7212
+ return {
7213
+ kind: value.kind,
7214
+ retryable: value.retryable,
7215
+ compactable: value.compactable,
7216
+ userAbort: value.userAbort,
7217
+ terminal: value.terminal,
7218
+ message: typeof value.message === "string" ? summarizeString(value.message) : void 0
7219
+ };
7220
+ }
7221
+ function summarizeRuntimeEvent(value) {
7222
+ if (!isRecord(value)) return typeof value;
7223
+ const summary = {
7224
+ type: value.type
7225
+ };
7226
+ if ("toolCallId" in value) summary.toolCallId = value.toolCallId;
7227
+ if ("toolName" in value) summary.toolName = value.toolName;
7228
+ if ("turnIndex" in value) summary.turnIndex = value.turnIndex;
7229
+ if ("isError" in value) summary.isError = value.isError;
7230
+ if ("errorMessage" in value && typeof value.errorMessage === "string") {
7231
+ summary.errorMessage = summarizeString(value.errorMessage);
7232
+ }
7233
+ if ("message" in value) summary.message = summarizeMessage(value.message);
7234
+ if ("toolResults" in value) summary.toolResults = summarizeToolResults(value.toolResults);
7235
+ return summary;
7236
+ }
7237
+ function shouldRecordRuntimeEvent(type) {
7238
+ const normalizedType = type.startsWith("runtime:") ? type.slice("runtime:".length) : type;
7239
+ return normalizedType !== "agent_event" && normalizedType !== "semantic_event";
7240
+ }
7241
+ function recomputeRuntimeSummaryFromTimeline(events) {
7242
+ const summary = createEmptyRuntimeSummary();
7243
+ for (const event of events ?? []) {
7244
+ if (/queue|queued_turn/.test(event.type)) summary.queuedTurnEvents++;
7245
+ if (/retry/.test(event.type)) summary.retryEvents++;
7246
+ if (/auto_compaction|auto_compact|compaction/.test(event.type)) summary.autoCompactionEvents++;
7247
+ if (/runtime_guard|repeated_tool_error/.test(event.type)) summary.runtimeGuardEvents++;
7248
+ if (/subagent/.test(event.type)) summary.subagentEvents++;
7249
+ if (event.type === "tool_end" || event.type === "tool_error") summary.toolResults++;
7250
+ if (event.type === "tool_error") summary.toolErrors++;
7251
+ }
7252
+ return summary;
7253
+ }
6394
7254
  function summarizeMeta(meta) {
6395
7255
  if (!isRecord(meta)) return meta;
6396
7256
  const summary = {};
@@ -6399,6 +7259,22 @@ function summarizeMeta(meta) {
6399
7259
  summary[key] = Array.isArray(value) ? { count: value.length } : typeof value;
6400
7260
  continue;
6401
7261
  }
7262
+ if (key === "message") {
7263
+ summary[key] = summarizeMessage(value);
7264
+ continue;
7265
+ }
7266
+ if (key === "toolResults") {
7267
+ summary[key] = summarizeToolResults(value);
7268
+ continue;
7269
+ }
7270
+ if (key === "failure") {
7271
+ summary[key] = summarizeFailure(value);
7272
+ continue;
7273
+ }
7274
+ if (key === "event") {
7275
+ summary[key] = summarizeRuntimeEvent(value);
7276
+ continue;
7277
+ }
6402
7278
  if (key === "queuedInput" && isRecord(value)) {
6403
7279
  summary[key] = {
6404
7280
  mode: value.mode,
@@ -6408,7 +7284,7 @@ function summarizeMeta(meta) {
6408
7284
  };
6409
7285
  continue;
6410
7286
  }
6411
- summary[key] = typeof value === "string" && value.length > 300 ? `${value.slice(0, 300)}...` : value;
7287
+ summary[key] = typeof value === "string" ? summarizeString(value) : value;
6412
7288
  }
6413
7289
  return summary;
6414
7290
  }
@@ -6522,9 +7398,7 @@ function timelineEventFromTraceEvent(type, meta) {
6522
7398
  failure: typeof data.failureCategory === "string" && data.failureCategory.length > 0
6523
7399
  };
6524
7400
  }
6525
- if (normalizedType === "subagent_summary") {
6526
- return { type: "subagent_child_end", summary: `subagent summary count=${String(data.count ?? "?")}`, details: detail };
6527
- }
7401
+ if (normalizedType === "subagent_summary") return null;
6528
7402
  return null;
6529
7403
  }
6530
7404
  function cloneTurn(turn) {
@@ -6565,6 +7439,9 @@ var SessionTraceController = class {
6565
7439
  if (!Array.isArray(this.trace.recentTimeline)) {
6566
7440
  this.trace.recentTimeline = [];
6567
7441
  }
7442
+ this.trace.recentEvents = this.trace.recentEvents.slice(-this.recentEventsLimit);
7443
+ this.trace.recentTimeline = this.trace.recentTimeline.slice(-this.recentEventsLimit);
7444
+ this.trace.runtimeSummary = recomputeRuntimeSummaryFromTimeline(this.trace.recentTimeline);
6568
7445
  }
6569
7446
  trace;
6570
7447
  recentTurnsLimit;
@@ -6579,7 +7456,6 @@ var SessionTraceController = class {
6579
7456
  if (this.trace.recentEvents.length > this.recentEventsLimit) {
6580
7457
  this.trace.recentEvents = this.trace.recentEvents.slice(-this.recentEventsLimit);
6581
7458
  }
6582
- this.updateRuntimeSummary(type, meta);
6583
7459
  const timelineEvent = timelineEventFromTraceEvent(type, meta);
6584
7460
  if (timelineEvent) {
6585
7461
  this.pushTimeline(timelineEvent);
@@ -6594,17 +7470,7 @@ var SessionTraceController = class {
6594
7470
  if (this.trace.recentTimeline.length > this.recentEventsLimit) {
6595
7471
  this.trace.recentTimeline = this.trace.recentTimeline.slice(-this.recentEventsLimit);
6596
7472
  }
6597
- }
6598
- updateRuntimeSummary(type, meta) {
6599
- const summary = this.trace.runtimeSummary ?? createEmptyRuntimeSummary();
6600
- if (/queue|queued_turn/.test(type)) summary.queuedTurnEvents++;
6601
- if (/retry/.test(type)) summary.retryEvents++;
6602
- if (/auto_compaction|auto_compact|compaction/.test(type)) summary.autoCompactionEvents++;
6603
- if (/runtime_guard|repeated_tool_error/.test(type)) summary.runtimeGuardEvents++;
6604
- if (/subagent/.test(type)) summary.subagentEvents++;
6605
- if (type === "tool_end") summary.toolResults++;
6606
- if (type === "tool_end" && isRecord(meta) && meta.isError) summary.toolErrors++;
6607
- this.trace.runtimeSummary = summary;
7473
+ this.trace.runtimeSummary = recomputeRuntimeSummaryFromTimeline(this.trace.recentTimeline);
6608
7474
  }
6609
7475
  nextTurnIndex() {
6610
7476
  if (this.trace.activeTurn) {
@@ -6676,6 +7542,7 @@ var SessionTraceController = class {
6676
7542
  this.touch();
6677
7543
  }
6678
7544
  noteRuntimeEvent(type, meta) {
7545
+ if (!shouldRecordRuntimeEvent(type)) return;
6679
7546
  this.pushEvent(`runtime:${type}`, summarizeMeta(meta));
6680
7547
  this.touch();
6681
7548
  }
@@ -6700,7 +7567,6 @@ var SessionTraceController = class {
6700
7567
  if (!message || message.role !== "assistant" || !this.trace.activeTurn) return;
6701
7568
  this.trace.activeTurn.state = this.trace.activeTurn.toolCalls.some((tool) => tool.status === "running") ? "tool_running" : "streaming";
6702
7569
  this.trace.activeTurn.assistantText = getTextFromMessageContent(message.content);
6703
- this.pushEvent("assistant_update");
6704
7570
  this.touch();
6705
7571
  }
6706
7572
  startTool(toolCallId, toolName, args) {
@@ -9023,7 +9889,10 @@ export {
9023
9889
  ManageTodoListParamsSchema,
9024
9890
  executeManageTodoList,
9025
9891
  maybeAdvanceTodoExecutionState,
9892
+ estimateTokens,
9026
9893
  calculateTotalTokens,
9894
+ estimateContextTokens,
9895
+ getAutoCompactTokenLimit,
9027
9896
  findFirstKeptEntryId,
9028
9897
  createCompactionSummary,
9029
9898
  createSessionManager,