@dexto/core 1.5.3 → 1.5.5

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 (143) hide show
  1. package/dist/agent/DextoAgent.cjs +190 -1
  2. package/dist/agent/DextoAgent.d.ts +71 -0
  3. package/dist/agent/DextoAgent.d.ts.map +1 -1
  4. package/dist/agent/DextoAgent.js +181 -1
  5. package/dist/agent/schemas.d.ts +51 -21
  6. package/dist/agent/schemas.d.ts.map +1 -1
  7. package/dist/context/compaction/overflow.cjs +6 -10
  8. package/dist/context/compaction/overflow.d.ts +14 -11
  9. package/dist/context/compaction/overflow.d.ts.map +1 -1
  10. package/dist/context/compaction/overflow.js +6 -10
  11. package/dist/context/compaction/providers/reactive-overflow-provider.cjs +15 -0
  12. package/dist/context/compaction/providers/reactive-overflow-provider.d.ts +15 -0
  13. package/dist/context/compaction/providers/reactive-overflow-provider.d.ts.map +1 -1
  14. package/dist/context/compaction/providers/reactive-overflow-provider.js +15 -0
  15. package/dist/context/compaction/schemas.cjs +22 -2
  16. package/dist/context/compaction/schemas.d.ts +45 -0
  17. package/dist/context/compaction/schemas.d.ts.map +1 -1
  18. package/dist/context/compaction/schemas.js +22 -2
  19. package/dist/context/compaction/strategies/reactive-overflow.cjs +168 -26
  20. package/dist/context/compaction/strategies/reactive-overflow.d.ts +22 -0
  21. package/dist/context/compaction/strategies/reactive-overflow.d.ts.map +1 -1
  22. package/dist/context/compaction/strategies/reactive-overflow.js +168 -26
  23. package/dist/context/compaction/types.d.ts +13 -1
  24. package/dist/context/compaction/types.d.ts.map +1 -1
  25. package/dist/context/manager.cjs +278 -31
  26. package/dist/context/manager.d.ts +192 -5
  27. package/dist/context/manager.d.ts.map +1 -1
  28. package/dist/context/manager.js +285 -32
  29. package/dist/context/types.d.ts +6 -0
  30. package/dist/context/types.d.ts.map +1 -1
  31. package/dist/context/utils.cjs +77 -11
  32. package/dist/context/utils.d.ts +86 -8
  33. package/dist/context/utils.d.ts.map +1 -1
  34. package/dist/context/utils.js +71 -11
  35. package/dist/errors/types.cjs +0 -2
  36. package/dist/errors/types.d.ts +1 -5
  37. package/dist/errors/types.d.ts.map +1 -1
  38. package/dist/errors/types.js +0 -2
  39. package/dist/events/index.cjs +2 -0
  40. package/dist/events/index.d.ts +21 -6
  41. package/dist/events/index.d.ts.map +1 -1
  42. package/dist/events/index.js +2 -0
  43. package/dist/llm/executor/stream-processor.cjs +104 -28
  44. package/dist/llm/executor/stream-processor.d.ts +7 -0
  45. package/dist/llm/executor/stream-processor.d.ts.map +1 -1
  46. package/dist/llm/executor/stream-processor.js +104 -28
  47. package/dist/llm/executor/turn-executor.cjs +147 -30
  48. package/dist/llm/executor/turn-executor.d.ts +28 -10
  49. package/dist/llm/executor/turn-executor.d.ts.map +1 -1
  50. package/dist/llm/executor/turn-executor.js +147 -30
  51. package/dist/llm/formatters/vercel.cjs +36 -28
  52. package/dist/llm/formatters/vercel.d.ts.map +1 -1
  53. package/dist/llm/formatters/vercel.js +36 -28
  54. package/dist/llm/services/factory.cjs +3 -2
  55. package/dist/llm/services/factory.d.ts +3 -1
  56. package/dist/llm/services/factory.d.ts.map +1 -1
  57. package/dist/llm/services/factory.js +3 -2
  58. package/dist/llm/services/vercel.cjs +31 -6
  59. package/dist/llm/services/vercel.d.ts +18 -3
  60. package/dist/llm/services/vercel.d.ts.map +1 -1
  61. package/dist/llm/services/vercel.js +31 -6
  62. package/dist/session/chat-session.cjs +29 -13
  63. package/dist/session/chat-session.d.ts +6 -4
  64. package/dist/session/chat-session.d.ts.map +1 -1
  65. package/dist/session/chat-session.js +29 -13
  66. package/dist/session/session-manager.cjs +11 -0
  67. package/dist/session/session-manager.d.ts +7 -0
  68. package/dist/session/session-manager.d.ts.map +1 -1
  69. package/dist/session/session-manager.js +11 -0
  70. package/dist/session/title-generator.cjs +2 -2
  71. package/dist/session/title-generator.js +2 -2
  72. package/dist/systemPrompt/in-built-prompts.cjs +36 -0
  73. package/dist/systemPrompt/in-built-prompts.d.ts +18 -1
  74. package/dist/systemPrompt/in-built-prompts.d.ts.map +1 -1
  75. package/dist/systemPrompt/in-built-prompts.js +25 -0
  76. package/dist/systemPrompt/manager.cjs +22 -0
  77. package/dist/systemPrompt/manager.d.ts +10 -0
  78. package/dist/systemPrompt/manager.d.ts.map +1 -1
  79. package/dist/systemPrompt/manager.js +22 -0
  80. package/dist/systemPrompt/registry.cjs +2 -1
  81. package/dist/systemPrompt/registry.d.ts +1 -1
  82. package/dist/systemPrompt/registry.d.ts.map +1 -1
  83. package/dist/systemPrompt/registry.js +2 -1
  84. package/dist/systemPrompt/schemas.cjs +7 -0
  85. package/dist/systemPrompt/schemas.d.ts +13 -13
  86. package/dist/systemPrompt/schemas.d.ts.map +1 -1
  87. package/dist/systemPrompt/schemas.js +7 -0
  88. package/dist/telemetry/telemetry.cjs +12 -5
  89. package/dist/telemetry/telemetry.d.ts.map +1 -1
  90. package/dist/telemetry/telemetry.js +12 -5
  91. package/dist/utils/index.cjs +3 -1
  92. package/dist/utils/index.d.ts +1 -0
  93. package/dist/utils/index.d.ts.map +1 -1
  94. package/dist/utils/index.js +1 -0
  95. package/package.json +15 -5
  96. package/dist/filesystem/error-codes.cjs +0 -53
  97. package/dist/filesystem/error-codes.d.ts +0 -31
  98. package/dist/filesystem/error-codes.d.ts.map +0 -1
  99. package/dist/filesystem/error-codes.js +0 -30
  100. package/dist/filesystem/errors.cjs +0 -303
  101. package/dist/filesystem/errors.d.ts +0 -109
  102. package/dist/filesystem/errors.d.ts.map +0 -1
  103. package/dist/filesystem/errors.js +0 -280
  104. package/dist/filesystem/filesystem-service.cjs +0 -534
  105. package/dist/filesystem/filesystem-service.d.ts +0 -97
  106. package/dist/filesystem/filesystem-service.d.ts.map +0 -1
  107. package/dist/filesystem/filesystem-service.js +0 -501
  108. package/dist/filesystem/index.cjs +0 -37
  109. package/dist/filesystem/index.d.ts +0 -11
  110. package/dist/filesystem/index.d.ts.map +0 -1
  111. package/dist/filesystem/index.js +0 -11
  112. package/dist/filesystem/path-validator.cjs +0 -250
  113. package/dist/filesystem/path-validator.d.ts +0 -103
  114. package/dist/filesystem/path-validator.d.ts.map +0 -1
  115. package/dist/filesystem/path-validator.js +0 -217
  116. package/dist/filesystem/types.cjs +0 -16
  117. package/dist/filesystem/types.d.ts +0 -175
  118. package/dist/filesystem/types.d.ts.map +0 -1
  119. package/dist/filesystem/types.js +0 -0
  120. package/dist/process/command-validator.cjs +0 -554
  121. package/dist/process/command-validator.d.ts +0 -49
  122. package/dist/process/command-validator.d.ts.map +0 -1
  123. package/dist/process/command-validator.js +0 -531
  124. package/dist/process/error-codes.cjs +0 -47
  125. package/dist/process/error-codes.d.ts +0 -25
  126. package/dist/process/error-codes.d.ts.map +0 -1
  127. package/dist/process/error-codes.js +0 -24
  128. package/dist/process/errors.cjs +0 -244
  129. package/dist/process/errors.d.ts +0 -87
  130. package/dist/process/errors.d.ts.map +0 -1
  131. package/dist/process/errors.js +0 -221
  132. package/dist/process/index.cjs +0 -37
  133. package/dist/process/index.d.ts +0 -11
  134. package/dist/process/index.d.ts.map +0 -1
  135. package/dist/process/index.js +0 -11
  136. package/dist/process/process-service.cjs +0 -497
  137. package/dist/process/process-service.d.ts +0 -69
  138. package/dist/process/process-service.d.ts.map +0 -1
  139. package/dist/process/process-service.js +0 -464
  140. package/dist/process/types.cjs +0 -16
  141. package/dist/process/types.d.ts +0 -107
  142. package/dist/process/types.d.ts.map +0 -1
  143. package/dist/process/types.js +0 -0
