@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.
package/dist/cli.js CHANGED
@@ -14,7 +14,7 @@ import {
14
14
  getSettingsPath,
15
15
  getThemesDir,
16
16
  migrateConfigFromAgentDir
17
- } from "./chunks/chunk-55RUSXEA.js";
17
+ } from "./chunks/chunk-62XU6P2H.js";
18
18
  import {
19
19
  AGENTS_CONTEXT_FILE_NAME,
20
20
  AgentSessionController,
@@ -37,11 +37,14 @@ import {
37
37
  createSharedFileSystemTools,
38
38
  createSharedWebSearchTool,
39
39
  createTodoWidgetView,
40
+ estimateContextTokens,
41
+ estimateTokens,
40
42
  evaluateTodoClosureAfterAbort,
41
43
  evaluateTodoClosureAfterCompletedTurn,
42
44
  evaluateTodoClosureAfterFailedTurn,
43
45
  findFirstKeptEntryId,
44
46
  formatSkillSummariesForPrompt,
47
+ getAutoCompactTokenLimit,
45
48
  hasInteractionHandler,
46
49
  maybeAdvanceTodoExecutionState,
47
50
  modalityForReadFileUnderstandingKind,
@@ -52,7 +55,7 @@ import {
52
55
  selectToolsForRuntimePolicy,
53
56
  shouldPreserveExecutionStateForUserText,
54
57
  supersedeExecutionState
55
- } from "./chunks/chunk-R5LYKDKA.js";
58
+ } from "./chunks/chunk-7GAYJ6AF.js";
56
59
  import "./chunks/chunk-VE2HDCNB.js";
57
60
  import {
58
61
  Deref,
@@ -61388,7 +61391,7 @@ function bindInteractiveRuntimeEvents(host) {
61388
61391
  lastStopReason: host.agent.state.messages.at(-1)?.role === "assistant" ? host.agent.state.messages.at(-1).stopReason : void 0
61389
61392
  });
61390
61393
  const lastMessage = host.agent.state.messages[host.agent.state.messages.length - 1];
61391
- if (lastMessage?.role === "assistant" && lastMessage?.stopReason === "error") {
61394
+ if (!hasControllerTurnAuthority(host) && lastMessage?.role === "assistant" && lastMessage?.stopReason === "error") {
61392
61395
  host.sessionTrace.abortTurn(String(lastMessage?.errorMessage || "agent_end"));
61393
61396
  }
61394
61397
  host.isProcessing = false;
@@ -61433,6 +61436,12 @@ function handleSemanticAgentEvent(host, event) {
61433
61436
  break;
61434
61437
  }
61435
61438
  case "turn_completed": {
61439
+ if (hasControllerTurnAuthority(host)) {
61440
+ host.logDebug("semantic:turn_completed_ignored_controller_authority", {
61441
+ turnIndex: host._semanticTurnIndex
61442
+ });
61443
+ break;
61444
+ }
61436
61445
  host.clearContinuationTimeout("turn_completed");
61437
61446
  host.emitExtensionEvent({
61438
61447
  type: "turn:completed",
@@ -61460,6 +61469,13 @@ function handleSemanticAgentEvent(host, event) {
61460
61469
  break;
61461
61470
  }
61462
61471
  case "turn_failed": {
61472
+ if (hasControllerTurnAuthority(host)) {
61473
+ host.logDebug("semantic:turn_failed_ignored_controller_authority", {
61474
+ turnIndex: host._semanticTurnIndex,
61475
+ recoveryState: host.agentSessionController?.turnRecoveryState
61476
+ });
61477
+ break;
61478
+ }
61463
61479
  host.clearContinuationTimeout("turn_failed");
61464
61480
  host.emitExtensionEvent({
61465
61481
  type: "turn:failed",
@@ -61488,6 +61504,9 @@ function handleSemanticAgentEvent(host, event) {
61488
61504
  }
61489
61505
  case "status_snapshot_changed": {
61490
61506
  const snapshot = event.snapshot;
61507
+ if (isControllerRecovering(host)) {
61508
+ break;
61509
+ }
61491
61510
  if (snapshot.phase === "idle" && !snapshot.hasPendingToolContinuation) {
61492
61511
  host.clearContinuationTimeout("status_idle");
61493
61512
  const outcome = host.executionState.markAwaitingContinuationStalled();
@@ -61509,6 +61528,13 @@ function handleSemanticAgentEvent(host, event) {
61509
61528
  }
61510
61529
  }
61511
61530
  }
61531
+ function hasControllerTurnAuthority(host) {
61532
+ return host.agentSessionController?.turnRecoveryState !== void 0;
61533
+ }
61534
+ function isControllerRecovering(host) {
61535
+ const state = host.agentSessionController?.turnRecoveryState;
61536
+ return state === "retry_pending" || state === "retrying" || state === "compacting";
61537
+ }
61512
61538
  function handleMessageStart(host, event) {
61513
61539
  if (event.message?.role === "assistant") {
61514
61540
  host.clearContinuationTimeout("assistant_message_start");
@@ -64044,9 +64070,103 @@ function buildCliSystemPrompt(options) {
64044
64070
  ].filter(Boolean).join("\n\n");
64045
64071
  }
64046
64072
 
64073
+ // src/runtime/cli-auto-compaction.ts
64074
+ function calculateCliContextUsage(params) {
64075
+ const messages = stripStaleUsageBeforeCompaction(
64076
+ params.messages,
64077
+ params.latestCompactionTimestamp ?? null
64078
+ );
64079
+ const estimate = estimateContextTokens(messages);
64080
+ const usageMessage = estimate.lastUsageIndex !== null ? messages[estimate.lastUsageIndex] : void 0;
64081
+ const usage = usageMessage?.role === "assistant" ? usageMessage.usage : void 0;
64082
+ return {
64083
+ tokens: estimate.tokens,
64084
+ input: usage?.input ?? 0,
64085
+ output: usage?.output ?? 0,
64086
+ cacheRead: usage?.cacheRead ?? 0,
64087
+ cacheWrite: usage?.cacheWrite ?? 0,
64088
+ cost: usage?.cost.total ?? 0
64089
+ };
64090
+ }
64091
+ function createCliAutoCompactDecision(params) {
64092
+ const errorMessage3 = params.message?.role === "assistant" ? params.message.errorMessage : void 0;
64093
+ if (errorMessage3 && params.isContextOverflowError(errorMessage3)) {
64094
+ return { reason: "overflow", willRetry: true };
64095
+ }
64096
+ if (!params.autoCompactEnabled || params.contextWindow <= 0) {
64097
+ return void 0;
64098
+ }
64099
+ const usage = calculateCliContextUsage({
64100
+ messages: params.messages,
64101
+ latestCompactionTimestamp: params.latestCompactionTimestamp
64102
+ });
64103
+ if (!usage.tokens) {
64104
+ return void 0;
64105
+ }
64106
+ const tokenLimit = getAutoCompactTokenLimit(params.contextWindow, params.reserveTokens);
64107
+ if (tokenLimit === void 0) {
64108
+ return void 0;
64109
+ }
64110
+ return usage.tokens > tokenLimit ? { reason: "threshold", willRetry: false } : void 0;
64111
+ }
64112
+ function shouldCliAutoCompactTokens(params) {
64113
+ if (!params.tokens || params.contextWindow <= 0) {
64114
+ return false;
64115
+ }
64116
+ const tokenLimit = getAutoCompactTokenLimit(params.contextWindow, params.reserveTokens);
64117
+ return tokenLimit !== void 0 && params.tokens > tokenLimit;
64118
+ }
64119
+ async function createCliAutoCompactionResult(params) {
64120
+ const activeEntryId = params.context.sessionManager.getActiveEntryId();
64121
+ if (!activeEntryId) {
64122
+ return { aborted: true };
64123
+ }
64124
+ const pathEntries = params.context.sessionManager.getPathToEntry(activeEntryId);
64125
+ const firstKeptEntryId = findFirstKeptEntryId(pathEntries);
64126
+ if (!firstKeptEntryId) {
64127
+ return { aborted: true };
64128
+ }
64129
+ const compactOptions = {
64130
+ model: params.model,
64131
+ apiKey: params.apiKey,
64132
+ systemPrompt: params.systemPrompt
64133
+ };
64134
+ const summary = await createCompactionSummary(params.context.messages, compactOptions);
64135
+ return {
64136
+ summary,
64137
+ firstKeptEntryId,
64138
+ tokensBefore: calculateTotalTokens(params.context.messages)
64139
+ };
64140
+ }
64141
+ function stripStaleUsageBeforeCompaction(messages, latestCompactionTimestamp) {
64142
+ if (latestCompactionTimestamp === null) {
64143
+ return messages;
64144
+ }
64145
+ return messages.map((message) => {
64146
+ if (message.role !== "assistant" || message.timestamp > latestCompactionTimestamp) {
64147
+ return message;
64148
+ }
64149
+ return {
64150
+ ...message,
64151
+ usage: {
64152
+ input: 0,
64153
+ output: 0,
64154
+ cacheRead: 0,
64155
+ cacheWrite: 0,
64156
+ totalTokens: 0,
64157
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 }
64158
+ }
64159
+ };
64160
+ });
64161
+ }
64162
+
64047
64163
  // src/runtime/retry-policy.ts
64048
- var TRANSIENT_MODEL_ERROR_PATTERN = /overloaded|rate.?limit|too many requests|429|500|502|503|504|service.?unavailable|server error|internal error|connection.?error|connection.?refused|other side closed|fetch failed|upstream.?connect|reset before headers|terminated|retry delay|sse stream aborted|sse stream closed before end|sse stream ended before response completed/i;
64049
- var USER_ABORT_PATTERN = /request was aborted/i;
64164
+ var TRANSIENT_NETWORK_ERROR_PATTERN = /sse stream aborted|sse stream closed before end|sse stream ended before response completed|(?:read\s+)?ECONNRESET|ETIMEDOUT|EAI_AGAIN|ENOTFOUND|ECONNREFUSED|socket hang up|request timeout|client network socket disconnected before secure TLS connection was established|connection.?error|connection.?refused|other side closed|fetch failed|upstream.?connect|reset before headers|terminated/i;
64165
+ var PROVIDER_TRANSIENT_ERROR_PATTERN = /overloaded|rate.?limit|too many requests|\b429\b|\b500\b|\b502\b|\b503\b|\b504\b|service.?unavailable|server error|internal error|retry delay/i;
64166
+ var USER_ABORT_PATTERN = /request was aborted|local abort signal|user abort|user_interrupt|interrupted by user|esc pressed|ctrl\+c/i;
64167
+ var AUTH_CONFIG_PATTERN = /missing api key|invalid api key|api key.*invalid|unauthorized|\b401\b|\b403\b|forbidden|authentication failed|no configured model|no models? are configured/i;
64168
+ var INVALID_FILE_REFERENCE_PATTERN = /not the owner of this file|owner of this file|invalid_authentication_error/i;
64169
+ var INVALID_REQUEST_PATTERN = /bad request|\b400\b|invalid request|invalid schema|schema validation|invalid parameter|unsupported parameter|malformed/i;
64050
64170
  var CLI_RETRY_MAX_RETRIES = 3;
64051
64171
  var CLI_RETRY_BASE_DELAY_MS = 2e3;
64052
64172
  function isCliContextOverflowError(errorMessage3) {
@@ -64054,14 +64174,56 @@ function isCliContextOverflowError(errorMessage3) {
64054
64174
  errorMessage3
64055
64175
  );
64056
64176
  }
64057
- function isCliRetryableError(errorMessage3) {
64177
+ function createClassification(kind, message, overrides) {
64178
+ return {
64179
+ kind,
64180
+ message,
64181
+ retryable: false,
64182
+ compactable: false,
64183
+ userAbort: false,
64184
+ terminal: true,
64185
+ ...overrides
64186
+ };
64187
+ }
64188
+ function classifyCliFailure(context) {
64189
+ const errorMessage3 = typeof context === "string" ? context : context.errorMessage;
64058
64190
  if (isCliContextOverflowError(errorMessage3)) {
64059
- return false;
64191
+ return createClassification("context_overflow", errorMessage3, {
64192
+ compactable: true,
64193
+ terminal: false
64194
+ });
64060
64195
  }
64061
64196
  if (USER_ABORT_PATTERN.test(errorMessage3)) {
64062
- return false;
64197
+ return createClassification("user_abort", errorMessage3, {
64198
+ userAbort: true,
64199
+ terminal: false
64200
+ });
64063
64201
  }
64064
- return TRANSIENT_MODEL_ERROR_PATTERN.test(errorMessage3);
64202
+ if (INVALID_FILE_REFERENCE_PATTERN.test(errorMessage3)) {
64203
+ return createClassification("invalid_file_reference", errorMessage3);
64204
+ }
64205
+ if (AUTH_CONFIG_PATTERN.test(errorMessage3)) {
64206
+ return createClassification("auth_config", errorMessage3);
64207
+ }
64208
+ if (TRANSIENT_NETWORK_ERROR_PATTERN.test(errorMessage3)) {
64209
+ return createClassification("transient_network", errorMessage3, {
64210
+ retryable: true,
64211
+ terminal: false
64212
+ });
64213
+ }
64214
+ if (PROVIDER_TRANSIENT_ERROR_PATTERN.test(errorMessage3)) {
64215
+ return createClassification("provider_transient", errorMessage3, {
64216
+ retryable: true,
64217
+ terminal: false
64218
+ });
64219
+ }
64220
+ if (INVALID_REQUEST_PATTERN.test(errorMessage3)) {
64221
+ return createClassification("invalid_request", errorMessage3);
64222
+ }
64223
+ return createClassification("unknown", errorMessage3);
64224
+ }
64225
+ function isCliRetryableError(errorMessage3) {
64226
+ return classifyCliFailure(errorMessage3).retryable;
64065
64227
  }
64066
64228
  function getCliRetryBaseDelayMs() {
64067
64229
  const override = process.env.PIE_TEST_CLI_RETRY_BASE_DELAY_MS;
@@ -64078,78 +64240,13 @@ function calculateContextUsage(host) {
64078
64240
  if (messages.length === 0) {
64079
64241
  return { tokens: 0, input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0 };
64080
64242
  }
64081
- let lastAssistantIndex = -1;
64082
- let lastUsage = null;
64083
- const latestCompactionTimestamp = host.sessionManager.getLatestCompactionTimestamp();
64084
- for (let i = messages.length - 1; i >= 0; i--) {
64085
- const msg = messages[i];
64086
- if (msg.role === "assistant") {
64087
- const assistantMsg = msg;
64088
- if (latestCompactionTimestamp !== null && assistantMsg.timestamp <= latestCompactionTimestamp) {
64089
- continue;
64090
- }
64091
- if (assistantMsg.usage && assistantMsg.stopReason !== "aborted" && assistantMsg.stopReason !== "error") {
64092
- lastAssistantIndex = i;
64093
- lastUsage = assistantMsg.usage;
64094
- break;
64095
- }
64096
- }
64097
- }
64098
- if (!lastUsage || lastAssistantIndex < 0) {
64099
- const estimated = messages.reduce((sum, msg) => sum + estimateMessageTokens(msg), 0);
64100
- return { tokens: estimated, input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0 };
64101
- }
64102
- const usageTokens = lastUsage.totalTokens || lastUsage.input + lastUsage.output + lastUsage.cacheRead + lastUsage.cacheWrite;
64103
- if (usageTokens <= 0) {
64104
- const estimated = messages.reduce((sum, msg) => sum + estimateMessageTokens(msg), 0);
64105
- return {
64106
- tokens: estimated,
64107
- input: lastUsage.input,
64108
- output: lastUsage.output,
64109
- cacheRead: lastUsage.cacheRead,
64110
- cacheWrite: lastUsage.cacheWrite,
64111
- cost: lastUsage.cost.total
64112
- };
64113
- }
64114
- let trailingTokens = 0;
64115
- for (let i = lastAssistantIndex + 1; i < messages.length; i++) {
64116
- trailingTokens += estimateMessageTokens(messages[i]);
64117
- }
64118
- return {
64119
- tokens: usageTokens + trailingTokens,
64120
- input: lastUsage.input,
64121
- output: lastUsage.output,
64122
- cacheRead: lastUsage.cacheRead,
64123
- cacheWrite: lastUsage.cacheWrite,
64124
- cost: lastUsage.cost.total
64125
- };
64243
+ return calculateCliContextUsage({
64244
+ messages,
64245
+ latestCompactionTimestamp: host.sessionManager.getLatestCompactionTimestamp()
64246
+ });
64126
64247
  }
64127
64248
  function estimateMessageTokens(msg) {
64128
- let chars = 0;
64129
- if (msg.role === "user") {
64130
- const content = msg.content;
64131
- if (typeof content === "string") {
64132
- chars = content.length;
64133
- } else if (Array.isArray(content)) {
64134
- for (const block of content) {
64135
- if (block.type === "text" && block.text) {
64136
- chars += block.text.length;
64137
- }
64138
- }
64139
- }
64140
- } else if (msg.role === "assistant") {
64141
- const assistant = msg;
64142
- for (const block of assistant.content) {
64143
- if (block.type === "text") {
64144
- chars += block.text.length;
64145
- } else if (block.type === "thinking") {
64146
- chars += block.thinking.length;
64147
- } else if (block.type === "toolCall") {
64148
- chars += block.name.length + JSON.stringify(block.arguments).length;
64149
- }
64150
- }
64151
- }
64152
- return Math.ceil(chars / 4);
64249
+ return estimateTokens(msg);
64153
64250
  }
64154
64251
  function buildSystemPrompt(host) {
64155
64252
  const tools = (host.agent?.state?.tools ?? host.options.tools ?? []).filter((tool) => typeof tool.name === "string").map((tool) => ({
@@ -64299,9 +64396,8 @@ function isContextOverflowError(errorMessage3) {
64299
64396
  }
64300
64397
  function shouldAutoCompact(host, contextTokens, contextWindow) {
64301
64398
  if (!host.options.settingsManager.getAutoCompactEnabled()) return false;
64302
- if (!contextTokens || contextWindow <= 0) return false;
64303
64399
  const reserveTokens = host.options.settingsManager.getAutoCompactReserveTokens();
64304
- return contextTokens > contextWindow - reserveTokens;
64400
+ return shouldCliAutoCompactTokens({ tokens: contextTokens, contextWindow, reserveTokens });
64305
64401
  }
64306
64402
  function isRetryableError(errorMessage3) {
64307
64403
  return isCliRetryableError(errorMessage3);
@@ -64746,28 +64842,6 @@ async function prepareAgentTurn(host, context) {
64746
64842
  messages: result.messages?.length ? [...context.messages, ...result.messages] : context.messages
64747
64843
  };
64748
64844
  }
64749
- async function createAutoCompactionResult(host, messages) {
64750
- const activeEntryId = host.sessionManager.getActiveEntryId();
64751
- if (!activeEntryId) {
64752
- return { aborted: true };
64753
- }
64754
- const pathEntries = host.sessionManager.getPathToEntry(activeEntryId);
64755
- const firstKeptEntryId = findFirstKeptEntryId(pathEntries);
64756
- if (!firstKeptEntryId) {
64757
- return { aborted: true };
64758
- }
64759
- const compactOptions = {
64760
- model: host.options.model,
64761
- apiKey: host.options.apiKey,
64762
- systemPrompt: host.agent.state.systemPrompt
64763
- };
64764
- const summary = await createCompactionSummary(messages, compactOptions);
64765
- return {
64766
- summary,
64767
- firstKeptEntryId,
64768
- tokensBefore: calculateTotalTokens(messages)
64769
- };
64770
- }
64771
64845
  function createInteractiveAgentSessionController(host) {
64772
64846
  return new AgentSessionController({
64773
64847
  agent: host.agent,
@@ -64787,14 +64861,16 @@ function createInteractiveAgentSessionController(host) {
64787
64861
  }
64788
64862
  return { allowed: true };
64789
64863
  },
64790
- getAutoCompactDecision: ({ message }) => {
64864
+ getAutoCompactDecision: ({ message, messages }) => {
64791
64865
  if (message?.role === "assistant" && message.stopReason === "error" && message.errorMessage) {
64792
64866
  if (host._isContextOverflowError(message.errorMessage)) {
64793
64867
  return { reason: "overflow", willRetry: true };
64794
64868
  }
64795
- return void 0;
64796
64869
  }
64797
- const contextUsage = host.calculateContextUsage();
64870
+ const contextUsage = calculateCliContextUsage({
64871
+ messages,
64872
+ latestCompactionTimestamp: host.sessionManager.getLatestCompactionTimestamp()
64873
+ });
64798
64874
  const contextWindow = host.options.model?.contextWindow ?? 0;
64799
64875
  if (host._shouldAutoCompact(contextUsage.tokens, contextWindow)) {
64800
64876
  return { reason: "threshold", willRetry: false };
@@ -64802,13 +64878,19 @@ function createInteractiveAgentSessionController(host) {
64802
64878
  return void 0;
64803
64879
  },
64804
64880
  getAutoContinueMessage: () => host.getTodoAutoContinueMessage(),
64881
+ classifyFailure: (context) => classifyCliFailure(context),
64805
64882
  retry: {
64806
64883
  isRetryableError: (errorMessage3) => !host._isContextOverflowError(errorMessage3) && isCliRetryableError(errorMessage3),
64807
64884
  maxRetries: CLI_RETRY_MAX_RETRIES,
64808
64885
  baseDelayMs: getCliRetryBaseDelayMs()
64809
64886
  },
64810
64887
  compaction: {
64811
- compact: async ({ messages }) => createAutoCompactionResult(host, messages)
64888
+ compact: async (context) => createCliAutoCompactionResult({
64889
+ context,
64890
+ model: host.options.model,
64891
+ apiKey: host.options.apiKey,
64892
+ systemPrompt: host.agent.state.systemPrompt
64893
+ })
64812
64894
  },
64813
64895
  onEvent: (event) => {
64814
64896
  host.sessionTrace.noteRuntimeEvent(event.type, event);
@@ -64831,11 +64913,15 @@ function createInteractiveAgentSessionController(host) {
64831
64913
  reason: event.reason,
64832
64914
  queueLength: host.agentSessionController?.pendingMessageCount ?? 0
64833
64915
  });
64834
- } else if (event.type === "retry_scheduled") {
64835
- host.showStatus(`Rate limited. Retrying in ${event.delayMs / 1e3}s... (attempt ${event.attempt}/${event.maxRetries})`);
64836
- } else if (event.type === "retry_start") {
64916
+ } else if (event.type === "turn_recovery_pending") {
64917
+ if (event.recovery === "retry") {
64918
+ host.showStatus(`Transient request error. Retrying in ${(event.delayMs ?? 0) / 1e3}s... (attempt ${event.attempt}/${event.maxRetries})`);
64919
+ } else {
64920
+ host.showStatus(event.failure.kind === "context_overflow" ? "Context overflow detected, compacting..." : "Auto-compacting context before retry...");
64921
+ }
64922
+ } else if (event.type === "turn_retry_started") {
64837
64923
  host.showStatus(`Retrying request (attempt ${event.attempt}/${event.maxRetries})...`);
64838
- } else if (event.type === "retry_succeeded") {
64924
+ } else if (event.type === "turn_retry_succeeded") {
64839
64925
  const outcome = host.executionState.resumePausedTodo();
64840
64926
  if (outcome.changed) {
64841
64927
  host.sessionTrace.noteRuntimeEvent("todo_resumed_after_retry", {
@@ -64846,12 +64932,80 @@ function createInteractiveAgentSessionController(host) {
64846
64932
  void host.refreshTodoWidget();
64847
64933
  }
64848
64934
  host.showStatus("Retry succeeded");
64849
- } else if (event.type === "retry_exhausted") {
64935
+ } else if (event.type === "turn_completed") {
64936
+ host.clearContinuationTimeout("turn_completed");
64937
+ host.emitExtensionEvent({
64938
+ type: "turn:completed",
64939
+ turnIndex: host._semanticTurnIndex,
64940
+ message: event.message,
64941
+ toolResults: event.toolResults
64942
+ });
64943
+ const outcome = host.executionState.advanceFromCompletedTurn(event.message, event.toolResults || []);
64944
+ host.logDebug("execution:turn_completed", {
64945
+ turnIndex: host._semanticTurnIndex,
64946
+ changed: outcome.changed,
64947
+ currentStepId: host.executionState.read().currentStepId,
64948
+ lifecycle: host.executionState.read().lifecycle,
64949
+ reason: outcome.cleared ? `cleared_${outcome.terminalStatus}` : outcome.changed ? "completed_turn" : "unchanged"
64950
+ });
64951
+ if (outcome.changed) {
64952
+ host.persistExecutionState();
64953
+ void host.refreshTodoWidget();
64954
+ }
64955
+ if (outcome.terminalSummary) {
64956
+ host.appendWarningSummary(outcome.terminalSummary);
64957
+ }
64958
+ host.sessionTrace.completeTurn(event.message, false);
64959
+ host._semanticTurnIndex++;
64960
+ } else if (event.type === "turn_failed_terminal") {
64961
+ host.clearContinuationTimeout("turn_failed_terminal");
64962
+ if (event.message) {
64963
+ host.emitExtensionEvent({
64964
+ type: "turn:failed",
64965
+ turnIndex: host._semanticTurnIndex,
64966
+ message: event.message,
64967
+ toolResults: []
64968
+ });
64969
+ }
64970
+ const outcome = host.executionState.handleTurnFailed();
64971
+ host.logDebug("execution:turn_failed_terminal", {
64972
+ turnIndex: host._semanticTurnIndex,
64973
+ changed: outcome.changed,
64974
+ currentStepId: host.executionState.read().currentStepId,
64975
+ lifecycle: host.executionState.read().lifecycle,
64976
+ kind: event.failure.kind
64977
+ });
64978
+ if (outcome.changed) {
64979
+ host.persistExecutionState();
64980
+ void host.refreshTodoWidget();
64981
+ }
64982
+ if (outcome.terminalSummary) {
64983
+ host.appendWarningSummary(outcome.terminalSummary);
64984
+ }
64985
+ if (event.message) {
64986
+ host.sessionTrace.completeTurn(event.message, true);
64987
+ }
64988
+ host._semanticTurnIndex++;
64850
64989
  const state = host.executionState.read();
64851
64990
  if (state.mode === "todo" && state.lifecycle === "paused" && state.steps.length > 0) {
64852
64991
  host.showStatus("Todo list preserved. Send 'continue' when you want to resume it.");
64853
64992
  }
64854
- host.showError(`Request failed after ${event.maxRetries} retries: ${event.errorMessage}`);
64993
+ host.showError(event.failure.message);
64994
+ } else if (event.type === "turn_cancelled") {
64995
+ const outcome = host.executionState.abort();
64996
+ host.logDebug("execution:turn_cancelled", {
64997
+ turnIndex: host._semanticTurnIndex,
64998
+ cleared: outcome.cleared,
64999
+ lifecycle: host.executionState.read().lifecycle,
65000
+ reason: event.reason
65001
+ });
65002
+ if (outcome.cleared || outcome.terminalSummary) {
65003
+ host.persistExecutionState();
65004
+ void host.refreshTodoWidget();
65005
+ }
65006
+ if (outcome.terminalSummary) {
65007
+ host.appendWarningSummary(outcome.terminalSummary);
65008
+ }
64855
65009
  } else if (event.type === "runtime_guard_triggered") {
64856
65010
  host.sessionTrace.noteStalled("runtime_guard_triggered", {
64857
65011
  toolName: event.toolName,
@@ -67497,16 +67651,38 @@ async function runPrintMode(params) {
67497
67651
  maxRetries: CLI_RETRY_MAX_RETRIES,
67498
67652
  baseDelayMs: getCliRetryBaseDelayMs()
67499
67653
  },
67654
+ getAutoCompactDecision: ({ message, messages }) => createCliAutoCompactDecision({
67655
+ message,
67656
+ messages,
67657
+ contextWindow: runtime.model.contextWindow ?? 0,
67658
+ autoCompactEnabled: runtime.settingsManager.getAutoCompactEnabled(),
67659
+ reserveTokens: runtime.settingsManager.getAutoCompactReserveTokens(),
67660
+ latestCompactionTimestamp: runtime.sessionManager.getLatestCompactionTimestamp(),
67661
+ isContextOverflowError: isCliContextOverflowError
67662
+ }),
67663
+ compaction: {
67664
+ compact: async (context) => createCliAutoCompactionResult({
67665
+ context,
67666
+ model: runtime.model,
67667
+ apiKey: runtime.apiKey,
67668
+ systemPrompt: agent?.state.systemPrompt ?? baseSystemPrompt
67669
+ })
67670
+ },
67671
+ classifyFailure: (context) => classifyCliFailure(context),
67500
67672
  onEvent: (event) => {
67501
67673
  sessionTrace.noteRuntimeEvent(event.type, event);
67502
- if (event.type === "retry_scheduled") {
67503
- console.error(`Transient request error: ${event.errorMessage}. Retrying in ${event.delayMs / 1e3}s (${event.attempt}/${event.maxRetries}).`);
67504
- } else if (event.type === "retry_start") {
67674
+ if (event.type === "turn_recovery_pending" && event.recovery === "retry") {
67675
+ console.error(`Transient request error: ${event.failure.message}. Retrying in ${(event.delayMs ?? 0) / 1e3}s (${event.attempt}/${event.maxRetries}).`);
67676
+ } else if (event.type === "turn_recovery_pending" && event.recovery === "compaction") {
67677
+ console.error(event.failure.kind === "context_overflow" ? "Context overflow detected, compacting..." : "Auto-compacting context before retry...");
67678
+ } else if (event.type === "auto_compaction_start") {
67679
+ console.error(event.reason === "overflow" ? "Context overflow detected, compacting..." : "Auto-compacting context...");
67680
+ } else if (event.type === "turn_retry_started") {
67505
67681
  console.error(`Retrying request (${event.attempt}/${event.maxRetries})...`);
67506
- } else if (event.type === "retry_succeeded") {
67682
+ } else if (event.type === "turn_retry_succeeded") {
67507
67683
  console.error(`Retry succeeded after ${event.attempts} attempt${event.attempts === 1 ? "" : "s"}.`);
67508
- } else if (event.type === "retry_exhausted") {
67509
- console.error(`Request failed after ${event.maxRetries} retries: ${event.errorMessage}`);
67684
+ } else if (event.type === "turn_failed_terminal" && (event.failure.kind === "transient_network" || event.failure.kind === "provider_transient")) {
67685
+ console.error(`Request failed after ${CLI_RETRY_MAX_RETRIES} retries: ${event.failure.message}`);
67510
67686
  }
67511
67687
  }
67512
67688
  });
@@ -67665,8 +67841,12 @@ function assistantTextFromAgent(agent) {
67665
67841
  const messages = agent?.state.messages ?? [];
67666
67842
  const lastMessage = messages[messages.length - 1];
67667
67843
  if (lastMessage?.role !== "assistant") return "";
67844
+ return assistantTextFromMessage(lastMessage);
67845
+ }
67846
+ function assistantTextFromMessage(message) {
67847
+ if (message.role !== "assistant") return "";
67668
67848
  let text = "";
67669
- for (const content of lastMessage.content) {
67849
+ for (const content of message.content) {
67670
67850
  if (content.type === "text") {
67671
67851
  text += content.text;
67672
67852
  }
@@ -67818,6 +67998,24 @@ async function runJsonEventsMode(params) {
67818
67998
  chunk
67819
67999
  });
67820
68000
  }
68001
+ function bufferAssistantOutput(chunk) {
68002
+ if (!activeTurn || !chunk) return;
68003
+ activeTurn.pendingAssistantOutput += chunk;
68004
+ touch();
68005
+ }
68006
+ function clearPendingAssistantOutput() {
68007
+ if (activeTurn) {
68008
+ activeTurn.pendingAssistantOutput = "";
68009
+ }
68010
+ }
68011
+ function flushAssistantOutput(fallbackText = "") {
68012
+ if (!activeTurn) return;
68013
+ const chunk = activeTurn.pendingAssistantOutput || fallbackText;
68014
+ activeTurn.pendingAssistantOutput = "";
68015
+ if (chunk) {
68016
+ display(chunk);
68017
+ }
68018
+ }
67821
68019
  function finalize(kind, detail) {
67822
68020
  const turn = activeTurn;
67823
68021
  if (!turn || turn.finalized) return;
@@ -67841,7 +68039,7 @@ async function runJsonEventsMode(params) {
67841
68039
  if (!activeTurn || activeTurn.finalized) return;
67842
68040
  switch (event.type) {
67843
68041
  case "assistant_response_delta":
67844
- display(event.delta);
68042
+ bufferAssistantOutput(event.delta);
67845
68043
  break;
67846
68044
  case "tool_started":
67847
68045
  touch();
@@ -67883,26 +68081,47 @@ async function runJsonEventsMode(params) {
67883
68081
  }
67884
68082
  function handleControllerEvent(event) {
67885
68083
  trace?.noteRuntimeEvent(event.type, event);
68084
+ if (event.type === "turn_settled" && activeTurn) {
68085
+ writeEvent({
68086
+ type: "turn.settled",
68087
+ sessionId: activeTurn.sessionId,
68088
+ turnId: activeTurn.turnId
68089
+ });
68090
+ return;
68091
+ }
67886
68092
  if (!activeTurn || activeTurn.finalized) return;
67887
- if (event.type === "retry_scheduled") {
68093
+ if (event.type === "turn_recovery_pending" && event.recovery === "retry") {
67888
68094
  lastRetryEvent = {
67889
- attempt: event.attempt,
67890
- maxRetries: event.maxRetries,
68095
+ attempt: event.attempt ?? 0,
68096
+ maxRetries: event.maxRetries ?? CLI_RETRY_MAX_RETRIES,
67891
68097
  delayMs: event.delayMs,
67892
- error: event.errorMessage
68098
+ error: event.failure.message
67893
68099
  };
67894
68100
  touch();
67895
68101
  writeEvent({
67896
- type: "turn.retry.scheduled",
68102
+ type: "turn.recovery.pending",
67897
68103
  sessionId: activeTurn.sessionId,
67898
68104
  turnId: activeTurn.turnId,
68105
+ recovery: event.recovery,
67899
68106
  attempt: event.attempt,
67900
68107
  maxRetries: event.maxRetries,
67901
68108
  delayMs: event.delayMs,
67902
- error: event.errorMessage
68109
+ error: event.failure.message
67903
68110
  });
67904
- } else if (event.type === "retry_start") {
68111
+ } else if (event.type === "turn_recovery_pending" && event.recovery === "compaction") {
67905
68112
  activeTurn.summary = "";
68113
+ clearPendingAssistantOutput();
68114
+ touch();
68115
+ writeEvent({
68116
+ type: "turn.recovery.pending",
68117
+ sessionId: activeTurn.sessionId,
68118
+ turnId: activeTurn.turnId,
68119
+ recovery: event.recovery,
68120
+ error: event.failure.message
68121
+ });
68122
+ } else if (event.type === "turn_retry_started") {
68123
+ activeTurn.summary = "";
68124
+ clearPendingAssistantOutput();
67906
68125
  lastRetryEvent = {
67907
68126
  attempt: event.attempt,
67908
68127
  maxRetries: event.maxRetries,
@@ -67919,7 +68138,7 @@ async function runJsonEventsMode(params) {
67919
68138
  delayMs: lastRetryEvent.delayMs,
67920
68139
  error: lastRetryEvent.error
67921
68140
  });
67922
- } else if (event.type === "retry_succeeded") {
68141
+ } else if (event.type === "turn_retry_succeeded") {
67923
68142
  touch();
67924
68143
  writeEvent({
67925
68144
  type: "turn.retry.succeeded",
@@ -67931,18 +68150,40 @@ async function runJsonEventsMode(params) {
67931
68150
  error: lastRetryEvent?.error
67932
68151
  });
67933
68152
  lastRetryEvent = void 0;
67934
- } else if (event.type === "retry_exhausted") {
68153
+ } else if (event.type === "turn_compaction_started") {
68154
+ if (event.willRetry) {
68155
+ activeTurn.summary = "";
68156
+ clearPendingAssistantOutput();
68157
+ }
67935
68158
  touch();
67936
68159
  writeEvent({
67937
- type: "turn.retry.exhausted",
68160
+ type: "turn.compaction.started",
67938
68161
  sessionId: activeTurn.sessionId,
67939
68162
  turnId: activeTurn.turnId,
67940
- attempt: lastRetryEvent?.attempt ?? event.maxRetries,
67941
- maxRetries: event.maxRetries,
67942
- delayMs: lastRetryEvent?.delayMs,
67943
- error: event.errorMessage
68163
+ reason: event.reason,
68164
+ willRetry: event.willRetry
67944
68165
  });
67945
- lastRetryEvent = void 0;
68166
+ } else if (event.type === "turn_completed") {
68167
+ const text = assistantTextFromMessage(event.message);
68168
+ flushAssistantOutput(text);
68169
+ finalize("completed", text || activeTurn.summary.trim());
68170
+ } else if (event.type === "turn_failed_terminal") {
68171
+ touch();
68172
+ if (lastRetryEvent) {
68173
+ writeEvent({
68174
+ type: "turn.retry.exhausted",
68175
+ sessionId: activeTurn.sessionId,
68176
+ turnId: activeTurn.turnId,
68177
+ attempt: lastRetryEvent.attempt,
68178
+ maxRetries: lastRetryEvent.maxRetries,
68179
+ delayMs: lastRetryEvent.delayMs,
68180
+ error: event.failure.message
68181
+ });
68182
+ lastRetryEvent = void 0;
68183
+ }
68184
+ finalize("failed", event.failure.message);
68185
+ } else if (event.type === "turn_cancelled") {
68186
+ finalize("cancelled", event.reason);
67946
68187
  }
67947
68188
  }
67948
68189
  async function ensureRuntime() {
@@ -68006,6 +68247,24 @@ async function runJsonEventsMode(params) {
68006
68247
  maxRetries: CLI_RETRY_MAX_RETRIES,
68007
68248
  baseDelayMs: getCliRetryBaseDelayMs()
68008
68249
  },
68250
+ getAutoCompactDecision: ({ message, messages }) => createCliAutoCompactDecision({
68251
+ message,
68252
+ messages,
68253
+ contextWindow: runtime.model.contextWindow ?? 0,
68254
+ autoCompactEnabled: runtime.settingsManager.getAutoCompactEnabled(),
68255
+ reserveTokens: runtime.settingsManager.getAutoCompactReserveTokens(),
68256
+ latestCompactionTimestamp: runtime.sessionManager.getLatestCompactionTimestamp(),
68257
+ isContextOverflowError: isCliContextOverflowError
68258
+ }),
68259
+ compaction: {
68260
+ compact: async (context) => createCliAutoCompactionResult({
68261
+ context,
68262
+ model: runtime.model,
68263
+ apiKey: runtime.apiKey,
68264
+ systemPrompt: agent?.state.systemPrompt ?? baseSystemPrompt
68265
+ })
68266
+ },
68267
+ classifyFailure: (context) => classifyCliFailure(context),
68009
68268
  onEvent: handleControllerEvent
68010
68269
  });
68011
68270
  trace = new SessionTraceWriter(runtime.paths.sessionsDir, runtime.sessionManager);
@@ -68042,7 +68301,8 @@ async function runJsonEventsMode(params) {
68042
68301
  interrupted: false,
68043
68302
  stalled: false,
68044
68303
  finalized: false,
68045
- summary: ""
68304
+ summary: "",
68305
+ pendingAssistantOutput: ""
68046
68306
  };
68047
68307
  const turn = activeTurn;
68048
68308
  writeEvent({ type: "turn.started", sessionId: activeTurn.sessionId, turnId: activeTurn.turnId });
@@ -68110,9 +68370,7 @@ async function runJsonEventsMode(params) {
68110
68370
  return;
68111
68371
  }
68112
68372
  const text = assistantTextFromAgent(agent);
68113
- if (!activeTurn?.summary && text) {
68114
- display(text);
68115
- }
68373
+ flushAssistantOutput(text);
68116
68374
  finalize("completed", text || activeTurn?.summary.trim());
68117
68375
  }
68118
68376
  } catch (error) {