@cydm/pie 1.0.15 → 1.0.16

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}
@@ -1692,6 +1722,138 @@ function appendFileOperationsToSummary(summary, messages) {
1692
1722
  const { readFiles, modifiedFiles } = computeFileLists(fileOps);
1693
1723
  return `${summary}${formatFileOperations(readFiles, modifiedFiles)}`;
1694
1724
  }
1725
+ function estimateModelVisibleBytes(message) {
1726
+ const visibleMessage = normalizeModelVisibleMessage(message);
1727
+ const wrapperBytes = utf8ByteLength(safeStringify(visibleMessage));
1728
+ return wrapperBytes + estimateMessageMediaBytes(message);
1729
+ }
1730
+ function normalizeModelVisibleMessage(message) {
1731
+ if (message.role === "user") {
1732
+ return {
1733
+ role: "user",
1734
+ content: typeof message.content === "string" ? message.content : message.content.map(normalizeUserContentBlock)
1735
+ };
1736
+ }
1737
+ if (message.role === "assistant") {
1738
+ return {
1739
+ role: "assistant",
1740
+ content: message.content.map((block) => {
1741
+ if (block.type === "text") {
1742
+ return {
1743
+ type: "text",
1744
+ text: block.text,
1745
+ ...block.textSignature ? { textSignature: block.textSignature } : {}
1746
+ };
1747
+ }
1748
+ if (block.type === "thinking") {
1749
+ return {
1750
+ type: "thinking",
1751
+ thinking: block.thinking,
1752
+ ...block.thinkingSignature ? { thinkingSignature: block.thinkingSignature } : {}
1753
+ };
1754
+ }
1755
+ if (block.type === "toolCall") {
1756
+ return {
1757
+ type: "toolCall",
1758
+ id: block.id,
1759
+ name: block.name,
1760
+ arguments: block.arguments ?? {},
1761
+ ...block.thoughtSignature ? { thoughtSignature: block.thoughtSignature } : {}
1762
+ };
1763
+ }
1764
+ return block;
1765
+ })
1766
+ };
1767
+ }
1768
+ if (message.role === "toolResult") {
1769
+ return {
1770
+ role: "toolResult",
1771
+ toolCallId: message.toolCallId,
1772
+ toolName: message.toolName,
1773
+ isError: message.isError,
1774
+ content: message.content.map(normalizeUserContentBlock)
1775
+ };
1776
+ }
1777
+ return {
1778
+ role: String(message.role ?? "unknown"),
1779
+ content: getMessageText(message)
1780
+ };
1781
+ }
1782
+ function normalizeUserContentBlock(block) {
1783
+ if (block.type === "text") {
1784
+ return { type: "text", text: block.text };
1785
+ }
1786
+ if (block.type === "image") {
1787
+ return { type: "image", mimeType: block.mimeType };
1788
+ }
1789
+ if (block.type === "audio") {
1790
+ return { type: "audio", mimeType: block.mimeType };
1791
+ }
1792
+ if (block.type === "video") {
1793
+ return { type: "video", mimeType: block.mimeType };
1794
+ }
1795
+ if (block.type === "fileRef") {
1796
+ return {
1797
+ type: "fileRef",
1798
+ url: block.url,
1799
+ mimeType: block.mimeType,
1800
+ modality: block.modality
1801
+ };
1802
+ }
1803
+ return block;
1804
+ }
1805
+ function estimateMessageMediaBytes(message) {
1806
+ if (message.role === "user" && Array.isArray(message.content)) {
1807
+ return message.content.reduce((sum, block) => sum + estimateUserContentBlockMediaBytes(block), 0);
1808
+ }
1809
+ if (message.role === "toolResult") {
1810
+ return message.content.reduce((sum, block) => sum + estimateUserContentBlockMediaBytes(block), 0);
1811
+ }
1812
+ return 0;
1813
+ }
1814
+ function estimateUserContentBlockMediaBytes(block) {
1815
+ if (block.type === "image") {
1816
+ return estimateImageContentBytes(block);
1817
+ }
1818
+ if (block.type === "audio") {
1819
+ return estimateInlineDataBytes(block);
1820
+ }
1821
+ if (block.type === "video") {
1822
+ return estimateInlineDataBytes(block);
1823
+ }
1824
+ return 0;
1825
+ }
1826
+ function estimateImageContentBytes(block) {
1827
+ return Math.max(RESIZED_IMAGE_BYTES_ESTIMATE, estimateInlineDataBytes(block));
1828
+ }
1829
+ function estimateInlineDataBytes(block) {
1830
+ return utf8ByteLength(block.data || "");
1831
+ }
1832
+ function approximateBytesAsTokens(bytes) {
1833
+ return Math.ceil(bytes / APPROX_BYTES_PER_TOKEN);
1834
+ }
1835
+ function safeStringify(value) {
1836
+ try {
1837
+ return JSON.stringify(value) ?? "";
1838
+ } catch {
1839
+ return String(value ?? "");
1840
+ }
1841
+ }
1842
+ var utf8Encoder = typeof globalThis.TextEncoder === "function" ? new globalThis.TextEncoder() : void 0;
1843
+ function utf8ByteLength(text) {
1844
+ if (utf8Encoder) {
1845
+ return utf8Encoder.encode(text).length;
1846
+ }
1847
+ let bytes = 0;
1848
+ for (const char of text) {
1849
+ const codePoint = char.codePointAt(0) ?? 0;
1850
+ if (codePoint <= 127) bytes += 1;
1851
+ else if (codePoint <= 2047) bytes += 2;
1852
+ else if (codePoint <= 65535) bytes += 3;
1853
+ else bytes += 4;
1854
+ }
1855
+ return bytes;
1856
+ }
1695
1857
  function isSyntheticUserEnvelope(text) {
1696
1858
  const trimmed = text.trimStart();
1697
1859
  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 +2969,20 @@ var AgentSessionAutoCompactScheduler = class {
2807
2969
  options;
2808
2970
  autoCompactTimer;
2809
2971
  pendingAutoCompact;
2972
+ runningAutoCompact;
2973
+ pendingWait;
2974
+ resolvePendingWait;
2810
2975
  get hasPendingAutoCompact() {
2811
- return this.autoCompactTimer !== void 0;
2976
+ return this.autoCompactTimer !== void 0 || this.pendingAutoCompact !== void 0 || this.runningAutoCompact !== void 0;
2977
+ }
2978
+ waitForAutoCompact() {
2979
+ return this.pendingWait ?? Promise.resolve();
2812
2980
  }
2813
2981
  schedule(decision, delayMs = 0) {
2814
2982
  if (this.options.getRuntimeGuardTriggered()) {
2815
2983
  return;
2816
2984
  }
2985
+ this.ensurePendingWait();
2817
2986
  this.pendingAutoCompact = decision;
2818
2987
  if (this.autoCompactTimer !== void 0) {
2819
2988
  return;
@@ -2824,6 +2993,7 @@ var AgentSessionAutoCompactScheduler = class {
2824
2993
  const pending = this.pendingAutoCompact;
2825
2994
  this.pendingAutoCompact = void 0;
2826
2995
  if (!pending || this.options.getDisposed()) {
2996
+ this.resolvePending();
2827
2997
  return;
2828
2998
  }
2829
2999
  if (this.options.isStreaming()) {
@@ -2831,17 +3001,33 @@ var AgentSessionAutoCompactScheduler = class {
2831
3001
  this.schedule(pending, 100);
2832
3002
  return;
2833
3003
  }
2834
- void this.options.runAutoCompact(pending);
3004
+ this.runningAutoCompact = this.options.runAutoCompact(pending).catch(() => {
3005
+ }).finally(() => {
3006
+ this.runningAutoCompact = void 0;
3007
+ this.resolvePending();
3008
+ });
2835
3009
  }, delayMs);
2836
3010
  }
2837
3011
  cancel() {
2838
- if (this.autoCompactTimer === void 0) {
2839
- return;
3012
+ if (this.autoCompactTimer !== void 0) {
3013
+ const clearTimer = this.options.retry?.clearTimeout ?? defaultClearTimeout;
3014
+ clearTimer(this.autoCompactTimer);
3015
+ this.autoCompactTimer = void 0;
2840
3016
  }
2841
- const clearTimer = this.options.retry?.clearTimeout ?? defaultClearTimeout;
2842
- clearTimer(this.autoCompactTimer);
2843
- this.autoCompactTimer = void 0;
2844
3017
  this.pendingAutoCompact = void 0;
3018
+ this.resolvePending();
3019
+ }
3020
+ ensurePendingWait() {
3021
+ if (this.pendingWait) return;
3022
+ this.pendingWait = new Promise((resolve2) => {
3023
+ this.resolvePendingWait = resolve2;
3024
+ });
3025
+ }
3026
+ resolvePending() {
3027
+ const resolve2 = this.resolvePendingWait;
3028
+ this.pendingWait = void 0;
3029
+ this.resolvePendingWait = void 0;
3030
+ resolve2?.();
2845
3031
  }
2846
3032
  };
2847
3033
 
@@ -2858,15 +3044,24 @@ var AgentSessionAutoContinueController = class {
2858
3044
  }
2859
3045
  options;
2860
3046
  autoContinueTimer;
3047
+ pendingAutoContinue;
3048
+ resolvePendingAutoContinue;
3049
+ get hasPendingAutoContinue() {
3050
+ return this.autoContinueTimer !== void 0 || this.pendingAutoContinue !== void 0;
3051
+ }
3052
+ waitForAutoContinue() {
3053
+ return this.pendingAutoContinue ?? Promise.resolve();
3054
+ }
2861
3055
  schedule(messages) {
2862
3056
  if (!this.options.getAutoContinueMessage || this.options.getDisposed() || this.autoContinueTimer !== void 0) {
2863
3057
  return;
2864
3058
  }
3059
+ this.ensurePending();
2865
3060
  const setTimer = this.options.retry?.setTimeout ?? defaultSetTimeout2;
2866
3061
  const snapshot = messages.slice();
2867
3062
  this.autoContinueTimer = setTimer(() => {
2868
3063
  this.autoContinueTimer = void 0;
2869
- void this.maybeScheduleAutoContinue(snapshot);
3064
+ void this.maybeScheduleAutoContinue(snapshot).finally(() => this.resolvePending());
2870
3065
  }, 0);
2871
3066
  }
2872
3067
  cancel() {
@@ -2876,9 +3071,10 @@ var AgentSessionAutoContinueController = class {
2876
3071
  const clearTimer = this.options.retry?.clearTimeout ?? defaultClearTimeout2;
2877
3072
  clearTimer(this.autoContinueTimer);
2878
3073
  this.autoContinueTimer = void 0;
3074
+ this.resolvePending();
2879
3075
  }
2880
3076
  async maybeScheduleAutoContinue(messages) {
2881
- if (!this.options.getAutoContinueMessage || this.options.getDisposed() || this.options.getRuntimeGuardTriggered() || this.options.hasQueuedMessages() || this.options.isStreaming()) {
3077
+ if (!this.options.getAutoContinueMessage || this.options.getDisposed() || this.options.getRuntimeGuardTriggered() || this.options.isBlockedByRuntimePolicy() || this.options.hasQueuedMessages() || this.options.isStreaming()) {
2882
3078
  if (this.options.hasQueuedMessages()) {
2883
3079
  this.options.emit({ type: "auto_continue_skipped", reason: "queued_messages" });
2884
3080
  }
@@ -2902,21 +3098,36 @@ var AgentSessionAutoContinueController = class {
2902
3098
  this.options.emit({ type: "auto_continue_skipped", reason: reason ?? "no_message" });
2903
3099
  return;
2904
3100
  }
2905
- if (this.options.hasQueuedMessages() || this.options.isStreaming()) {
3101
+ if (this.options.hasQueuedMessages() || this.options.isStreaming() || this.options.isBlockedByRuntimePolicy()) {
2906
3102
  this.options.emit({ type: "auto_continue_skipped", reason: "runtime_busy" });
2907
3103
  return;
2908
3104
  }
2909
3105
  this.options.emit({ type: "auto_continue_scheduled", reason });
2910
3106
  this.options.agent.followUp(message);
2911
3107
  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);
3108
+ await new Promise((resolve2) => {
3109
+ this.autoContinueTimer = setTimer(() => {
3110
+ this.autoContinueTimer = void 0;
3111
+ if (this.options.getDisposed()) {
3112
+ resolve2();
3113
+ return;
3114
+ }
3115
+ void this.options.continueTurn().catch(() => {
3116
+ }).finally(resolve2);
3117
+ }, 0);
3118
+ });
3119
+ }
3120
+ ensurePending() {
3121
+ if (this.pendingAutoContinue) return;
3122
+ this.pendingAutoContinue = new Promise((resolve2) => {
3123
+ this.resolvePendingAutoContinue = resolve2;
3124
+ });
3125
+ }
3126
+ resolvePending() {
3127
+ const resolve2 = this.resolvePendingAutoContinue;
3128
+ this.pendingAutoContinue = void 0;
3129
+ this.resolvePendingAutoContinue = void 0;
3130
+ resolve2?.();
2920
3131
  }
2921
3132
  };
2922
3133
 
@@ -2934,7 +3145,7 @@ async function runAgentSessionCompaction(params) {
2934
3145
  }
2935
3146
  params.emit({ type: "auto_compaction_start", reason: params.reason, willRetry: params.willRetry });
2936
3147
  try {
2937
- if (params.reason === "overflow") {
3148
+ if (params.willRetry) {
2938
3149
  params.removeTrailingAssistantError();
2939
3150
  }
2940
3151
  const result = await params.compaction.compact({
@@ -3028,6 +3239,27 @@ async function preparePromptMessages(params) {
3028
3239
  };
3029
3240
  }
3030
3241
 
3242
+ // ../../packages/agent-framework/src/session/controller/pre-request-policy.ts
3243
+ async function runPreRequestAutoCompact(params) {
3244
+ if (params.disposed || params.runtimeGuardTriggered || params.isStreaming || params.recoveryActive || params.agent.state.messages.length === 0) {
3245
+ return;
3246
+ }
3247
+ const decision = params.getAutoCompactDecision?.({
3248
+ agent: params.agent,
3249
+ sessionManager: params.sessionManager,
3250
+ cwd: params.cwd,
3251
+ messages: params.agent.state.messages.slice()
3252
+ });
3253
+ if (decision?.reason !== "threshold") {
3254
+ return;
3255
+ }
3256
+ try {
3257
+ await params.runAutoCompact();
3258
+ } catch {
3259
+ params.setRecoveryState("none");
3260
+ }
3261
+ }
3262
+
3031
3263
  // ../../packages/agent-framework/src/session/controller/queue.ts
3032
3264
  function cloneQueuedInput(input) {
3033
3265
  return {
@@ -3186,14 +3418,23 @@ var AgentSessionQueueDispatcher = class {
3186
3418
  }
3187
3419
  options;
3188
3420
  dispatchTimer;
3421
+ pendingDispatch;
3422
+ resolvePendingDispatch;
3423
+ get hasPendingDispatch() {
3424
+ return this.dispatchTimer !== void 0 || this.pendingDispatch !== void 0;
3425
+ }
3426
+ waitForDispatch() {
3427
+ return this.pendingDispatch ?? Promise.resolve();
3428
+ }
3189
3429
  schedule(delayMs = 0) {
3190
3430
  if (this.options.getDisposed() || this.options.getRuntimeGuardTriggered() || this.dispatchTimer !== void 0) {
3191
3431
  return;
3192
3432
  }
3433
+ this.ensurePending();
3193
3434
  const setTimer = this.options.retry?.setTimeout ?? defaultSetTimeout3;
3194
3435
  this.dispatchTimer = setTimer(() => {
3195
3436
  this.dispatchTimer = void 0;
3196
- void this.dispatchNext();
3437
+ void this.dispatchNext().finally(() => this.resolvePending());
3197
3438
  }, delayMs);
3198
3439
  }
3199
3440
  cancel() {
@@ -3203,6 +3444,7 @@ var AgentSessionQueueDispatcher = class {
3203
3444
  const clearTimer = this.options.retry?.clearTimeout ?? defaultClearTimeout3;
3204
3445
  clearTimer(this.dispatchTimer);
3205
3446
  this.dispatchTimer = void 0;
3447
+ this.resolvePending();
3206
3448
  }
3207
3449
  async dispatchNext() {
3208
3450
  if (this.options.getDisposed() || this.options.isStreaming() || this.options.isBlockedByRuntimePolicy()) {
@@ -3230,6 +3472,11 @@ var AgentSessionQueueDispatcher = class {
3230
3472
  }
3231
3473
  return false;
3232
3474
  }
3475
+ await this.options.runPreRequestAutoCompact();
3476
+ if (this.options.getDisposed() || this.options.isStreaming() || this.options.isBlockedByRuntimePolicy()) {
3477
+ this.schedule(100);
3478
+ return false;
3479
+ }
3233
3480
  const dispatched = this.options.queue.shift();
3234
3481
  if (!dispatched) {
3235
3482
  return false;
@@ -3259,8 +3506,56 @@ var AgentSessionQueueDispatcher = class {
3259
3506
  }
3260
3507
  return true;
3261
3508
  }
3509
+ ensurePending() {
3510
+ if (this.pendingDispatch) return;
3511
+ this.pendingDispatch = new Promise((resolve2) => {
3512
+ this.resolvePendingDispatch = resolve2;
3513
+ });
3514
+ }
3515
+ resolvePending() {
3516
+ const resolve2 = this.resolvePendingDispatch;
3517
+ this.pendingDispatch = void 0;
3518
+ this.resolvePendingDispatch = void 0;
3519
+ resolve2?.();
3520
+ }
3262
3521
  };
3263
3522
 
3523
+ // ../../packages/agent-framework/src/session/controller/failure-classification.ts
3524
+ function isAssistantFailureMessage(message) {
3525
+ return message?.role === "assistant" && (message.stopReason === "error" || message.stopReason === "aborted");
3526
+ }
3527
+ function isAssistantTerminalCompletion(message) {
3528
+ if (message?.role !== "assistant") return false;
3529
+ if (message.stopReason === "error" || message.stopReason === "aborted") return false;
3530
+ if (message.stopReason === "toolUse") return false;
3531
+ return !message.content.some((block) => block.type === "toolCall");
3532
+ }
3533
+ function createFailureClassification(context, retry, classifyFailure) {
3534
+ const hostClassification = classifyFailure?.(context);
3535
+ if (hostClassification) {
3536
+ return hostClassification;
3537
+ }
3538
+ const retryable = retry?.isRetryableError(context.errorMessage) === true;
3539
+ const userAbort = context.message?.role === "assistant" && context.message.stopReason === "aborted";
3540
+ return {
3541
+ kind: userAbort ? "user_abort" : retryable ? "provider_transient" : "unknown",
3542
+ retryable: retryable && !userAbort,
3543
+ compactable: false,
3544
+ userAbort,
3545
+ terminal: !retryable || userAbort,
3546
+ message: context.errorMessage
3547
+ };
3548
+ }
3549
+ function asTerminalFailure(failure, message = failure.message) {
3550
+ return {
3551
+ ...failure,
3552
+ retryable: false,
3553
+ compactable: false,
3554
+ terminal: true,
3555
+ message
3556
+ };
3557
+ }
3558
+
3264
3559
  // ../../packages/agent-framework/src/session/controller/retry-policy.ts
3265
3560
  function defaultSetTimeout4(callback, delay) {
3266
3561
  return setTimeout(callback, delay);
@@ -3269,42 +3564,49 @@ function defaultClearTimeout4(handle) {
3269
3564
  clearTimeout(handle);
3270
3565
  }
3271
3566
  var AgentSessionRetryController = class {
3272
- constructor(retry, emit, removeTrailingAssistantError2, continueTurn) {
3567
+ constructor(retry, emit, removeTrailingAssistantError2, continueTurn, setRecoveryState) {
3273
3568
  this.retry = retry;
3274
3569
  this.emit = emit;
3275
3570
  this.removeTrailingAssistantError = removeTrailingAssistantError2;
3276
3571
  this.continueTurn = continueTurn;
3572
+ this.setRecoveryState = setRecoveryState;
3277
3573
  }
3278
3574
  retry;
3279
3575
  emit;
3280
3576
  removeTrailingAssistantError;
3281
3577
  continueTurn;
3578
+ setRecoveryState;
3282
3579
  retryAttempt = 0;
3283
3580
  retryTimer;
3284
3581
  pendingRetry;
3285
3582
  resolvePendingRetry;
3583
+ get policy() {
3584
+ return this.retry;
3585
+ }
3286
3586
  get hasPendingRetry() {
3287
3587
  return this.retryTimer !== void 0 || this.pendingRetry !== void 0;
3288
3588
  }
3289
3589
  waitForRetry() {
3290
3590
  return this.pendingRetry ?? Promise.resolve();
3291
3591
  }
3292
- async handleTurnEnd(message) {
3592
+ async handleTurnEnd(message, failure) {
3293
3593
  if (!this.retry || !message || message.role !== "assistant") {
3294
3594
  return;
3295
3595
  }
3296
- const errorMessage = message.stopReason === "error" ? message.errorMessage : void 0;
3297
- if (!errorMessage) {
3596
+ if (!failure) {
3298
3597
  if (this.retryAttempt > 0) {
3299
3598
  this.emit({ type: "retry_succeeded", attempts: this.retryAttempt });
3599
+ this.emit({ type: "turn_retry_succeeded", attempts: this.retryAttempt });
3300
3600
  this.retryAttempt = 0;
3601
+ this.setRecoveryState("none");
3301
3602
  this.resolvePending();
3302
3603
  }
3303
3604
  return;
3304
3605
  }
3305
- if (!this.retry.isRetryableError(errorMessage)) {
3606
+ if (!failure.retryable) {
3306
3607
  if (this.retryAttempt > 0) {
3307
3608
  this.retryAttempt = 0;
3609
+ this.setRecoveryState("none");
3308
3610
  this.resolvePending();
3309
3611
  }
3310
3612
  return;
@@ -3312,28 +3614,41 @@ var AgentSessionRetryController = class {
3312
3614
  const maxRetries = this.retry.maxRetries ?? 3;
3313
3615
  this.retryAttempt += 1;
3314
3616
  if (this.retryAttempt > maxRetries) {
3315
- this.emit({ type: "retry_exhausted", errorMessage, maxRetries });
3617
+ const terminalFailure = asTerminalFailure(failure);
3618
+ this.emit({ type: "retry_exhausted", errorMessage: failure.message, maxRetries });
3619
+ this.emit({ type: "turn_failed_terminal", failure: terminalFailure, message });
3620
+ this.emit({ type: "turn_settled" });
3316
3621
  this.retryAttempt = 0;
3622
+ this.setRecoveryState("terminal");
3317
3623
  this.resolvePending();
3318
3624
  return;
3319
3625
  }
3320
3626
  const attempt = this.retryAttempt;
3321
3627
  const delayMs = (this.retry.baseDelayMs ?? 2e3) * 2 ** (attempt - 1);
3322
3628
  this.ensurePending();
3323
- this.emit({ type: "retry_scheduled", errorMessage, attempt, maxRetries, delayMs });
3629
+ this.setRecoveryState("retry_pending");
3630
+ this.emit({ type: "turn_recovery_pending", recovery: "retry", failure, attempt, maxRetries, delayMs });
3631
+ this.emit({ type: "retry_scheduled", errorMessage: failure.message, attempt, maxRetries, delayMs });
3324
3632
  this.cancelTimerOnly();
3325
3633
  const setTimer = this.retry.setTimeout ?? defaultSetTimeout4;
3326
3634
  this.retryTimer = setTimer(() => {
3327
3635
  this.retryTimer = void 0;
3636
+ this.setRecoveryState("retrying");
3328
3637
  this.emit({ type: "retry_start", attempt, maxRetries });
3638
+ this.emit({ type: "turn_retry_started", attempt, maxRetries, failure });
3329
3639
  this.removeTrailingAssistantError();
3330
3640
  void this.continueTurn().catch((error) => {
3641
+ const errorMessage = error instanceof Error ? error.message : String(error);
3642
+ const terminalFailure = asTerminalFailure(failure, errorMessage);
3331
3643
  this.emit({
3332
3644
  type: "retry_exhausted",
3333
- errorMessage: error instanceof Error ? error.message : String(error),
3645
+ errorMessage,
3334
3646
  maxRetries
3335
3647
  });
3648
+ this.emit({ type: "turn_failed_terminal", failure: terminalFailure, message });
3649
+ this.emit({ type: "turn_settled" });
3336
3650
  this.retryAttempt = 0;
3651
+ this.setRecoveryState("terminal");
3337
3652
  this.resolvePending();
3338
3653
  });
3339
3654
  }, delayMs);
@@ -3464,6 +3779,33 @@ var AgentSessionRuntimeGuard = class {
3464
3779
  }
3465
3780
  };
3466
3781
 
3782
+ // ../../packages/agent-framework/src/session/controller/runtime-phase-policy.ts
3783
+ function classifyControllerRuntimeError(params) {
3784
+ const errorMessage = params.error instanceof Error ? params.error.message : String(params.error);
3785
+ return createFailureClassification({ errorMessage, phase: params.phase }, params.retry, params.classifyFailure);
3786
+ }
3787
+ async function runAgentSessionRuntimePhase(params) {
3788
+ try {
3789
+ return await params.action();
3790
+ } catch (error) {
3791
+ const failure = classifyControllerRuntimeError({
3792
+ error,
3793
+ phase: params.phase,
3794
+ retry: params.retry,
3795
+ classifyFailure: params.classifyFailure
3796
+ });
3797
+ if (failure.userAbort) {
3798
+ params.setRecoveryState("cancelled");
3799
+ params.emit({ type: "turn_cancelled", reason: failure.message });
3800
+ } else {
3801
+ params.setRecoveryState("terminal");
3802
+ params.emit({ type: "turn_failed_terminal", failure: asTerminalFailure(failure) });
3803
+ }
3804
+ params.emit({ type: "turn_settled" });
3805
+ throw error;
3806
+ }
3807
+ }
3808
+
3467
3809
  // ../../packages/agent-framework/src/session/controller/session-sync.ts
3468
3810
  function getConversationMessages(messages) {
3469
3811
  return messages.filter(
@@ -3568,6 +3910,161 @@ function reduceAgentSessionLifecycle(state, input) {
3568
3910
  }
3569
3911
  }
3570
3912
 
3913
+ // ../../packages/agent-framework/src/session/controller/turn-lifecycle-policy.ts
3914
+ function isAgentSessionRecoveryActive(state) {
3915
+ return state === "retry_pending" || state === "retrying" || state === "compacting";
3916
+ }
3917
+ function createRuntimeGuardFailure(reason) {
3918
+ return {
3919
+ kind: "runtime_guard",
3920
+ retryable: false,
3921
+ compactable: false,
3922
+ userAbort: false,
3923
+ terminal: true,
3924
+ message: reason
3925
+ };
3926
+ }
3927
+ function classifyControllerTurnFailure(params) {
3928
+ if (!isAssistantFailureMessage(params.message)) {
3929
+ return void 0;
3930
+ }
3931
+ const errorMessage = params.message.errorMessage || (params.message.stopReason === "aborted" ? "Request was aborted" : "assistant response failed");
3932
+ return createFailureClassification(
3933
+ { errorMessage, message: params.message, phase: "stream" },
3934
+ params.retry,
3935
+ params.classifyFailure
3936
+ );
3937
+ }
3938
+ async function handleAgentSessionTurnEndRuntimePolicies(params) {
3939
+ if (params.disposed || params.runtimeGuardTriggered) {
3940
+ return;
3941
+ }
3942
+ const failure = classifyControllerTurnFailure({
3943
+ message: params.message,
3944
+ retry: params.retry,
3945
+ classifyFailure: params.classifyFailure
3946
+ });
3947
+ if (failure?.userAbort) {
3948
+ params.cancelRetry();
3949
+ params.setRecoveryState("cancelled");
3950
+ params.emit({ type: "turn_cancelled", reason: failure.message, message: params.message });
3951
+ params.emit({ type: "turn_settled" });
3952
+ return;
3953
+ }
3954
+ const compactDecision = params.getAutoCompactDecision?.({
3955
+ agent: params.agent,
3956
+ sessionManager: params.sessionManager,
3957
+ cwd: params.cwd,
3958
+ message: params.message,
3959
+ messages: params.agent.state.messages.slice()
3960
+ });
3961
+ if (failure?.compactable) {
3962
+ params.scheduleAutoCompact(compactDecision ?? { reason: "overflow", willRetry: true }, failure);
3963
+ return;
3964
+ }
3965
+ if (failure && compactDecision?.reason === "overflow") {
3966
+ params.scheduleAutoCompact(compactDecision, failure);
3967
+ return;
3968
+ }
3969
+ if (failure?.retryable && compactDecision?.reason === "threshold") {
3970
+ params.scheduleAutoCompact({ reason: "threshold", willRetry: true }, failure);
3971
+ return;
3972
+ }
3973
+ if (failure) {
3974
+ if (failure.retryable) {
3975
+ await params.handleRetryTurnEnd(params.message, failure);
3976
+ return;
3977
+ }
3978
+ await params.handleRetryTurnEnd(params.message, failure);
3979
+ params.setRecoveryState("terminal");
3980
+ params.emit({ type: "turn_failed_terminal", failure: asTerminalFailure(failure), message: params.message });
3981
+ params.emit({ type: "turn_settled" });
3982
+ return;
3983
+ }
3984
+ await params.handleRetryTurnEnd(params.message);
3985
+ if (isAssistantTerminalCompletion(params.message)) {
3986
+ params.emit({ type: "turn_completed", message: params.message, toolResults: params.toolResults });
3987
+ const postTurnCompactDecision = compactDecision ?? params.getAutoCompactDecision?.({
3988
+ agent: params.agent,
3989
+ sessionManager: params.sessionManager,
3990
+ cwd: params.cwd,
3991
+ message: params.message,
3992
+ messages: params.agent.state.messages.slice()
3993
+ });
3994
+ if (postTurnCompactDecision?.reason === "threshold") {
3995
+ params.scheduleAutoCompact(postTurnCompactDecision);
3996
+ return;
3997
+ }
3998
+ params.setRecoveryState("none");
3999
+ params.emit({ type: "turn_settled" });
4000
+ }
4001
+ }
4002
+ async function runAgentSessionControllerAutoCompact(params) {
4003
+ const emitSettled = params.emitSettled ?? true;
4004
+ const runAfterAgentEndHook = params.runAfterAgentEndHook ?? true;
4005
+ const cancelOnAbort = params.cancelOnAbort ?? true;
4006
+ params.setRecoveryState("compacting");
4007
+ params.emit({ type: "turn_compaction_started", reason: params.reason, willRetry: params.willRetry });
4008
+ try {
4009
+ const result = await runAgentSessionCompaction({
4010
+ reason: params.reason,
4011
+ willRetry: params.willRetry,
4012
+ compaction: params.compaction,
4013
+ agent: params.agent,
4014
+ sessionManager: params.sessionManager,
4015
+ cwd: params.cwd,
4016
+ emit: params.emit,
4017
+ removeTrailingAssistantError: params.removeTrailingAssistantError,
4018
+ replaceMessagesAndMarkSynced: params.replaceMessagesAndMarkSynced,
4019
+ markSyncedToAgentState: params.markSyncedToAgentState,
4020
+ continueTurn: params.continueTurn
4021
+ });
4022
+ if (!params.willRetry || result?.aborted) {
4023
+ params.setRecoveryState(result?.aborted && cancelOnAbort ? "cancelled" : "none");
4024
+ if (result?.aborted) {
4025
+ params.cancelRetry();
4026
+ if (cancelOnAbort) {
4027
+ params.emit({ type: "turn_cancelled", reason: "compaction_aborted" });
4028
+ }
4029
+ }
4030
+ if (emitSettled) {
4031
+ params.emit({ type: "turn_settled" });
4032
+ }
4033
+ if (!result?.aborted && runAfterAgentEndHook) {
4034
+ void params.handleAgentEndHook(params.agent.state.messages.slice());
4035
+ }
4036
+ }
4037
+ return result;
4038
+ } catch (error) {
4039
+ const errorMessage = error instanceof Error ? error.message : String(error);
4040
+ if (!params.willRetry && params.reason !== "overflow") {
4041
+ params.setRecoveryState("none");
4042
+ if (emitSettled) {
4043
+ params.emit({ type: "turn_settled" });
4044
+ }
4045
+ if (runAfterAgentEndHook) {
4046
+ void params.handleAgentEndHook(params.agent.state.messages.slice());
4047
+ }
4048
+ throw error;
4049
+ }
4050
+ const failure = asTerminalFailure(params.pendingFailure ?? {
4051
+ kind: "internal_error",
4052
+ retryable: false,
4053
+ compactable: false,
4054
+ userAbort: false,
4055
+ terminal: true,
4056
+ message: errorMessage
4057
+ }, errorMessage);
4058
+ params.cancelRetry();
4059
+ params.setRecoveryState("terminal");
4060
+ params.emit({ type: "turn_failed_terminal", failure });
4061
+ params.emit({ type: "turn_settled" });
4062
+ throw error;
4063
+ } finally {
4064
+ params.clearPendingFailure();
4065
+ }
4066
+ }
4067
+
3571
4068
  // ../../packages/agent-framework/src/session/controller.ts
3572
4069
  var AgentSessionController = class {
3573
4070
  agent;
@@ -3589,6 +4086,9 @@ var AgentSessionController = class {
3589
4086
  prepareTurn;
3590
4087
  afterAgentEnd;
3591
4088
  getAutoCompactDecision;
4089
+ classifyFailure;
4090
+ recoveryState = "none";
4091
+ pendingCompactionFailure;
3592
4092
  lifecycleState = createInitialAgentSessionLifecycleState();
3593
4093
  constructor(options) {
3594
4094
  this.agent = options.agent;
@@ -3599,6 +4099,7 @@ var AgentSessionController = class {
3599
4099
  this.prepareTurn = options.prepareTurn;
3600
4100
  this.afterAgentEnd = options.afterAgentEnd;
3601
4101
  this.getAutoCompactDecision = options.getAutoCompactDecision;
4102
+ this.classifyFailure = options.classifyFailure;
3602
4103
  if (options.onEvent) {
3603
4104
  this.listeners.add(options.onEvent);
3604
4105
  }
@@ -3614,7 +4115,10 @@ var AgentSessionController = class {
3614
4115
  options.retry,
3615
4116
  (event) => this.emit(event),
3616
4117
  () => this.removeTrailingAssistantError(),
3617
- () => this.continue()
4118
+ () => this.continue(),
4119
+ (state) => {
4120
+ this.recoveryState = state;
4121
+ }
3618
4122
  );
3619
4123
  this.autoCompact = new AgentSessionAutoCompactScheduler({
3620
4124
  retry: options.retry,
@@ -3634,6 +4138,7 @@ var AgentSessionController = class {
3634
4138
  emit: (event) => this.emit(event),
3635
4139
  getDisposed: () => this.disposed,
3636
4140
  getRuntimeGuardTriggered: () => this.runtimeGuard.triggered,
4141
+ isBlockedByRuntimePolicy: () => this.isBlockedByRuntimePolicy(),
3637
4142
  hasQueuedMessages: () => this.hasQueuedMessages,
3638
4143
  isStreaming: () => this.isStreaming,
3639
4144
  continueTurn: () => this.continue()
@@ -3649,7 +4154,8 @@ var AgentSessionController = class {
3649
4154
  getDisposed: () => this.disposed,
3650
4155
  getRuntimeGuardTriggered: () => this.runtimeGuard.triggered,
3651
4156
  isStreaming: () => this.isStreaming,
3652
- isBlockedByRuntimePolicy: () => this.retryController.hasPendingRetry || this.autoCompact.hasPendingAutoCompact,
4157
+ isBlockedByRuntimePolicy: () => this.isBlockedByRuntimePolicy(),
4158
+ runPreRequestAutoCompact: () => this.runPreRequestAutoCompactIfNeeded(),
3653
4159
  preparePromptMessages: (messages, promptOptions) => this.preparePromptMessages(messages, promptOptions),
3654
4160
  continueTurn: () => this.continue(),
3655
4161
  syncNewAgentMessages: () => this.syncNewAgentMessages()
@@ -3672,6 +4178,9 @@ var AgentSessionController = class {
3672
4178
  get queuedInputsSnapshot() {
3673
4179
  return this.queue.snapshot;
3674
4180
  }
4181
+ get turnRecoveryState() {
4182
+ return this.recoveryState;
4183
+ }
3675
4184
  get activeSessionMetadata() {
3676
4185
  const metadata = this.sessionManager.getActiveSession()?.metadata;
3677
4186
  return metadata ? { ...metadata } : void 0;
@@ -3680,10 +4189,7 @@ var AgentSessionController = class {
3680
4189
  return this.lastSyncedMessageCount;
3681
4190
  }
3682
4191
  get runtimeContext() {
3683
- return {
3684
- systemPrompt: this.agent.state.systemPrompt,
3685
- tools: this.agent.state.tools.slice()
3686
- };
4192
+ return { systemPrompt: this.agent.state.systemPrompt, tools: this.agent.state.tools.slice() };
3687
4193
  }
3688
4194
  getRuntimeSnapshot() {
3689
4195
  return {
@@ -3730,6 +4236,17 @@ var AgentSessionController = class {
3730
4236
  }
3731
4237
  async prompt(input, options) {
3732
4238
  const messages = normalizePromptInput(input);
4239
+ if (this.isRecoveryActive()) {
4240
+ this.queue.enqueue(messages, options?.streamingBehavior ?? "followUp", {
4241
+ ...options,
4242
+ streamingBehavior: options?.streamingBehavior ?? "followUp"
4243
+ });
4244
+ this.recordLifecycle({ type: "queued_input_added" });
4245
+ return;
4246
+ }
4247
+ if (this.recoveryState === "terminal" || this.recoveryState === "cancelled") {
4248
+ this.recoveryState = "none";
4249
+ }
3733
4250
  if (options?.streamingBehavior) {
3734
4251
  this.queue.enqueue(messages, options.streamingBehavior, options);
3735
4252
  this.recordLifecycle({ type: "queued_input_added" });
@@ -3741,7 +4258,8 @@ var AgentSessionController = class {
3741
4258
  if (this.isStreaming) {
3742
4259
  throw new Error("Agent is already processing. Specify streamingBehavior ('steer' or 'followUp') to queue the message.");
3743
4260
  }
3744
- const prepared = await this.preparePromptMessages(messages, options);
4261
+ await this.runPreRequestAutoCompactIfNeeded();
4262
+ const prepared = await this.runRuntimePhase("prepare", () => this.preparePromptMessages(messages, options));
3745
4263
  if (prepared.handled) {
3746
4264
  return;
3747
4265
  }
@@ -3749,23 +4267,30 @@ var AgentSessionController = class {
3749
4267
  if (prepared.systemPrompt !== void 0) {
3750
4268
  this.agent.setSystemPrompt(prepared.systemPrompt);
3751
4269
  }
3752
- await this.agent.prompt(prepared.messages);
3753
- await this.syncNewAgentMessages();
4270
+ await this.runRuntimePhase("request", () => this.agent.prompt(prepared.messages));
4271
+ await this.runRuntimePhase("post_turn", () => this.syncNewAgentMessages());
3754
4272
  }
3755
4273
  async continue() {
3756
4274
  if (this.isStreaming) {
3757
4275
  throw new Error("Agent is already processing. Wait for completion before continuing.");
3758
4276
  }
4277
+ await this.runPreRequestAutoCompactIfNeeded();
3759
4278
  this.recordLifecycle({ type: "prompt_started" });
3760
- await this.agent.continue();
3761
- await this.syncNewAgentMessages();
4279
+ await this.runRuntimePhase("request", () => this.agent.continue());
4280
+ await this.runRuntimePhase("post_turn", () => this.syncNewAgentMessages());
3762
4281
  }
3763
4282
  abort() {
3764
4283
  this.recordLifecycle({ type: "abort" });
4284
+ const cancelPendingRecovery = this.isRecoveryActive() && !this.isStreaming;
3765
4285
  this.retryController.cancel();
3766
4286
  this.autoCompact.cancel();
3767
4287
  this.queueDispatcher.cancel();
3768
4288
  this.autoContinue.cancel();
4289
+ if (cancelPendingRecovery) {
4290
+ this.recoveryState = "cancelled";
4291
+ this.emit({ type: "turn_cancelled", reason: "user_abort" });
4292
+ this.emit({ type: "turn_settled" });
4293
+ }
3769
4294
  this.agent.abort();
3770
4295
  }
3771
4296
  waitForIdle() {
@@ -3774,25 +4299,37 @@ var AgentSessionController = class {
3774
4299
  async waitForSettled() {
3775
4300
  while (true) {
3776
4301
  await this.agent.waitForIdle();
3777
- if (!this.retryController.hasPendingRetry) {
4302
+ if (!this.hasPendingRuntimePolicy()) {
3778
4303
  return;
3779
4304
  }
3780
- await this.retryController.waitForRetry();
4305
+ await Promise.all([this.retryController.waitForRetry(), this.autoCompact.waitForAutoCompact(), this.autoContinue.waitForAutoContinue(), this.queueDispatcher.waitForDispatch()]);
3781
4306
  }
3782
4307
  }
3783
- async runAutoCompact(reason, willRetry = false) {
3784
- return runAgentSessionCompaction({
4308
+ async runAutoCompact(reason, willRetry = false, options) {
4309
+ return runAgentSessionControllerAutoCompact({
3785
4310
  reason,
3786
4311
  willRetry,
3787
4312
  compaction: this.compaction,
3788
4313
  agent: this.agent,
3789
4314
  sessionManager: this.sessionManager,
3790
4315
  cwd: this.cwd,
4316
+ pendingFailure: this.pendingCompactionFailure,
3791
4317
  emit: (event) => this.emit(event),
4318
+ setRecoveryState: (state) => {
4319
+ this.recoveryState = state;
4320
+ },
4321
+ cancelRetry: () => this.retryController.cancel(),
4322
+ clearPendingFailure: () => {
4323
+ this.pendingCompactionFailure = void 0;
4324
+ },
3792
4325
  removeTrailingAssistantError: () => this.removeTrailingAssistantError(),
3793
- replaceMessagesAndMarkSynced: (messages, options) => this.replaceMessagesAndMarkSynced(messages, options),
4326
+ replaceMessagesAndMarkSynced: (messages, options2) => this.replaceMessagesAndMarkSynced(messages, options2),
3794
4327
  markSyncedToAgentState: () => this.markSyncedToAgentState(),
3795
- continueTurn: () => this.continue()
4328
+ continueTurn: () => this.continue(),
4329
+ handleAgentEndHook: (messages) => this.handleAgentEndHook(messages),
4330
+ emitSettled: options?.emitSettled,
4331
+ runAfterAgentEndHook: options?.runAfterAgentEndHook,
4332
+ cancelOnAbort: options?.cancelOnAbort
3796
4333
  });
3797
4334
  }
3798
4335
  dispose() {
@@ -3814,9 +4351,23 @@ var AgentSessionController = class {
3814
4351
  this.queue.replaceText(search, replacement);
3815
4352
  }
3816
4353
  requestQueueDispatch() {
3817
- if (this.queue.length > 0) {
3818
- this.queueDispatcher.schedule();
3819
- }
4354
+ if (this.queue.length > 0) this.queueDispatcher.schedule();
4355
+ }
4356
+ async runPreRequestAutoCompactIfNeeded() {
4357
+ return runPreRequestAutoCompact({
4358
+ disposed: this.disposed,
4359
+ runtimeGuardTriggered: this.runtimeGuard.triggered,
4360
+ isStreaming: this.isStreaming,
4361
+ recoveryActive: this.isRecoveryActive(),
4362
+ agent: this.agent,
4363
+ sessionManager: this.sessionManager,
4364
+ cwd: this.cwd,
4365
+ getAutoCompactDecision: this.getAutoCompactDecision,
4366
+ runAutoCompact: () => this.runAutoCompact("threshold", false, { emitSettled: false, runAfterAgentEndHook: false, cancelOnAbort: false }).then(() => void 0),
4367
+ setRecoveryState: (state) => {
4368
+ this.recoveryState = state;
4369
+ }
4370
+ });
3820
4371
  }
3821
4372
  handleAgentEvent(event) {
3822
4373
  this.emit({ type: "agent_event", event });
@@ -3837,7 +4388,7 @@ var AgentSessionController = class {
3837
4388
  if (event.type === "turn_end") {
3838
4389
  void this.syncNewAgentMessages();
3839
4390
  this.recordLifecycle({ type: "turn_end_success" });
3840
- void this.handleTurnEndRuntimePolicies(event.message);
4391
+ void this.handleTurnEndRuntimePolicies(event.message, event.toolResults);
3841
4392
  }
3842
4393
  if (event.type === "agent_end") {
3843
4394
  this.recordLifecycle({ type: "agent_end" });
@@ -3874,31 +4425,29 @@ var AgentSessionController = class {
3874
4425
  replaceMessagesAndMarkSynced: (messages, options) => this.replaceMessagesAndMarkSynced(messages, options)
3875
4426
  });
3876
4427
  }
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?.({
4428
+ async handleTurnEndRuntimePolicies(message, toolResults = []) {
4429
+ return handleAgentSessionTurnEndRuntimePolicies({
4430
+ disposed: this.disposed,
4431
+ runtimeGuardTriggered: this.runtimeGuard.triggered,
4432
+ message,
4433
+ toolResults,
3886
4434
  agent: this.agent,
3887
4435
  sessionManager: this.sessionManager,
3888
4436
  cwd: this.cwd,
3889
- message,
3890
- messages: this.agent.state.messages.slice()
4437
+ getAutoCompactDecision: this.getAutoCompactDecision,
4438
+ classifyFailure: this.classifyFailure,
4439
+ retry: this.retryController.policy,
4440
+ emit: (event) => this.emit(event),
4441
+ setRecoveryState: (state) => {
4442
+ this.recoveryState = state;
4443
+ },
4444
+ handleRetryTurnEnd: (turnMessage, failure) => this.retryController.handleTurnEnd(turnMessage, failure),
4445
+ cancelRetry: () => this.retryController.cancel(),
4446
+ scheduleAutoCompact: (decision, failure) => this.scheduleAutoCompact(decision, failure)
3891
4447
  });
3892
- if (compactDecision) {
3893
- this.scheduleAutoCompact(compactDecision);
3894
- return;
3895
- }
3896
- if (assistantError) {
3897
- await this.retryController.handleTurnEnd(message);
3898
- }
3899
4448
  }
3900
4449
  async handleAgentEndHook(messages) {
3901
- if (this.disposed || this.runtimeGuard.triggered) {
4450
+ if (this.disposed || this.runtimeGuard.triggered || this.isBlockedByRuntimePolicy()) {
3902
4451
  return;
3903
4452
  }
3904
4453
  if (this.afterAgentEnd) {
@@ -3933,8 +4482,36 @@ var AgentSessionController = class {
3933
4482
  recordToolExecutionEndForGuard(event) {
3934
4483
  this.runtimeGuard.recordToolExecutionEnd(event);
3935
4484
  }
3936
- scheduleAutoCompact(decision) {
4485
+ async runRuntimePhase(phase, action) {
4486
+ return runAgentSessionRuntimePhase({
4487
+ phase,
4488
+ action,
4489
+ retry: this.retryController.policy,
4490
+ classifyFailure: this.classifyFailure,
4491
+ emit: (event) => this.emit(event),
4492
+ setRecoveryState: (state) => {
4493
+ this.recoveryState = state;
4494
+ }
4495
+ });
4496
+ }
4497
+ isRecoveryActive() {
4498
+ return isAgentSessionRecoveryActive(this.recoveryState);
4499
+ }
4500
+ isBlockedByRuntimePolicy() {
4501
+ return this.recoveryState !== "none" || this.retryController.hasPendingRetry || this.autoCompact.hasPendingAutoCompact;
4502
+ }
4503
+ hasPendingRuntimePolicy() {
4504
+ return this.retryController.hasPendingRetry || this.autoCompact.hasPendingAutoCompact || this.autoContinue.hasPendingAutoContinue || this.queueDispatcher.hasPendingDispatch;
4505
+ }
4506
+ scheduleAutoCompact(decision, failure) {
3937
4507
  this.recordLifecycle({ type: "auto_compact_scheduled" });
4508
+ if (failure) {
4509
+ this.pendingCompactionFailure = failure;
4510
+ this.recoveryState = "compacting";
4511
+ this.emit({ type: "turn_recovery_pending", recovery: "compaction", failure });
4512
+ } else if (decision.reason === "threshold") {
4513
+ this.recoveryState = "compacting";
4514
+ }
3938
4515
  this.autoCompact.schedule(decision);
3939
4516
  }
3940
4517
  triggerRuntimeGuard(toolName, repeatCount, reason) {
@@ -3946,7 +4523,13 @@ var AgentSessionController = class {
3946
4523
  this.autoCompact.cancel();
3947
4524
  this.queueDispatcher.cancel();
3948
4525
  this.autoContinue.cancel();
4526
+ this.recoveryState = "terminal";
3949
4527
  this.emit({ type: "runtime_guard_triggered", toolName, repeatCount, reason });
4528
+ this.emit({
4529
+ type: "turn_failed_terminal",
4530
+ failure: createRuntimeGuardFailure(reason)
4531
+ });
4532
+ this.emit({ type: "turn_settled" });
3950
4533
  this.agent.abort();
3951
4534
  }
3952
4535
  emit(event) {
@@ -9023,7 +9606,10 @@ export {
9023
9606
  ManageTodoListParamsSchema,
9024
9607
  executeManageTodoList,
9025
9608
  maybeAdvanceTodoExecutionState,
9609
+ estimateTokens,
9026
9610
  calculateTotalTokens,
9611
+ estimateContextTokens,
9612
+ getAutoCompactTokenLimit,
9027
9613
  findFirstKeptEntryId,
9028
9614
  createCompactionSummary,
9029
9615
  createSessionManager,