@@ -2,11 +2,31 @@ import "../../chunk-PTJYTZNU.js";
2
2
  import { z } from "zod";
3
3
  const CompactionConfigSchema = z.object({
4
4
  type: z.string().describe("Compaction provider type"),
5
- enabled: z.boolean().default(true).describe("Enable or disable compaction")
5
+ enabled: z.boolean().default(true).describe("Enable or disable compaction"),
6
+ /**
7
+ * Maximum context tokens before compaction triggers.
8
+ * When set, overrides the model's context window for compaction threshold.
9
+ * Useful for capping context size below the model's maximum limit.
10
+ * Example: Set to 50000 to trigger compaction at 50K tokens even if
11
+ * the model supports 200K tokens.
12
+ */
13
+ maxContextTokens: z.number().positive().optional().describe(
14
+ "Maximum context tokens before compaction triggers. Overrides model context window when set."
15
+ ),
16
+ /**
17
+ * Percentage of context window that triggers compaction (0.0 to 1.0).
18
+ * Default is 0.9 (90%), leaving a 10% buffer to avoid context degradation.
19
+ * Set lower values to trigger compaction earlier.
20
+ * Example: 0.8 triggers compaction when 80% of context is used.
21
+ */
22
+ thresholdPercent: z.number().min(0.1).max(1).default(0.9).describe(
23
+ "Percentage of context window that triggers compaction (0.1 to 1.0, default 0.9)"
24
+ )
6
25
  }).passthrough().describe("Context compaction configuration");
