@aexol/spectral 0.9.162 → 0.9.167

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/dist/agent/agents.d.ts +7 -8
  2. package/dist/agent/agents.d.ts.map +1 -1
  3. package/dist/agent/agents.js +6 -17
  4. package/dist/agent/index.d.ts +1 -1
  5. package/dist/agent/index.d.ts.map +1 -1
  6. package/dist/agent/index.js +12 -6
  7. package/dist/commands/serve.d.ts.map +1 -1
  8. package/dist/commands/serve.js +12 -3
  9. package/dist/sdk/coding-agent/core/agent-session.d.ts +21 -1
  10. package/dist/sdk/coding-agent/core/agent-session.d.ts.map +1 -1
  11. package/dist/sdk/coding-agent/core/agent-session.js +377 -75
  12. package/dist/sdk/coding-agent/core/compaction/llm-compaction.d.ts +42 -0
  13. package/dist/sdk/coding-agent/core/compaction/llm-compaction.d.ts.map +1 -0
  14. package/dist/sdk/coding-agent/core/compaction/llm-compaction.js +83 -0
  15. package/dist/sdk/coding-agent/core/resource-loader.d.ts.map +1 -1
  16. package/dist/sdk/coding-agent/core/resource-loader.js +23 -1
  17. package/dist/sdk/coding-agent/core/session-manager.d.ts +50 -1
  18. package/dist/sdk/coding-agent/core/session-manager.d.ts.map +1 -1
  19. package/dist/sdk/coding-agent/core/session-manager.js +222 -26
  20. package/dist/sdk/coding-agent/core/settings-manager.d.ts +15 -0
  21. package/dist/sdk/coding-agent/core/settings-manager.d.ts.map +1 -1
  22. package/dist/sdk/coding-agent/core/settings-manager.js +11 -0
  23. package/dist/sdk/coding-agent/core/tools/apply-patch.d.ts.map +1 -1
  24. package/dist/sdk/coding-agent/core/tools/apply-patch.js +106 -1
  25. package/dist/server/agent-bridge.d.ts +5 -1
  26. package/dist/server/agent-bridge.d.ts.map +1 -1
  27. package/dist/server/agent-bridge.js +4 -1
  28. package/dist/server/session-stream.d.ts +15 -3
  29. package/dist/server/session-stream.d.ts.map +1 -1
  30. package/dist/server/session-stream.js +14 -0
  31. package/package.json +1 -1