7
26
  const DEFAULT_COMPACTION_CONFIG = {
8
27
  type: "reactive-overflow",
9
- enabled: true
28
+ enabled: true,
29
+ thresholdPercent: 0.9
10
30
  };
11
31
  export {
12
32
  CompactionConfigSchema,
@@ -26,15 +26,36 @@ var import_types = require("../../types.js");
26
26
  const DEFAULT_OPTIONS = {
27
27
  preserveLastNTurns: 2,
28
28
  maxSummaryTokens: 2e3,
29
- summaryPrompt: `You are a conversation summarizer. Summarize the following conversation history concisely, focusing on:
30
- - What tasks were attempted and their outcomes
31
- - Current state and context the assistant needs to remember
32
- - Any important decisions or information discovered
33
- - What the user was trying to accomplish
29
+ summaryPrompt: `You are a conversation summarizer creating a structured summary for session continuation.
34
30
 
35
- Be concise but preserve essential context. Output only the summary, no preamble.
31
+ Analyze the conversation and produce a summary in the following XML format:
36
32
 
37
- Conversation:
33
+ <session_compaction>
34
+ <conversation_history>
35
+ A concise summary of what happened in the conversation:
36
+ - Tasks attempted and their outcomes (success/failure/in-progress)
37
+ - Important decisions made
38
+ - Key information discovered (file paths, configurations, errors encountered)
39
+ - Tools used and their results
40
+ </conversation_history>
41
+
42
+ <current_task>
43
+ The most recent task or instruction the user requested that may still be in progress.
44
+ Be specific - include the exact request and current status.
45
+ </current_task>
46
+
47
+ <important_context>
48
+ Critical state that must be preserved:
49
+ - File paths being worked on
50
+ - Variable values or configurations
51
+ - Error messages that need addressing
52
+ - Any pending actions or next steps
53
+ </important_context>
54
+ </session_compaction>
55
+
56
+ IMPORTANT: The assistant will continue working based on this summary. Ensure the current_task section clearly states what needs to be done next.
57
+
58
+ Conversation to summarize:
38
59
  {conversation}`
39
60
  };
40
61
  class ReactiveOverflowStrategy {
@@ -63,15 +84,73 @@ class ReactiveOverflowStrategy {
63
84
  this.logger.debug("ReactiveOverflowStrategy: History too short, skipping compaction");
64
85
  return [];
65
86
  }
87
+ let existingSummaryIndex = -1;
88
+ for (let i = history.length - 1; i >= 0; i--) {
89
+ const msg = history[i];
90
+ if (msg?.metadata?.isSummary === true || msg?.metadata?.isSessionSummary === true) {
91
+ existingSummaryIndex = i;
92
+ break;
93
+ }
94
+ }
95
+ if (existingSummaryIndex !== -1) {
96
+ const messagesAfterSummary = history.slice(existingSummaryIndex + 1);
97
+ if (messagesAfterSummary.length <= 4) {
98
+ this.logger.debug(
99
+ `ReactiveOverflowStrategy: Only ${messagesAfterSummary.length} messages after existing summary, skipping re-compaction`
100
+ );
101
+ return [];
102
+ }
103
+ this.logger.info(
104
+ `ReactiveOverflowStrategy: Found existing summary at index ${existingSummaryIndex}, working with ${messagesAfterSummary.length} messages after it`
105
+ );
106
+ return this.compactSubset(messagesAfterSummary, history, existingSummaryIndex);
107
+ }
66
108
  const { toSummarize, toKeep } = this.splitHistory(history);
67
109
  if (toSummarize.length === 0) {
68
110
  this.logger.debug("ReactiveOverflowStrategy: No messages to summarize");
69
111
  return [];
70
112
  }
113
+ const currentTaskMessage = this.findCurrentTaskMessage(history);
71
114
  this.logger.info(
72
115
  `ReactiveOverflowStrategy: Summarizing ${toSummarize.length} messages, keeping ${toKeep.length}`
73
116
  );
74
- const summary = await this.generateSummary(toSummarize);
117
+ const summary = await this.generateSummary(toSummarize, currentTaskMessage);
118
+ const summaryMessage = {
119
+ role: "assistant",
120
+ content: [{ type: "text", text: summary }],
121
+ timestamp: Date.now(),
122
+ metadata: {
123
+ isSummary: true,
124
+ summarizedAt: Date.now(),
125
+ originalMessageCount: toSummarize.length,
126
+ originalFirstTimestamp: toSummarize[0]?.timestamp,
127
+ originalLastTimestamp: toSummarize[toSummarize.length - 1]?.timestamp
128
+ }
129
+ };
130
+ return [summaryMessage];
131
+ }
132
+ /**
133
+ * Handle re-compaction when there's already a summary in history.
134
+ * Only summarizes messages AFTER the existing summary, preventing
135
+ * cascading summaries of summaries.
136
+ *
137
+ * @param messagesAfterSummary Messages after the existing summary
138
+ * @param fullHistory The complete history (for current task detection)
139
+ * @param existingSummaryIndex Index of the existing summary in fullHistory
140
+ * @returns Array with single summary message, or empty if nothing to summarize
141
+ */
142
+ async compactSubset(messagesAfterSummary, fullHistory, existingSummaryIndex) {
143
+ const { toSummarize, toKeep } = this.splitHistory(messagesAfterSummary);
144
+ if (toSummarize.length === 0) {
145
+ this.logger.debug("ReactiveOverflowStrategy: No messages to summarize in subset");
146
+ return [];
147
+ }
148
+ const currentTaskMessage = this.findCurrentTaskMessage(fullHistory);
149
+ this.logger.info(
150
+ `ReactiveOverflowStrategy (re-compact): Summarizing ${toSummarize.length} messages after existing summary, keeping ${toKeep.length}`
151
+ );
152
+ const summary = await this.generateSummary(toSummarize, currentTaskMessage);
153
+ const absoluteOriginalMessageCount = existingSummaryIndex + 1 + toSummarize.length;
75
154
  const summaryMessage = {
76
155
  role: "assistant",
77
156
  content: [{ type: "text", text: summary }],
@@ -79,16 +158,43 @@ class ReactiveOverflowStrategy {
79
158
  metadata: {
80
159
  isSummary: true,
81
160
  summarizedAt: Date.now(),
82
- summarizedMessageCount: toSummarize.length,
161
+ originalMessageCount: absoluteOriginalMessageCount,
162
+ isRecompaction: true,
163
+ // Mark that this is a re-compaction
83
164
  originalFirstTimestamp: toSummarize[0]?.timestamp,
84
165
  originalLastTimestamp: toSummarize[toSummarize.length - 1]?.timestamp
85
166
  }
86
167
  };
87
168
  return [summaryMessage];
88
169
  }
170
+ /**
171
+ * Find the most recent user message that represents the current task.
172
+ * This helps preserve context about what the user is currently asking for.
173
+ */
174
+ findCurrentTaskMessage(history) {
175
+ for (let i = history.length - 1; i >= 0; i--) {
176
+ const msg = history[i];
177
+ if (msg?.role === "user") {
178
+ if (typeof msg.content === "string") {
179
+ return msg.content;
180
+ } else if (Array.isArray(msg.content)) {
181
+ const textParts = msg.content.filter(
182
+ (part) => part.type === "text"
183
+ ).map((part) => part.text).join("\n");
184
+ if (textParts.length > 0) {
185
+ return textParts;
186
+ }
187
+ }
188
+ }
189
+ }
190
+ return null;
191
+ }
89
192
  /**
90
193
  * Split history into messages to summarize and messages to keep.
91
194
  * Keeps the last N turns (user + assistant pairs) intact.
195
+ *
196
+ * For long agentic conversations with many tool calls, this also ensures
197
+ * we don't try to keep too many messages even within preserved turns.
92
198
  */
93
199
  splitHistory(history) {
94
200
  const turnsToKeep = this.options.preserveLastNTurns;
@@ -103,20 +209,25 @@ class ReactiveOverflowStrategy {
103
209
  }
104
210
  if (userMessageIndices.length > 0) {
105
211
  const splitIndex = userMessageIndices[0];
106
- if (splitIndex !== void 0) {
107
- if (splitIndex === 0) {
108
- return {
109
- toSummarize: [],
110
- toKeep: history
111
- };
112
- }
212
+ if (splitIndex !== void 0 && splitIndex > 0) {
113
213
  return {
114
214
  toSummarize: history.slice(0, splitIndex),
115
215
  toKeep: history.slice(splitIndex)
116
216
  };
117
217
  }
118
218
  }
119
- const keepCount = Math.min(4, history.length);
219
+ const minKeep = 3;
220
+ const maxKeepPercent = 0.2;
221
+ const keepCount = Math.max(minKeep, Math.floor(history.length * maxKeepPercent));
222
+ if (keepCount >= history.length) {
223
+ return {
224
+ toSummarize: [],
225
+ toKeep: history
226
+ };
227
+ }
228
+ this.logger.debug(
229
+ `splitHistory: Using fallback - keeping last ${keepCount} of ${history.length} messages`
230
+ );
120
231
  return {
121
232
  toSummarize: history.slice(0, -keepCount),
122
233
  toKeep: history.slice(-keepCount)
@@ -124,21 +235,36 @@ class ReactiveOverflowStrategy {
124
235
  }
125
236
  /**
126
237
  * Generate an LLM summary of the messages.
238
+ *
239
+ * @param messages Messages to summarize
240
+ * @param currentTask The most recent user message (current task context)
127
241
  */
128
- async generateSummary(messages) {
242
+ async generateSummary(messages, currentTask) {
129
243
  const formattedConversation = this.formatMessagesForSummary(messages);
130
- const prompt = this.options.summaryPrompt.replace("{conversation}", formattedConversation);
244
+ let conversationWithContext = formattedConversation;
245
+ if (currentTask) {
246
+ conversationWithContext += `
247
+
248
+ --- CURRENT TASK (most recent user request) ---
249
+ ${currentTask}`;
250
+ }
251
+ const prompt = this.options.summaryPrompt.replace(
252
+ "{conversation}",
253
+ conversationWithContext
254
+ );
131
255
  try {
132
256
  const result = await (0, import_ai.generateText)({
133
257
  model: this.model,
134
258
  prompt,
135
259
  maxOutputTokens: this.options.maxSummaryTokens
136
260
  });
137
- return `[Previous conversation summary]
261
+ return `[Session Compaction Summary]
138
262
  ${result.text}`;
139
263
  } catch (error) {
140
- this.logger.error("ReactiveOverflowStrategy: Failed to generate summary", { error });
141
- return this.createFallbackSummary(messages);
264
+ this.logger.error(
265
+ `ReactiveOverflowStrategy: Failed to generate summary - ${error instanceof Error ? error.message : String(error)}`
266
+ );
267
+ return this.createFallbackSummary(messages, currentTask);
142
268
  }
143
269
  }
144
270
  /**
@@ -174,7 +300,7 @@ ${result.text}`;
174
300
  /**
175
301
  * Create a fallback summary if LLM call fails.
176
302
  */
177
- createFallbackSummary(messages) {
303
+ createFallbackSummary(messages, currentTask) {
178
304
  const userMessages = messages.filter((m) => m.role === "user");
179
305
  const assistantWithTools = messages.filter(
180
306
  (m) => (0, import_types.isAssistantMessage)(m) && !!m.toolCalls && m.toolCalls.length > 0
@@ -190,9 +316,25 @@ ${result.text}`;
190
316
  assistantWithTools.flatMap((m) => m.toolCalls.map((tc) => tc.function.name))
191
317
  )
192
318
  ].join(", ");
193
- return `[Previous conversation summary - fallback]
194
- User discussed: ${userTopics || "various topics"}
195
- Tools used: ${toolsUsed || "none"}`;
319
+ let fallback = `[Session Compaction Summary - Fallback]
320
+ <session_compaction>
321
+ <conversation_history>
322
+ User discussed: ${userTopics || "various topics"}
323
+ Tools used: ${toolsUsed || "none"}
324
+ Messages summarized: ${messages.length}
325
+ </conversation_history>`;
326
+ if (currentTask) {
327
+ fallback += `
328
+ <current_task>
329
+ ${currentTask.slice(0, 500)}${currentTask.length > 500 ? "..." : ""}
330
+ </current_task>`;
331
+ }
332
+ fallback += `
333
+ <important_context>
334
+ Note: This is a fallback summary due to LLM error. Context may be incomplete.
335
+ </important_context>
336
+ </session_compaction>`;
337
+ return fallback;
196
338
  }
197
339
  }
198
340
  // Annotate the CommonJS export names for ESM import in node:
@@ -60,13 +60,35 @@ export declare class ReactiveOverflowStrategy implements ICompactionStrategy {
60
60
  * @returns Array with single summary message to add, or empty if nothing to summarize
61
61
  */
62
62
  compact(history: readonly InternalMessage[]): Promise<InternalMessage[]>;
63
+ /**
64
+ * Handle re-compaction when there's already a summary in history.
65
+ * Only summarizes messages AFTER the existing summary, preventing
66
+ * cascading summaries of summaries.
67
+ *
68
+ * @param messagesAfterSummary Messages after the existing summary
69
+ * @param fullHistory The complete history (for current task detection)
70
+ * @param existingSummaryIndex Index of the existing summary in fullHistory
71
+ * @returns Array with single summary message, or empty if nothing to summarize
72
+ */
73
+ private compactSubset;
74
+ /**
75
+ * Find the most recent user message that represents the current task.
76
+ * This helps preserve context about what the user is currently asking for.
77
+ */
78
+ private findCurrentTaskMessage;
63
79
  /**
64
80
  * Split history into messages to summarize and messages to keep.
65
81
  * Keeps the last N turns (user + assistant pairs) intact.
82
+ *
83
+ * For long agentic conversations with many tool calls, this also ensures
84
+ * we don't try to keep too many messages even within preserved turns.
66
85
  */
67
86
  private splitHistory;
68
87
  /**
69
88
  * Generate an LLM summary of the messages.
89
+ *
90
+ * @param messages Messages to summarize
91
+ * @param currentTask The most recent user message (current task context)
70
92
  */
71
93
  private generateSummary;
72
94
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"reactive-overflow.d.ts","sourceRoot":"","sources":["../../../../src/context/compaction/strategies/reactive-overflow.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,aAAa,EAAE,MAAM,IAAI,CAAC;AACtD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AACvD,OAAO,KAAK,EAAE,eAAe,EAAY,MAAM,gBAAgB,CAAC;AAEhE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAEhE;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACpC;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAE5B;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAE1B;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;CAC1B;AAiBD;;;;;;;;;;;;;;;;;;GAkBG;AACH,qBAAa,wBAAyB,YAAW,mBAAmB;IAChE,QAAQ,CAAC,IAAI,uBAAuB;IAEpC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAgB;IACtC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAoC;IAC5D,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAe;gBAE1B,KAAK,EAAE,aAAa,EAAE,OAAO,EAAE,uBAAuB,YAAK,EAAE,MAAM,EAAE,YAAY;IAM7F;;;;;;;;;;OAUG;IACG,OAAO,CAAC,OAAO,EAAE,SAAS,eAAe,EAAE,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC;IA0C9E;;;OAGG;IACH,OAAO,CAAC,YAAY;IA2CpB;;OAEG;YACW,eAAe;IAmB7B;;OAEG;IACH,OAAO,CAAC,wBAAwB;IA2ChC;;OAEG;IACH,OAAO,CAAC,qBAAqB;CAiChC"}
1
+ {"version":3,"file":"reactive-overflow.d.ts","sourceRoot":"","sources":["../../../../src/context/compaction/strategies/reactive-overflow.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,aAAa,EAAE,MAAM,IAAI,CAAC;AACtD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AACvD,OAAO,KAAK,EAAE,eAAe,EAAY,MAAM,gBAAgB,CAAC;AAEhE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAEhE;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACpC;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAE5B;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAE1B;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;CAC1B;AAsCD;;;;;;;;;;;;;;;;;;GAkBG;AACH,qBAAa,wBAAyB,YAAW,mBAAmB;IAChE,QAAQ,CAAC,IAAI,uBAAuB;IAEpC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAgB;IACtC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAoC;IAC5D,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAe;gBAE1B,KAAK,EAAE,aAAa,EAAE,OAAO,EAAE,uBAAuB,YAAK,EAAE,MAAM,EAAE,YAAY;IAM7F;;;;;;;;;;OAUG;IACG,OAAO,CAAC,OAAO,EAAE,SAAS,eAAe,EAAE,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC;IAiF9E;;;;;;;;;OASG;YACW,aAAa;IAkD3B;;;OAGG;IACH,OAAO,CAAC,sBAAsB;IAuB9B;;;;;;OAMG;IACH,OAAO,CAAC,YAAY;IAwDpB;;;;;OAKG;YACW,eAAe;IAmC7B;;OAEG;IACH,OAAO,CAAC,wBAAwB;IA2ChC;;OAEG;IACH,OAAO,CAAC,qBAAqB;CA0DhC"}
@@ -4,15 +4,36 @@ import { isAssistantMessage, isToolMessage } from "../../types.js";
4
4
  const DEFAULT_OPTIONS = {
5
5
  preserveLastNTurns: 2,
6
6
  maxSummaryTokens: 2e3,
7
- summaryPrompt: `You are a conversation summarizer. Summarize the following conversation history concisely, focusing on:
8
- - What tasks were attempted and their outcomes
9
- - Current state and context the assistant needs to remember
10
- - Any important decisions or information discovered
11
- - What the user was trying to accomplish
7
+ summaryPrompt: `You are a conversation summarizer creating a structured summary for session continuation.
12
8
 
13
- Be concise but preserve essential context. Output only the summary, no preamble.
9
+ Analyze the conversation and produce a summary in the following XML format:
14
10
 
15
- Conversation:
11
+ <session_compaction>
12
+ <conversation_history>
13
+ A concise summary of what happened in the conversation:
14
+ - Tasks attempted and their outcomes (success/failure/in-progress)
15
+ - Important decisions made
16
+ - Key information discovered (file paths, configurations, errors encountered)
17
+ - Tools used and their results
18
+ </conversation_history>
19
+
20
+ <current_task>
21
+ The most recent task or instruction the user requested that may still be in progress.
22
+ Be specific - include the exact request and current status.
23
+ </current_task>
24
+
25
+ <important_context>
26
+ Critical state that must be preserved:
27
+ - File paths being worked on
28
+ - Variable values or configurations
29
+ - Error messages that need addressing
30
+ - Any pending actions or next steps
31
+ </important_context>
32
+ </session_compaction>
33
+
34
+ IMPORTANT: The assistant will continue working based on this summary. Ensure the current_task section clearly states what needs to be done next.
35
+
36
+ Conversation to summarize:
16
37
  {conversation}`
17
38
  };
18
39
  class ReactiveOverflowStrategy {
@@ -41,15 +62,73 @@ class ReactiveOverflowStrategy {
41
62
  this.logger.debug("ReactiveOverflowStrategy: History too short, skipping compaction");
42
63
  return [];
43
64
  }
65
+ let existingSummaryIndex = -1;
66
+ for (let i = history.length - 1; i >= 0; i--) {
67
+ const msg = history[i];
68
+ if (msg?.metadata?.isSummary === true || msg?.metadata?.isSessionSummary === true) {
69
+ existingSummaryIndex = i;
70
+ break;
71
+ }
72
+ }
73
+ if (existingSummaryIndex !== -1) {
74
+ const messagesAfterSummary = history.slice(existingSummaryIndex + 1);
75
+ if (messagesAfterSummary.length <= 4) {
76
+ this.logger.debug(
77
+ `ReactiveOverflowStrategy: Only ${messagesAfterSummary.length} messages after existing summary, skipping re-compaction`
78
+ );
79
+ return [];
80
+ }
81
+ this.logger.info(
82
+ `ReactiveOverflowStrategy: Found existing summary at index ${existingSummaryIndex}, working with ${messagesAfterSummary.length} messages after it`
83
+ );
84
+ return this.compactSubset(messagesAfterSummary, history, existingSummaryIndex);
85
+ }
44
86
  const { toSummarize, toKeep } = this.splitHistory(history);
45
87
  if (toSummarize.length === 0) {
46
88
  this.logger.debug("ReactiveOverflowStrategy: No messages to summarize");
47
89
  return [];
48
90
  }
91
+ const currentTaskMessage = this.findCurrentTaskMessage(history);
49
92
  this.logger.info(
50
93
  `ReactiveOverflowStrategy: Summarizing ${toSummarize.length} messages, keeping ${toKeep.length}`
51
94
  );
52
- const summary = await this.generateSummary(toSummarize);
95
+ const summary = await this.generateSummary(toSummarize, currentTaskMessage);
96
+ const summaryMessage = {
97
+ role: "assistant",
98
+ content: [{ type: "text", text: summary }],
99
+ timestamp: Date.now(),
100
+ metadata: {
101
+ isSummary: true,
102
+ summarizedAt: Date.now(),
103
+ originalMessageCount: toSummarize.length,
104
+ originalFirstTimestamp: toSummarize[0]?.timestamp,
105
+ originalLastTimestamp: toSummarize[toSummarize.length - 1]?.timestamp
106
+ }
107
+ };
108
+ return [summaryMessage];
109
+ }
110
+ /**
111
+ * Handle re-compaction when there's already a summary in history.
112
+ * Only summarizes messages AFTER the existing summary, preventing
113
+ * cascading summaries of summaries.
114
+ *
115
+ * @param messagesAfterSummary Messages after the existing summary
116
+ * @param fullHistory The complete history (for current task detection)
117
+ * @param existingSummaryIndex Index of the existing summary in fullHistory
118
+ * @returns Array with single summary message, or empty if nothing to summarize
119
+ */
120
+ async compactSubset(messagesAfterSummary, fullHistory, existingSummaryIndex) {
121
+ const { toSummarize, toKeep } = this.splitHistory(messagesAfterSummary);
122
+ if (toSummarize.length === 0) {
123
+ this.logger.debug("ReactiveOverflowStrategy: No messages to summarize in subset");
124
+ return [];
125
+ }
126
+ const currentTaskMessage = this.findCurrentTaskMessage(fullHistory);
127
+ this.logger.info(
128
+ `ReactiveOverflowStrategy (re-compact): Summarizing ${toSummarize.length} messages after existing summary, keeping ${toKeep.length}`
129
+ );
130
+ const summary = await this.generateSummary(toSummarize, currentTaskMessage);
131
+ const absoluteOriginalMessageCount = existingSummaryIndex + 1 + toSummarize.length;
53
132
  const summaryMessage = {
54
133
  role: "assistant",
55
134
  content: [{ type: "text", text: summary }],
@@ -57,16 +136,43 @@ class ReactiveOverflowStrategy {
57
136
  metadata: {
58
137
  isSummary: true,
59
138
  summarizedAt: Date.now(),
60
- summarizedMessageCount: toSummarize.length,
139
+ originalMessageCount: absoluteOriginalMessageCount,
140
+ isRecompaction: true,
141
+ // Mark that this is a re-compaction
61
142
  originalFirstTimestamp: toSummarize[0]?.timestamp,
62
143
  originalLastTimestamp: toSummarize[toSummarize.length - 1]?.timestamp
63
144
  }
64
145
  };
65
146
  return [summaryMessage];
66
147
  }
148
+ /**
149
+ * Find the most recent user message that represents the current task.
150
+ * This helps preserve context about what the user is currently asking for.
151
+ */
152
+ findCurrentTaskMessage(history) {
153
+ for (let i = history.length - 1; i >= 0; i--) {
154
+ const msg = history[i];
155
+ if (msg?.role === "user") {
156
+ if (typeof msg.content === "string") {
157
+ return msg.content;
158
+ } else if (Array.isArray(msg.content)) {
159
+ const textParts = msg.content.filter(
160
+ (part) => part.type === "text"
161
+ ).map((part) => part.text).join("\n");
162
+ if (textParts.length > 0) {
163
+ return textParts;
164
+ }
165
+ }
166
+ }
167
+ }
168
+ return null;
169
+ }
67
170
  /**
68
171
  * Split history into messages to summarize and messages to keep.
69
172
  * Keeps the last N turns (user + assistant pairs) intact.
173
+ *
174
+ * For long agentic conversations with many tool calls, this also ensures
175
+ * we don't try to keep too many messages even within preserved turns.
70
176
  */
71
177
  splitHistory(history) {
72
178
  const turnsToKeep = this.options.preserveLastNTurns;
@@ -81,20 +187,25 @@ class ReactiveOverflowStrategy {
81
187
  }
82
188
  if (userMessageIndices.length > 0) {
83
189
  const splitIndex = userMessageIndices[0];
84
- if (splitIndex !== void 0) {
85
- if (splitIndex === 0) {
86
- return {
87
- toSummarize: [],
88
- toKeep: history
89
- };
90
- }
190
+ if (splitIndex !== void 0 && splitIndex > 0) {
91
191
  return {
92
192
  toSummarize: history.slice(0, splitIndex),
93
193
  toKeep: history.slice(splitIndex)
94
194
  };
95
195
  }
96
196
  }
97
- const keepCount = Math.min(4, history.length);
197
+ const minKeep = 3;
198
+ const maxKeepPercent = 0.2;
199
+ const keepCount = Math.max(minKeep, Math.floor(history.length * maxKeepPercent));
200
+ if (keepCount >= history.length) {
201
+ return {
202
+ toSummarize: [],
203
+ toKeep: history
204
+ };
205
+ }
206
+ this.logger.debug(
207
+ `splitHistory: Using fallback - keeping last ${keepCount} of ${history.length} messages`
208
+ );
98
209
  return {
99
210
  toSummarize: history.slice(0, -keepCount),
100
211
  toKeep: history.slice(-keepCount)
@@ -102,21 +213,36 @@ class ReactiveOverflowStrategy {
102
213
  }
103
214
  /**
104
215
  * Generate an LLM summary of the messages.
216
+ *
217
+ * @param messages Messages to summarize
218
+ * @param currentTask The most recent user message (current task context)
105
219
  */
106
- async generateSummary(messages) {
220
+ async generateSummary(messages, currentTask) {
107
221
  const formattedConversation = this.formatMessagesForSummary(messages);
108
- const prompt = this.options.summaryPrompt.replace("{conversation}", formattedConversation);
222
+ let conversationWithContext = formattedConversation;
223
+ if (currentTask) {
224
+ conversationWithContext += `
225
+
226
+ --- CURRENT TASK (most recent user request) ---
227
+ ${currentTask}`;
228
+ }
229
+ const prompt = this.options.summaryPrompt.replace(
230
+ "{conversation}",
231
+ conversationWithContext
232
+ );
109
233
  try {
110
234
  const result = await generateText({
111
235
  model: this.model,
112
236
  prompt,
113
237
  maxOutputTokens: this.options.maxSummaryTokens
114
238
  });
115
- return `[Previous conversation summary]
239
+ return `[Session Compaction Summary]
116
240
  ${result.text}`;
117
241
  } catch (error) {
118
- this.logger.error("ReactiveOverflowStrategy: Failed to generate summary", { error });
119
- return this.createFallbackSummary(messages);
242
+ this.logger.error(
243
+ `ReactiveOverflowStrategy: Failed to generate summary - ${error instanceof Error ? error.message : String(error)}`
244
+ );
245
+ return this.createFallbackSummary(messages, currentTask);
120
246
  }
121
247
  }
122
248
  /**
@@ -152,7 +278,7 @@ ${result.text}`;
152
278
  /**
153
279
  * Create a fallback summary if LLM call fails.
154
280
  */
155
- createFallbackSummary(messages) {
281
+ createFallbackSummary(messages, currentTask) {
156
282
  const userMessages = messages.filter((m) => m.role === "user");
157
283
  const assistantWithTools = messages.filter(
158
284
  (m) => isAssistantMessage(m) && !!m.toolCalls && m.toolCalls.length > 0
@@ -168,9 +294,25 @@ ${result.text}`;
168
294
  assistantWithTools.flatMap((m) => m.toolCalls.map((tc) => tc.function.name))
169
295
  )
170
296
  ].join(", ");
171
- return `[Previous conversation summary - fallback]
172
- User discussed: ${userTopics || "various topics"}
173
- Tools used: ${toolsUsed || "none"}`;
297
+ let fallback = `[Session Compaction Summary - Fallback]
298
+ <session_compaction>
299
+ <conversation_history>
300
+ User discussed: ${userTopics || "various topics"}
301
+ Tools used: ${toolsUsed || "none"}
302
+ Messages summarized: ${messages.length}
303
+ </conversation_history>`;
304
+ if (currentTask) {
305
+ fallback += `
306
+ <current_task>
307
+ ${currentTask.slice(0, 500)}${currentTask.length > 500 ? "..." : ""}
308
+ </current_task>`;
309
+ }
310
+ fallback += `
311
+ <important_context>
312
+ Note: This is a fallback summary due to LLM error. Context may be incomplete.
313
+ </important_context>
314
+ </session_compaction>`;
315
+ return fallback;
174
316
  }
175
317
  }
176
318
  export {
@@ -12,8 +12,20 @@ export interface ICompactionStrategy {
12
12
  /**
13
13
  * Compacts the provided message history.
14
14
  *
15
+ * The returned summary messages MUST include specific metadata fields for
16
+ * `filterCompacted()` to correctly exclude pre-summary messages at read-time:
17
+ *
18
+ * Required metadata:
19
+ * - `isSummary: true` - Marks the message as a compaction summary
20
+ * - `originalMessageCount: number` - Count of messages that were summarized
21
+ * (used by filterCompacted to determine which messages to exclude)
22
+ *
23
+ * Optional metadata:
24
+ * - `isRecompaction: true` - Set when re-compacting after a previous summary
25
+ * - `isSessionSummary: true` - Alternative to isSummary for session-level summaries
26
+ *
15
27
  * @param history The current conversation history.
16
- * @returns Summary messages to add to history (filterCompacted handles the rest).
28
+ * @returns Summary messages to add to history. Empty array if nothing to compact.
17
29
  */
18
30
  compact(history: readonly InternalMessage[]): Promise<InternalMessage[]> | InternalMessage[];
19
31
  }
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/context/compaction/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAE9C;;;;;;GAMG;AACH,MAAM,WAAW,mBAAmB;IAChC,yCAAyC;IACzC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAEtB;;;;;OAKG;IACH,OAAO,CAAC,OAAO,EAAE,SAAS,eAAe,EAAE,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC,GAAG,eAAe,EAAE,CAAC;CAChG"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/context/compaction/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAE9C;;;;;;GAMG;AACH,MAAM,WAAW,mBAAmB;IAChC,yCAAyC;IACzC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAEtB;;;;;;;;;;;;;;;;;OAiBG;IACH,OAAO,CAAC,OAAO,EAAE,SAAS,eAAe,EAAE,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC,GAAG,eAAe,EAAE,CAAC;CAChG"}