@@ -18,18 +18,19 @@ import { clampThinkingLevel, cleanupSessionResources, getSupportedThinkingLevels
18
18
  import { stripFrontmatter } from "../utils/frontmatter.js";
19
19
  import { resolvePath } from "../utils/paths.js";
20
20
  import { sleep } from "../utils/sleep.js";
21
- import { formatNoApiKeyFoundMessage, formatNoModelSelectedMessage } from "./auth-guidance.js";
21
+ import { formatNoApiKeyFoundMessage, formatNoModelSelectedMessage, } from "./auth-guidance.js";
22
22
  import { executeBashWithOperations } from "./bash-executor.js";
23
23
  import { calculateContextTokens, collectEntriesForBranchSummary, compactDcpLite, estimateContextTokens, estimateCompactionTokenMetrics, getManualCompactionSettings, normalizeCompactionPolicy, normalizeManualKeepRecentTokens, generateBranchSummary, prepareCompaction, shouldCompact, } from "./compaction/index.js";
24
+ import { enrichDcpCompaction, mergeCompactionObservations } from "./compaction/llm-compaction.js";
24
25
  import { DEFAULT_THINKING_LEVEL } from "./defaults.js";
25
26
  import { ExtensionRunner, wrapRegisteredTools, } from "./extensions/index.js";
26
27
  import { emitSessionShutdownEvent } from "./extensions/runner.js";
27
- import { expandPromptTemplate } from "./prompt-templates.js";
28
- import { CURRENT_SESSION_VERSION, getLatestCompactionEntry } from "./session-manager.js";
28
+ import { expandPromptTemplate, } from "./prompt-templates.js";
29
+ import { CURRENT_SESSION_VERSION, getLatestCompactionEntry, } from "./session-manager.js";
29
30
  import { createSyntheticSourceInfo } from "./source-info.js";
30
- import { buildSystemPrompt } from "./system-prompt.js";
31
- import { mutateSystemPrompt } from "./system-prompt-mutator.js";
32
- import { createLocalBashOperations } from "./tools/bash.js";
31
+ import { buildSystemPrompt, } from "./system-prompt.js";
32
+ import { mutateSystemPrompt, } from "./system-prompt-mutator.js";
33
+ import { createLocalBashOperations, } from "./tools/bash.js";
33
34
  import { createAllToolDefinitions } from "./tools/index.js";
34
35
  import { createToolDefinitionFromAgentTool } from "./tools/tool-definition-wrapper.js";
35
36
  /**
@@ -73,7 +74,8 @@ function createVirtualPhaseBoundaryEntry(pathEntries) {
73
74
  };
74
75
  }
75
76
  function isDcpLitePhaseBoundaryEntry(entry) {
76
- return entry?.type === "custom_message" && entry.customType === DCP_LITE_PHASE_BOUNDARY_CUSTOM_TYPE;
77
+ return (entry?.type === "custom_message" &&
78
+ entry.customType === DCP_LITE_PHASE_BOUNDARY_CUSTOM_TYPE);
77
79
  }
78
80
  function isRecord(value) {
79
81
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
@@ -135,9 +137,9 @@ function getNonShrinkingSkipMessage(result) {
135
137
  return "Compaction skipped: no eligible history to compact.";
136
138
  }
137
139
  if ((tokensRemoved ?? 0) <= 0 || summaryTokens >= tokensCompacted) {
138
- return `Compaction skipped: generated summary would not shrink context (` +
140
+ return (`Compaction skipped: generated summary would not shrink context (` +
139
141
  `compacted ~${tokensCompacted.toLocaleString()} tokens, ` +
140
- `summary +~${summaryTokens.toLocaleString()} tokens).`;
142
+ `summary +~${summaryTokens.toLocaleString()} tokens).`);
141
143
  }
142
144
  return undefined;
143
145
  }
@@ -153,7 +155,13 @@ function markCompactionSkipped(result, skipReason, message) {
153
155
  // Constants
154
156
  // ============================================================================
155
157
  /** Standard thinking levels */
156
- const THINKING_LEVELS = ["off", "minimal", "low", "medium", "high"];
158
+ const THINKING_LEVELS = [
159
+ "off",
160
+ "minimal",
161
+ "low",
162
+ "medium",
163
+ "high",
164
+ ];
157
165
  const LENGTH_CONTINUATION_PROMPT = "Your previous assistant response ended because the provider hit its maximum output length. Continue exactly from where it stopped. Do not repeat, recap, or summarize earlier content. If the previous response ended mid-sentence, resume with the next missing word; otherwise continue with the next missing content.";
158
166
  // ============================================================================
159
167
  // AgentSession Class
@@ -176,6 +184,8 @@ export class AgentSession {
176
184
  _compactionAbortController = undefined;
177
185
  _autoCompactionAbortController = undefined;
178
186
  _compactionInProgress = false;
187
+ _requestTimeJobs = new Map();
188
+ _requestTimeControllers = new Map();
179
189
  _overflowRecoveryAttempted = false;
180
190
  // Branch summarization state
181
191
  _branchSummaryAbortController = undefined;
@@ -232,9 +242,14 @@ export class AgentSession {
232
242
  this._modelRegistry = config.modelRegistry;
233
243
  this._extensionRunnerRef = config.extensionRunnerRef;
234
244
  this._initialActiveToolNames = config.initialActiveToolNames;
235
- this._allowedToolNames = config.allowedToolNames ? new Set(config.allowedToolNames) : undefined;
245
+ this._allowedToolNames = config.allowedToolNames
246
+ ? new Set(config.allowedToolNames)
247
+ : undefined;
236
248
  this._baseToolsOverride = config.baseToolsOverride;
237
- this._sessionStartEvent = config.sessionStartEvent ?? { type: "session_start", reason: "startup" };
249
+ this._sessionStartEvent = config.sessionStartEvent ?? {
250
+ type: "session_start",
251
+ reason: "startup",
252
+ };
238
253
  // Always subscribe to agent events for internal handling
239
254
  // (session persistence, extensions, auto-compaction, retry logic)
240
255
  this._unsubscribeAgent = this.agent.subscribe(this._handleAgentEvent);
@@ -243,6 +258,14 @@ export class AgentSession {
243
258
  activeToolNames: this._initialActiveToolNames,
244
259
  includeAllExtensionTools: true,
245
260
  });
261
+ // Recover journaled request-time workers after a process reload. The
262
+ // journal is authoritative; only pending/running jobs are resumed.
263
+ for (const job of this.sessionManager.getRequestTimeCompactionJobs()) {
264
+ this._requestTimeJobs.set(job.snapshot.compactionId, job);
265
+ const controller = new AbortController();
266
+ this._requestTimeControllers.set(job.snapshot.compactionId, controller);
267
+ void this._runRequestTimeCompaction(job, controller).catch(() => undefined);
268
+ }
246
269
  }
247
270
  /** Model registry for API key resolution and model discovery */
248
271
  get modelRegistry() {
@@ -366,7 +389,9 @@ export class AgentSession {
366
389
  // Emit to extensions first
367
390
  await this._emitExtensionEvent(event);
368
391
  // Notify all listeners
369
- this._emit(event.type === "agent_end" ? { ...event, willRetry: this._willRetryAfterAgentEnd(event) } : event);
392
+ this._emit(event.type === "agent_end"
393
+ ? { ...event, willRetry: this._willRetryAfterAgentEnd(event) }
394
+ : event);
370
395
  // Handle session persistence
371
396
  if (event.type === "message_end") {
372
397
  this._persistMessageEnd(event.message);
@@ -397,7 +422,8 @@ export class AgentSession {
397
422
  // Persist as CustomMessageEntry
398
423
  this.sessionManager.appendCustomMessageEntry(message.customType, message.content, message.display, message.details);
399
424
  }
400
- else if ((message.role === "user" && !this._shouldSkipMessagePersistence(message)) ||
425
+ else if ((message.role === "user" &&
426
+ !this._shouldSkipMessagePersistence(message)) ||
401
427
  message.role === "assistant" ||
402
428
  message.role === "toolResult") {
403
429
  // Regular LLM message - persist as SessionMessageEntry
@@ -405,7 +431,8 @@ export class AgentSession {
405
431
  }
406
432
  }
407
433
  _handleUserMessageStart(messageText, message) {
408
- const isInternalLengthContinuation = messageText === LENGTH_CONTINUATION_PROMPT && this._pendingInternalLengthContinuationMessages > 0;
434
+ const isInternalLengthContinuation = messageText === LENGTH_CONTINUATION_PROMPT &&
435
+ this._pendingInternalLengthContinuationMessages > 0;
409
436
  if (isInternalLengthContinuation) {
410
437
  this._pendingInternalLengthContinuationMessages--;
411
438
  this._internalLengthContinuationUserMessages.add(message);
@@ -483,7 +510,10 @@ export class AgentSession {
483
510
  await this._extensionRunner.emit({ type: "agent_start" });
484
511
  }
485
512
  else if (event.type === "agent_end") {
486
- await this._extensionRunner.emit({ type: "agent_end", messages: event.messages });
513
+ await this._extensionRunner.emit({
514
+ type: "agent_end",
515
+ messages: event.messages,
516
+ });
487
517
  }
488
518
  else if (event.type === "turn_start") {
489
519
  const extensionEvent = {
@@ -755,7 +785,9 @@ export class AgentSession {
755
785
  }
756
786
  const loaderSystemPrompt = this._resourceLoader.getSystemPrompt();
757
787
  const loaderAppendSystemPrompt = this._resourceLoader.getAppendSystemPrompt();
758
- const appendSystemPrompt = loaderAppendSystemPrompt.length > 0 ? loaderAppendSystemPrompt.join("\n\n") : undefined;
788
+ const appendSystemPrompt = loaderAppendSystemPrompt.length > 0
789
+ ? loaderAppendSystemPrompt.join("\n\n")
790
+ : undefined;
759
791
  const loadedSkills = this._resourceLoader.getSkills().skills;
760
792
  const loadedContextFiles = this._resourceLoader.getAgentsFiles().agentsFiles;
761
793
  this._baseSystemPromptOptions = {
@@ -843,9 +875,19 @@ export class AgentSession {
843
875
  }
844
876
  return await this._checkCompaction(msg);
845
877
  }
878
+ _shouldRequestTimeCompact(message) {
879
+ const policy = this._getCompactionPolicy();
880
+ if (!this._isAutoOlderHistoryCompactionEnabled(policy) || message.stopReason === "error")
881
+ return false;
882
+ const contextWindow = this.model?.contextWindow ?? 0;
883
+ if (!contextWindow)
884
+ return false;
885
+ return shouldCompact(calculateContextTokens(message.usage), contextWindow, this._getAutoOlderHistoryCompactionSettings(policy));
886
+ }
846
887
  _getCompactionPolicy() {
847
888
  const manager = this.settingsManager;
848
- return manager.getCompactionPolicy?.() ?? normalizeCompactionPolicy(manager.getCompactionSettings());
889
+ return (manager.getCompactionPolicy?.() ??
890
+ normalizeCompactionPolicy(manager.getCompactionSettings()));
849
891
  }
850
892
  _shouldAutoContinueLengthStop(message) {
851
893
  if (message.stopReason !== "length")
@@ -878,7 +920,9 @@ export class AgentSession {
878
920
  this._lastLengthContinuationReason = undefined;
879
921
  return false;
880
922
  }
881
- this._lastLengthContinuationReason = truncatedToolCall ? "truncated_tool_call" : undefined;
923
+ this._lastLengthContinuationReason = truncatedToolCall
924
+ ? "truncated_tool_call"
925
+ : undefined;
882
926
  return true;
883
927
  }
884
928
  _getAssistantOutputTokens(message) {
@@ -937,7 +981,9 @@ export class AgentSession {
937
981
  // All values empty/blank => the arguments were only partially emitted.
938
982
  return keys.every((key) => {
939
983
  const value = args[key];
940
- return value === undefined || value === null || (typeof value === "string" && value.trim() === "");
984
+ return (value === undefined ||
985
+ value === null ||
986
+ (typeof value === "string" && value.trim() === ""));
941
987
  });
942
988
  });
943
989
  }
@@ -994,7 +1040,9 @@ export class AgentSession {
994
1040
  let expandedText = currentText;
995
1041
  if (expandPromptTemplates) {
996
1042
  expandedText = this._expandSkillCommand(expandedText);
997
- expandedText = expandPromptTemplate(expandedText, [...this.promptTemplates]);
1043
+ expandedText = expandPromptTemplate(expandedText, [
1044
+ ...this.promptTemplates,
1045
+ ]);
998
1046
  }
999
1047
  // If streaming, queue via steer() or followUp() based on option
1000
1048
  if (this.isStreaming) {
@@ -1027,7 +1075,20 @@ export class AgentSession {
1027
1075
  }
1028
1076
  // Check if we need to compact before sending (catches aborted responses)
1029
1077
  const lastAssistant = this._findLastAssistantMessage();
1030
- if (lastAssistant && (await this._checkCompaction(lastAssistant, false))) {
1078
+ // This is the production request-time cut point: the previous agent run
1079
+ // has ended, but the next request has not started. Snapshotting here is
1080
+ // non-blocking and leaves overflow recovery on its existing path.
1081
+ const requestTimeScheduled = lastAssistant !== undefined &&
1082
+ lastAssistant.stopReason !== "aborted" &&
1083
+ this._shouldRequestTimeCompact(lastAssistant);
1084
+ if (requestTimeScheduled) {
1085
+ this.requestTimeCompact({
1086
+ targetTurnId: this.sessionManager.getLeafId() ?? undefined,
1087
+ });
1088
+ }
1089
+ if (lastAssistant &&
1090
+ !requestTimeScheduled &&
1091
+ (await this._checkCompaction(lastAssistant, false))) {
1031
1092
  try {
1032
1093
  await this.agent.continue();
1033
1094
  while (await this._handlePostAgentRun()) {
@@ -1041,7 +1102,9 @@ export class AgentSession {
1041
1102
  // Build messages array (custom message if any, then user message)
1042
1103
  messages = [];
1043
1104
  // Add user message
1044
- const userContent = [{ type: "text", text: expandedText }];
1105
+ const userContent = [
1106
+ { type: "text", text: expandedText },
1107
+ ];
1045
1108
  if (currentImages) {
1046
1109
  userContent.push(...currentImages);
1047
1110
  }
@@ -1127,7 +1190,9 @@ export class AgentSession {
1127
1190
  const spaceIndex = text.indexOf(" ");
1128
1191
  const skillName = spaceIndex === -1 ? text.slice(7) : text.slice(7, spaceIndex);
1129
1192
  const args = spaceIndex === -1 ? "" : text.slice(spaceIndex + 1).trim();
1130
- const skill = this.resourceLoader.getSkills().skills.find((s) => s.name === skillName);
1193
+ const skill = this.resourceLoader
1194
+ .getSkills()
1195
+ .skills.find((s) => s.name === skillName);
1131
1196
  if (!skill)
1132
1197
  return text; // Unknown skill, pass through
1133
1198
  try {
@@ -1161,7 +1226,9 @@ export class AgentSession {
1161
1226
  }
1162
1227
  // Expand skill commands and prompt templates
1163
1228
  let expandedText = this._expandSkillCommand(text);
1164
- expandedText = expandPromptTemplate(expandedText, [...this.promptTemplates]);
1229
+ expandedText = expandPromptTemplate(expandedText, [
1230
+ ...this.promptTemplates,
1231
+ ]);
1165
1232
  await this._queueSteer(expandedText, images);
1166
1233
  }
1167
1234
  /**
@@ -1178,7 +1245,9 @@ export class AgentSession {
1178
1245
  }
1179
1246
  // Expand skill commands and prompt templates
1180
1247
  let expandedText = this._expandSkillCommand(text);
1181
- expandedText = expandPromptTemplate(expandedText, [...this.promptTemplates]);
1248
+ expandedText = expandPromptTemplate(expandedText, [
1249
+ ...this.promptTemplates,
1250
+ ]);
1182
1251
  await this._queueFollowUp(expandedText, images);
1183
1252
  }
1184
1253
  /**
@@ -1392,7 +1461,9 @@ export class AgentSession {
1392
1461
  if (currentIndex === -1)
1393
1462
  currentIndex = 0;
1394
1463
  const len = scopedModels.length;
1395
- const nextIndex = direction === "forward" ? (currentIndex + 1) % len : (currentIndex - 1 + len) % len;
1464
+ const nextIndex = direction === "forward"
1465
+ ? (currentIndex + 1) % len
1466
+ : (currentIndex - 1 + len) % len;
1396
1467
  const next = scopedModels[nextIndex];
1397
1468
  const thinkingLevel = this._getThinkingLevelForModelSwitch(next.thinkingLevel);
1398
1469
  // Apply model
@@ -1405,7 +1476,11 @@ export class AgentSession {
1405
1476
  // setThinkingLevel clamps to model capabilities.
1406
1477
  this.setThinkingLevel(thinkingLevel);
1407
1478
  await this._emitModelSelect(next.model, currentModel, "cycle");
1408
- return { model: next.model, thinkingLevel: this.thinkingLevel, isScoped: true };
1479
+ return {
1480
+ model: next.model,
1481
+ thinkingLevel: this.thinkingLevel,
1482
+ isScoped: true,
1483
+ };
1409
1484
  }
1410
1485
  async _cycleAvailableModel(direction) {
1411
1486
  const availableModels = await this._modelRegistry.getAvailable();
@@ -1416,7 +1491,9 @@ export class AgentSession {
1416
1491
  if (currentIndex === -1)
1417
1492
  currentIndex = 0;
1418
1493
  const len = availableModels.length;
1419
- const nextIndex = direction === "forward" ? (currentIndex + 1) % len : (currentIndex - 1 + len) % len;
1494
+ const nextIndex = direction === "forward"
1495
+ ? (currentIndex + 1) % len
1496
+ : (currentIndex - 1 + len) % len;
1420
1497
  const nextModel = availableModels[nextIndex];
1421
1498
  const thinkingLevel = this._getThinkingLevelForModelSwitch();
1422
1499
  this.agent.state.model = nextModel;
@@ -1425,7 +1502,11 @@ export class AgentSession {
1425
1502
  // Re-clamp thinking level for new model's capabilities
1426
1503
  this.setThinkingLevel(thinkingLevel);
1427
1504
  await this._emitModelSelect(nextModel, currentModel, "cycle");
1428
- return { model: nextModel, thinkingLevel: this.thinkingLevel, isScoped: false };
1505
+ return {
1506
+ model: nextModel,
1507
+ thinkingLevel: this.thinkingLevel,
1508
+ isScoped: false,
1509
+ };
1429
1510
  }
1430
1511
  // =========================================================================
1431
1512
  // Thinking Level Management
@@ -1437,7 +1518,9 @@ export class AgentSession {
1437
1518
  */
1438
1519
  setThinkingLevel(level) {
1439
1520
  const availableLevels = this.getAvailableThinkingLevels();
1440
- const effectiveLevel = availableLevels.includes(level) ? level : this._clampThinkingLevel(level, availableLevels);
1521
+ const effectiveLevel = availableLevels.includes(level)
1522
+ ? level
1523
+ : this._clampThinkingLevel(level, availableLevels);
1441
1524
  // Only persist if actually changing
1442
1525
  const previousLevel = this.agent.state.thinkingLevel;
1443
1526
  const isChanging = effectiveLevel !== previousLevel;
@@ -1489,12 +1572,14 @@ export class AgentSession {
1489
1572
  return explicitLevel;
1490
1573
  }
1491
1574
  if (!this.supportsThinking()) {
1492
- return this.settingsManager.getDefaultThinkingLevel() ?? DEFAULT_THINKING_LEVEL;
1575
+ return (this.settingsManager.getDefaultThinkingLevel() ?? DEFAULT_THINKING_LEVEL);
1493
1576
  }
1494
1577
  return this.thinkingLevel;
1495
1578
  }
1496
1579
  _clampThinkingLevel(level, _availableLevels) {
1497
- return this.model ? clampThinkingLevel(this.model, level) : "off";
1580
+ return this.model
1581
+ ? clampThinkingLevel(this.model, level)
1582
+ : "off";
1498
1583
  }
1499
1584
  // =========================================================================
1500
1585
  // Queue Mode Management
@@ -1549,6 +1634,149 @@ export class AgentSession {
1549
1634
  _buildDcpCompactionResult(preparation, extensionCompaction) {
1550
1635
  return preserveDcpHookMemoryDetails(compactDcpLite(preparation), extensionCompaction);
1551
1636
  }
1637
+ /**
1638
+ * Schedule a durable request-time overlay. Snapshot and commit are short,
1639
+ * synchronous sections; summary generation never holds the normal compaction
1640
+ * lock and therefore cannot delay the next provider request.
1641
+ */
1642
+ requestTimeCompact(options = {}) {
1643
+ const snapshot = this.sessionManager.createRequestTimeSnapshot(options.targetTurnId);
1644
+ const previous = this._requestTimeJobs.get(snapshot.compactionId);
1645
+ if (previous)
1646
+ return previous;
1647
+ const job = { snapshot, status: "pending" };
1648
+ this._requestTimeJobs.set(snapshot.compactionId, job);
1649
+ this.sessionManager.persistRequestTimeCompactionJob(job);
1650
+ const controller = new AbortController();
1651
+ this._requestTimeControllers.set(snapshot.compactionId, controller);
1652
+ if (options.signal) {
1653
+ if (options.signal.aborted)
1654
+ controller.abort();
1655
+ else
1656
+ options.signal.addEventListener("abort", () => controller.abort(), {
1657
+ once: true,
1658
+ });
1659
+ }
1660
+ void this._runRequestTimeCompaction(job, controller).catch(() => undefined);
1661
+ return job;
1662
+ }
1663
+ async _runRequestTimeCompaction(job, controller) {
1664
+ job.status = "running";
1665
+ this.sessionManager.persistRequestTimeCompactionJob(job);
1666
+ try {
1667
+ if (controller.signal.aborted) {
1668
+ job.status = "aborted";
1669
+ return;
1670
+ }
1671
+ const settings = this._getAutoOlderHistoryCompactionSettings(this._getCompactionPolicy());
1672
+ const entries = job.snapshot.entries;
1673
+ const preparation = prepareCompaction(entries, settings, {
1674
+ phaseBoundary: false,
1675
+ });
1676
+ if (!preparation) {
1677
+ job.status = "failed";
1678
+ return;
1679
+ }
1680
+ // DCP-lite is deterministic and safe to execute away from the session
1681
+ // lock. Hook observations, when registered, are folded into details.
1682
+ let hook;
1683
+ let lastError;
1684
+ for (let attempt = 0; attempt < 3; attempt++) {
1685
+ if (controller.signal.aborted) {
1686
+ job.status = "aborted";
1687
+ this.sessionManager.persistRequestTimeCompactionJob(job);
1688
+ return;
1689
+ }
1690
+ try {
1691
+ hook = await this._emitSessionBeforeCompact(preparation, entries, undefined, controller.signal, () => undefined);
1692
+ lastError = undefined;
1693
+ break;
1694
+ }
1695
+ catch (error) {
1696
+ lastError = error;
1697
+ if (!this._isRequestTimeRetryable(error) || attempt === 2)
1698
+ break;
1699
+ await sleep(50 * 2 ** attempt, controller.signal);
1700
+ }
1701
+ }
1702
+ if (lastError !== undefined)
1703
+ throw lastError;
1704
+ if (controller.signal.aborted) {
1705
+ job.status = "aborted";
1706
+ return;
1707
+ }
1708
+ const result = this._buildDcpCompactionResult(preparation, hook?.cancel ? undefined : hook?.compaction);
1709
+ if (this._markIfNonShrinking(result)) {
1710
+ job.status = "failed";
1711
+ return;
1712
+ }
1713
+ const llmSettings = this.settingsManager.getLlmCompactionSettings();
1714
+ const persistedObservations = entries
1715
+ .filter((entry) => entry.type === "compaction")
1716
+ .flatMap((entry) => this._extractCompactionObservations(entry.details));
1717
+ const durableObservations = mergeCompactionObservations(persistedObservations, this._extractCompactionObservations(hook?.compaction?.details));
1718
+ const finalResult = await enrichDcpCompaction(result, durableObservations, llmSettings, async () => {
1719
+ try {
1720
+ const configured = llmSettings.provider && llmSettings.modelId
1721
+ ? this._modelRegistry.find(llmSettings.provider, llmSettings.modelId)
1722
+ : this.model;
1723
+ if (!configured)
1724
+ return undefined;
1725
+ const auth = await this._modelRegistry.getApiKeyAndHeaders(configured);
1726
+ return auth.ok && auth.apiKey ? { model: configured, apiKey: auth.apiKey, headers: auth.headers } : undefined;
1727
+ }
1728
+ catch {
1729
+ return undefined;
1730
+ }
1731
+ }, controller.signal);
1732
+ const committed = this.sessionManager.commitRequestTimeCompaction(job.snapshot, finalResult);
1733
+ job.status = committed.status === "stale" ? "stale" : "committed";
1734
+ this.sessionManager.persistRequestTimeCompactionJob(job);
1735
+ if (committed.status === "committed") {
1736
+ // Refresh provider state only after the durable entry exists. UI callers
1737
+ // continue to use getFullHistory(), which remains append-only.
1738
+ this.agent.state.messages =
1739
+ this.sessionManager.buildSessionContext().messages;
1740
+ }
1741
+ }
1742
+ catch (error) {
1743
+ job.status =
1744
+ controller.signal.aborted ||
1745
+ (error instanceof Error && error.name === "AbortError")
1746
+ ? "aborted"
1747
+ : "failed";
1748
+ this.sessionManager.persistRequestTimeCompactionJob(job);
1749
+ }
1750
+ finally {
1751
+ this._requestTimeControllers.delete(job.snapshot.compactionId);
1752
+ }
1753
+ }
1754
+ _extractCompactionObservations(details) {
1755
+ if (!isRecord(details) || !Array.isArray(details.observations))
1756
+ return [];
1757
+ return details.observations.filter((item) => isRecord(item) && typeof item.content === "string").map((item) => ({
1758
+ id: typeof item.id === "string" ? item.id : undefined,
1759
+ content: item.content,
1760
+ relevance: typeof item.relevance === "string" ? item.relevance : undefined,
1761
+ sourceEntryIds: Array.isArray(item.sourceEntryIds) ? item.sourceEntryIds.filter((id) => typeof id === "string") : undefined,
1762
+ }));
1763
+ }
1764
+ _isRequestTimeRetryable(error) {
1765
+ if (error instanceof Error && error.name === "AbortError")
1766
+ return false;
1767
+ const text = error instanceof Error ? error.message : String(error);
1768
+ return /timeout|temporar|network|rate.?limit|429|500|502|503|504|overload/i.test(text);
1769
+ }
1770
+ abortRequestTimeCompaction(compactionId) {
1771
+ const controller = this._requestTimeControllers.get(compactionId);
1772
+ if (!controller)
1773
+ return false;
1774
+ controller.abort();
1775
+ return true;
1776
+ }
1777
+ getRequestTimeCompaction(compactionId) {
1778
+ return this._requestTimeJobs.get(compactionId);
1779
+ }
1552
1780
  _emitCompactionResultSummary(result) {
1553
1781
  if (result.summary) {
1554
1782
  this._emit({
@@ -1560,7 +1788,9 @@ export class AgentSession {
1560
1788
  }
1561
1789
  _markIfNonShrinking(result) {
1562
1790
  const message = getNonShrinkingSkipMessage(result);
1563
- return message ? markCompactionSkipped(result, "non_shrinking", message) : undefined;
1791
+ return message
1792
+ ? markCompactionSkipped(result, "non_shrinking", message)
1793
+ : undefined;
1564
1794
  }
1565
1795
  async _emitSessionBeforeCompact(preparation, branchEntries, customInstructions, signal, emitProgress) {
1566
1796
  const runner = this._extensionRunner;
@@ -1579,7 +1809,7 @@ export class AgentSession {
1579
1809
  * Execute a manual compaction under the compaction lock.
1580
1810
  */
1581
1811
  async _executeCompaction(options) {
1582
- const { customInstructions, phaseBoundary, keepRecentTokens, memoryHookMode } = normalizeManualCompactionOptions(options);
1812
+ const { customInstructions, phaseBoundary, keepRecentTokens, memoryHookMode, } = normalizeManualCompactionOptions(options);
1583
1813
  this._disconnectFromAgent();
1584
1814
  await this.abort();
1585
1815
  this._compactionAbortController = new AbortController();
@@ -1588,14 +1818,23 @@ export class AgentSession {
1588
1818
  let pathEntries = this.sessionManager.getBranch();
1589
1819
  const policy = this._getCompactionPolicy();
1590
1820
  let settings = getManualCompactionSettings(policy.conversation, policy.manual);
1591
- if (typeof keepRecentTokens === "number" && Number.isFinite(keepRecentTokens) && keepRecentTokens > 0) {
1821
+ if (typeof keepRecentTokens === "number" &&
1822
+ Number.isFinite(keepRecentTokens) &&
1823
+ keepRecentTokens > 0) {
1592
1824
  const normalizedKeepRecentTokens = normalizeManualKeepRecentTokens(keepRecentTokens, policy.manual);
1593
1825
  if (normalizedKeepRecentTokens > 0) {
1594
- settings = { ...settings, keepRecentTokens: normalizedKeepRecentTokens };
1826
+ settings = {
1827
+ ...settings,
1828
+ keepRecentTokens: normalizedKeepRecentTokens,
1829
+ };
1595
1830
  }
1596
1831
  }
1597
- if (phaseBoundary && !isDcpLitePhaseBoundaryEntry(pathEntries[pathEntries.length - 1])) {
1598
- const preflightEntries = [...pathEntries, createVirtualPhaseBoundaryEntry(pathEntries)];
1832
+ if (phaseBoundary &&
1833
+ !isDcpLitePhaseBoundaryEntry(pathEntries[pathEntries.length - 1])) {
1834
+ const preflightEntries = [
1835
+ ...pathEntries,
1836
+ createVirtualPhaseBoundaryEntry(pathEntries),
1837
+ ];
1599
1838
  const preflightPreparation = prepareCompaction(preflightEntries, settings, { phaseBoundary: true });
1600
1839
  if (!preflightPreparation) {
1601
1840
  const lastEntry = pathEntries[pathEntries.length - 1];
@@ -1626,7 +1865,9 @@ export class AgentSession {
1626
1865
  });
1627
1866
  pathEntries = this.sessionManager.getBranch();
1628
1867
  }
1629
- const preparation = prepareCompaction(pathEntries, settings, { phaseBoundary });
1868
+ const preparation = prepareCompaction(pathEntries, settings, {
1869
+ phaseBoundary,
1870
+ });
1630
1871
  if (!preparation) {
1631
1872
  // Check why we can't compact
1632
1873
  const lastEntry = pathEntries[pathEntries.length - 1];
@@ -1641,7 +1882,8 @@ export class AgentSession {
1641
1882
  const emitExtensionProgress = (delta, content) => {
1642
1883
  if (!delta && !content)
1643
1884
  return;
1644
- extensionProgressContent = content ?? `${extensionProgressContent}${delta}`;
1885
+ extensionProgressContent =
1886
+ content ?? `${extensionProgressContent}${delta}`;
1645
1887
  this._emit({
1646
1888
  type: "compaction_delta",
1647
1889
  delta,
@@ -1698,7 +1940,8 @@ export class AgentSession {
1698
1940
  }
1699
1941
  catch (error) {
1700
1942
  const message = error instanceof Error ? error.message : String(error);
1701
- const aborted = message === "Compaction cancelled" || (error instanceof Error && error.name === "AbortError");
1943
+ const aborted = message === "Compaction cancelled" ||
1944
+ (error instanceof Error && error.name === "AbortError");
1702
1945
  this._emit({
1703
1946
  type: "compaction_end",
1704
1947
  reason: "manual",
@@ -1750,12 +1993,16 @@ export class AgentSession {
1750
1993
  // This handles the case where user switched from a smaller-context model (e.g. opus)
1751
1994
  // to a larger-context model (e.g. codex) - the overflow error from the old model
1752
1995
  // shouldn't trigger compaction for the new model.
1753
- const sameModel = this.model && assistantMessage.provider === this.model.provider && assistantMessage.model === this.model.id;
1996
+ const sameModel = this.model &&
1997
+ assistantMessage.provider === this.model.provider &&
1998
+ assistantMessage.model === this.model.id;
1754
1999
  // Skip compaction checks if this assistant message is older than the latest
1755
2000
  // compaction boundary. This prevents a stale pre-compaction usage/error
1756
2001
  // from retriggering compaction on the first prompt after compaction.
1757
2002
  const compactionEntry = getLatestCompactionEntry(this.sessionManager.getBranch());
1758
- const assistantIsFromBeforeCompaction = compactionEntry !== null && assistantMessage.timestamp <= new Date(compactionEntry.timestamp).getTime();
2003
+ const assistantIsFromBeforeCompaction = compactionEntry !== null &&
2004
+ assistantMessage.timestamp <=
2005
+ new Date(compactionEntry.timestamp).getTime();
1759
2006
  if (assistantIsFromBeforeCompaction) {
1760
2007
  return false;
1761
2008
  }
@@ -1776,7 +2023,8 @@ export class AgentSession {
1776
2023
  // Remove the error message from agent state (it IS saved to session for history,
1777
2024
  // but we don't want it in context for the retry)
1778
2025
  const messages = this.agent.state.messages;
1779
- if (messages.length > 0 && messages[messages.length - 1].role === "assistant") {
2026
+ if (messages.length > 0 &&
2027
+ messages[messages.length - 1].role === "assistant") {
1780
2028
  this.agent.state.messages = messages.slice(0, -1);
1781
2029
  }
1782
2030
  return await this._runAutoCompaction("overflow", true);
@@ -1796,7 +2044,8 @@ export class AgentSession {
1796
2044
  const usageMsg = messages[estimate.lastUsageIndex];
1797
2045
  if (compactionEntry &&
1798
2046
  usageMsg.role === "assistant" &&
1799
- usageMsg.timestamp <= new Date(compactionEntry.timestamp).getTime()) {
2047
+ usageMsg.timestamp <=
2048
+ new Date(compactionEntry.timestamp).getTime()) {
1800
2049
  return false;
1801
2050
  }
1802
2051
  contextTokens = estimate.tokens;
@@ -1832,7 +2081,8 @@ export class AgentSession {
1832
2081
  if (!this._isAutoOlderHistoryCompactionEnabled(policy)) {
1833
2082
  return false;
1834
2083
  }
1835
- if (reason === "threshold" && this._autoOlderHistoryCooldownTurnsRemaining > 0) {
2084
+ if (reason === "threshold" &&
2085
+ this._autoOlderHistoryCooldownTurnsRemaining > 0) {
1836
2086
  return false;
1837
2087
  }
1838
2088
  if (this._compactionInProgress) {
@@ -1851,7 +2101,9 @@ export class AgentSession {
1851
2101
  this._autoCompactionAbortController = new AbortController();
1852
2102
  try {
1853
2103
  const pathEntries = this.sessionManager.getBranch();
1854
- const preparation = prepareCompaction(pathEntries, settings, { phaseBoundary: false });
2104
+ const preparation = prepareCompaction(pathEntries, settings, {
2105
+ phaseBoundary: false,
2106
+ });
1855
2107
  if (!preparation) {
1856
2108
  this._emit({
1857
2109
  type: "compaction_end",
@@ -1864,8 +2116,10 @@ export class AgentSession {
1864
2116
  }
1865
2117
  const compactableTokens = estimateCompactionTokenMetrics(preparation, "").tokensCompacted ?? 0;
1866
2118
  if (compactableTokens < policy.autoOlderHistory.minRawTokens) {
1867
- this._lastAutoOlderHistorySkipSignature = this._autoOlderHistorySkipSignature(pathEntries, compactableTokens);
1868
- this._autoOlderHistoryCooldownTurnsRemaining = policy.autoOlderHistory.cooldownTurns;
2119
+ this._lastAutoOlderHistorySkipSignature =
2120
+ this._autoOlderHistorySkipSignature(pathEntries, compactableTokens);
2121
+ this._autoOlderHistoryCooldownTurnsRemaining =
2122
+ policy.autoOlderHistory.cooldownTurns;
1869
2123
  this._emit({
1870
2124
  type: "compaction_end",
1871
2125
  reason,
@@ -1878,8 +2132,10 @@ export class AgentSession {
1878
2132
  return false;
1879
2133
  }
1880
2134
  const skipSignature = this._autoOlderHistorySkipSignature(pathEntries, compactableTokens);
1881
- if (reason === "threshold" && this._lastAutoOlderHistorySkipSignature === skipSignature) {
1882
- this._autoOlderHistoryCooldownTurnsRemaining = policy.autoOlderHistory.cooldownTurns;
2135
+ if (reason === "threshold" &&
2136
+ this._lastAutoOlderHistorySkipSignature === skipSignature) {
2137
+ this._autoOlderHistoryCooldownTurnsRemaining =
2138
+ policy.autoOlderHistory.cooldownTurns;
1883
2139
  this._emit({
1884
2140
  type: "compaction_end",
1885
2141
  reason,
@@ -1894,7 +2150,8 @@ export class AgentSession {
1894
2150
  const emitExtensionProgress = (delta, content) => {
1895
2151
  if (!delta && !content)
1896
2152
  return;
1897
- extensionProgressContent = content ?? `${extensionProgressContent}${delta}`;
2153
+ extensionProgressContent =
2154
+ content ?? `${extensionProgressContent}${delta}`;
1898
2155
  this._emit({
1899
2156
  type: "compaction_delta",
1900
2157
  delta,
@@ -1902,7 +2159,9 @@ export class AgentSession {
1902
2159
  });
1903
2160
  };
1904
2161
  const beforeCompactResult = await this._emitSessionBeforeCompact(preparation, pathEntries, undefined, this._autoCompactionAbortController.signal, emitExtensionProgress);
1905
- const extensionCompaction = beforeCompactResult?.cancel ? undefined : beforeCompactResult?.compaction;
2162
+ const extensionCompaction = beforeCompactResult?.cancel
2163
+ ? undefined
2164
+ : beforeCompactResult?.compaction;
1906
2165
  const compactResult = this._buildDcpCompactionResult(preparation, extensionCompaction);
1907
2166
  if (this._autoCompactionAbortController.signal.aborted) {
1908
2167
  this._emit({
@@ -1917,7 +2176,8 @@ export class AgentSession {
1917
2176
  const skippedResult = this._markIfNonShrinking(compactResult);
1918
2177
  if (skippedResult) {
1919
2178
  this._lastAutoOlderHistorySkipSignature = skipSignature;
1920
- this._autoOlderHistoryCooldownTurnsRemaining = policy.autoOlderHistory.cooldownTurns;
2179
+ this._autoOlderHistoryCooldownTurnsRemaining =
2180
+ policy.autoOlderHistory.cooldownTurns;
1921
2181
  this._emit({
1922
2182
  type: "compaction_end",
1923
2183
  reason,
@@ -1931,7 +2191,8 @@ export class AgentSession {
1931
2191
  return false;
1932
2192
  }
1933
2193
  this._lastAutoOlderHistorySkipSignature = undefined;
1934
- this._autoOlderHistoryCooldownTurnsRemaining = policy.autoOlderHistory.cooldownTurns;
2194
+ this._autoOlderHistoryCooldownTurnsRemaining =
2195
+ policy.autoOlderHistory.cooldownTurns;
1935
2196
  this._emitCompactionResultSummary(compactResult);
1936
2197
  const { summary, firstKeptEntryId, tokensBefore, details } = compactResult;
1937
2198
  this.sessionManager.appendCompaction(summary, firstKeptEntryId, tokensBefore, details, false);
@@ -1948,11 +2209,18 @@ export class AgentSession {
1948
2209
  });
1949
2210
  }
1950
2211
  const result = { ...compactResult, details };
1951
- this._emit({ type: "compaction_end", reason, result, aborted: false, willRetry });
2212
+ this._emit({
2213
+ type: "compaction_end",
2214
+ reason,
2215
+ result,
2216
+ aborted: false,
2217
+ willRetry,
2218
+ });
1952
2219
  if (willRetry) {
1953
2220
  const messages = this.agent.state.messages;
1954
2221
  const lastMsg = messages[messages.length - 1];
1955
- if (lastMsg?.role === "assistant" && lastMsg.stopReason === "error") {
2222
+ if (lastMsg?.role === "assistant" &&
2223
+ lastMsg.stopReason === "error") {
1956
2224
  this.agent.state.messages = messages.slice(0, -1);
1957
2225
  }
1958
2226
  return true;
@@ -2015,7 +2283,9 @@ export class AgentSession {
2015
2283
  return;
2016
2284
  }
2017
2285
  const { skillPaths, promptPaths, themePaths } = await this._extensionRunner.emitResourcesDiscover(this._cwd, reason);
2018
- if (skillPaths.length === 0 && promptPaths.length === 0 && themePaths.length === 0) {
2286
+ if (skillPaths.length === 0 &&
2287
+ promptPaths.length === 0 &&
2288
+ themePaths.length === 0) {
2019
2289
  return;
2020
2290
  }
2021
2291
  const extensionPaths = {
@@ -2030,7 +2300,9 @@ export class AgentSession {
2030
2300
  buildExtensionResourcePaths(entries) {
2031
2301
  return entries.map((entry) => {
2032
2302
  const source = this.getExtensionSourceLabel(entry.extensionPath);
2033
- const baseDir = entry.extensionPath.startsWith("<") ? undefined : dirname(entry.extensionPath);
2303
+ const baseDir = entry.extensionPath.startsWith("<")
2304
+ ? undefined
2305
+ : dirname(entry.extensionPath);
2034
2306
  return {
2035
2307
  path: entry.path,
2036
2308
  metadata: {
@@ -2071,7 +2343,9 @@ export class AgentSession {
2071
2343
  }
2072
2344
  _bindExtensionCore(runner) {
2073
2345
  const getCommands = () => {
2074
- const extensionCommands = runner.getRegisteredCommands().map((command) => ({
2346
+ const extensionCommands = runner
2347
+ .getRegisteredCommands()
2348
+ .map((command) => ({
2075
2349
  name: command.invocationName,
2076
2350
  description: command.description,
2077
2351
  source: "extension",
@@ -2083,7 +2357,9 @@ export class AgentSession {
2083
2357
  source: "prompt",
2084
2358
  sourceInfo: template.sourceInfo,
2085
2359
  }));
2086
- const skills = this._resourceLoader.getSkills().skills.map((skill) => ({
2360
+ const skills = this._resourceLoader
2361
+ .getSkills()
2362
+ .skills.map((skill) => ({
2087
2363
  name: `skill:${skill.name}`,
2088
2364
  description: skill.description,
2089
2365
  source: "skill",
@@ -2192,7 +2468,9 @@ export class AgentSession {
2192
2468
  ...registeredTools,
2193
2469
  ...this._customTools.map((definition) => ({
2194
2470
  definition,
2195
- sourceInfo: createSyntheticSourceInfo(`<sdk:${definition.name}>`, { source: "sdk" }),
2471
+ sourceInfo: createSyntheticSourceInfo(`<sdk:${definition.name}>`, {
2472
+ source: "sdk",
2473
+ }),
2196
2474
  })),
2197
2475
  ].filter((tool) => isAllowedTool(tool.definition.name));
2198
2476
  const definitionRegistry = new Map(Array.from(this._baseToolDefinitions.entries())
@@ -2201,7 +2479,9 @@ export class AgentSession {
2201
2479
  name,
2202
2480
  {
2203
2481
  definition,
2204
- sourceInfo: createSyntheticSourceInfo(`<builtin:${name}>`, { source: "builtin" }),
2482
+ sourceInfo: createSyntheticSourceInfo(`<builtin:${name}>`, {
2483
+ source: "builtin",
2484
+ }),
2205
2485
  },
2206
2486
  ]));
2207
2487
  for (const tool of allCustomTools) {
@@ -2220,7 +2500,9 @@ export class AgentSession {
2220
2500
  this._toolPromptGuidelines = new Map(Array.from(definitionRegistry.values())
2221
2501
  .map(({ definition }) => {
2222
2502
  const guidelines = this._normalizePromptGuidelines(definition.promptGuidelines);
2223
- return guidelines.length > 0 ? [definition.name, guidelines] : undefined;
2503
+ return guidelines.length > 0
2504
+ ? [definition.name, guidelines]
2505
+ : undefined;
2224
2506
  })
2225
2507
  .filter((entry) => entry !== undefined));
2226
2508
  const runner = this._extensionRunner;
@@ -2236,7 +2518,9 @@ export class AgentSession {
2236
2518
  toolRegistry.set(tool.name, tool);
2237
2519
  }
2238
2520
  this._toolRegistry = toolRegistry;
2239
- const nextActiveToolNames = (options?.activeToolNames ? [...options.activeToolNames] : [...previousActiveToolNames]).filter((name) => isAllowedTool(name));
2521
+ const nextActiveToolNames = (options?.activeToolNames
2522
+ ? [...options.activeToolNames]
2523
+ : [...previousActiveToolNames]).filter((name) => isAllowedTool(name));
2240
2524
  if (allowedToolNames) {
2241
2525
  for (const toolName of this._toolRegistry.keys()) {
2242
2526
  if (allowedToolNames.has(toolName)) {
@@ -2271,7 +2555,10 @@ export class AgentSession {
2271
2555
  read: { autoResizeImages },
2272
2556
  bash: { commandPrefix: shellCommandPrefix, shellPath },
2273
2557
  });
2274
- this._baseToolDefinitions = new Map(Object.entries(baseToolDefinitions).map(([name, tool]) => [name, tool]));
2558
+ this._baseToolDefinitions = new Map(Object.entries(baseToolDefinitions).map(([name, tool]) => [
2559
+ name,
2560
+ tool,
2561
+ ]));
2275
2562
  const extensionsResult = this._resourceLoader.getExtensions();
2276
2563
  if (options.flagValues) {
2277
2564
  for (const [name, value] of options.flagValues) {
@@ -2295,7 +2582,10 @@ export class AgentSession {
2295
2582
  }
2296
2583
  async reload() {
2297
2584
  const previousFlagValues = this._extensionRunner.getFlagValues();
2298
- await emitSessionShutdownEvent(this._extensionRunner, { type: "session_shutdown", reason: "reload" });
2585
+ await emitSessionShutdownEvent(this._extensionRunner, {
2586
+ type: "session_shutdown",
2587
+ reason: "reload",
2588
+ });
2299
2589
  await this.settingsManager.reload();
2300
2590
  resetApiProviders();
2301
2591
  await this._resourceLoader.reload();
@@ -2309,7 +2599,10 @@ export class AgentSession {
2309
2599
  this._extensionShutdownHandler ||
2310
2600
  this._extensionErrorListener;
2311
2601
  if (hasBindings) {
2312
- await this._extensionRunner.emit({ type: "session_start", reason: "reload" });
2602
+ await this._extensionRunner.emit({
2603
+ type: "session_start",
2604
+ reason: "reload",
2605
+ });
2313
2606
  await this.extendResourcesFromExtensions("reload");
2314
2607
  }
2315
2608
  }
@@ -2356,7 +2649,8 @@ export class AgentSession {
2356
2649
  });
2357
2650
  // Remove error message from agent state (keep in session for history)
2358
2651
  const messages = this.agent.state.messages;
2359
- if (messages.length > 0 && messages[messages.length - 1].role === "assistant") {
2652
+ if (messages.length > 0 &&
2653
+ messages[messages.length - 1].role === "assistant") {
2360
2654
  this.agent.state.messages = messages.slice(0, -1);
2361
2655
  }
2362
2656
  // Wait with exponential backoff (abortable)
@@ -2495,7 +2789,10 @@ export class AgentSession {
2495
2789
  */
2496
2790
  setSessionName(name) {
2497
2791
  this.sessionManager.appendSessionInfo(name);
2498
- this._emit({ type: "session_info_changed", name: this.sessionManager.getSessionName() });
2792
+ this._emit({
2793
+ type: "session_info_changed",
2794
+ name: this.sessionManager.getSessionName(),
2795
+ });
2499
2796
  }
2500
2797
  // =========================================================================
2501
2798
  // Tree Navigation
@@ -2574,7 +2871,9 @@ export class AgentSession {
2574
2871
  // Run default summarizer if needed
2575
2872
  let summaryText;
2576
2873
  let summaryDetails;
2577
- if (options.summarize && entriesToSummarize.length > 0 && !extensionSummary) {
2874
+ if (options.summarize &&
2875
+ entriesToSummarize.length > 0 &&
2876
+ !extensionSummary) {
2578
2877
  const model = this.model;
2579
2878
  const { apiKey, headers } = await this._getRequiredRequestAuth(model);
2580
2879
  const branchSummarySettings = this.settingsManager.getBranchSummarySettings();
@@ -2606,7 +2905,8 @@ export class AgentSession {
2606
2905
  // Determine the new leaf position based on target type
2607
2906
  let newLeafId;
2608
2907
  let editorText;
2609
- if (targetEntry.type === "message" && targetEntry.message.role === "user") {
2908
+ if (targetEntry.type === "message" &&
2909
+ targetEntry.message.role === "user") {
2610
2910
  // User message: leaf = parent (null if root), text goes to editor
2611
2911
  newLeafId = targetEntry.parentId;
2612
2912
  editorText = this._extractUserMessageText(targetEntry.message.content);
@@ -2761,7 +3061,8 @@ export class AgentSession {
2761
3061
  const entry = branchEntries[i];
2762
3062
  if (entry.type === "message" && entry.message.role === "assistant") {
2763
3063
  const assistant = entry.message;
2764
- if (assistant.stopReason !== "aborted" && assistant.stopReason !== "error") {
3064
+ if (assistant.stopReason !== "aborted" &&
3065
+ assistant.stopReason !== "error") {
2765
3066
  const contextTokens = calculateContextTokens(assistant.usage);
2766
3067
  if (contextTokens > 0) {
2767
3068
  hasPostCompactionUsage = true;
@@ -2797,7 +3098,8 @@ export class AgentSession {
2797
3098
  * @returns The resolved output file path.
2798
3099
  */
2799
3100
  exportToJsonl(outputPath) {
2800
- const filePath = resolvePath(outputPath ?? `session-${new Date().toISOString().replace(/[:.]/g, "-")}.jsonl`, process.cwd());
3101
+ const filePath = resolvePath(outputPath ??
3102
+ `session-${new Date().toISOString().replace(/[:.]/g, "-")}.jsonl`, process.cwd());
2801
3103
  const dir = dirname(filePath);
2802
3104
  if (!existsSync(dir)) {
2803
3105
  mkdirSync(dir, { recursive: true });