@animus-labs/cortex 0.2.0

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 (293) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +73 -0
  3. package/dist/budget-guard.d.ts +75 -0
  4. package/dist/budget-guard.d.ts.map +1 -0
  5. package/dist/budget-guard.js +142 -0
  6. package/dist/budget-guard.js.map +1 -0
  7. package/dist/compaction/compaction.d.ts +99 -0
  8. package/dist/compaction/compaction.d.ts.map +1 -0
  9. package/dist/compaction/compaction.js +302 -0
  10. package/dist/compaction/compaction.js.map +1 -0
  11. package/dist/compaction/failsafe.d.ts +57 -0
  12. package/dist/compaction/failsafe.d.ts.map +1 -0
  13. package/dist/compaction/failsafe.js +135 -0
  14. package/dist/compaction/failsafe.js.map +1 -0
  15. package/dist/compaction/index.d.ts +381 -0
  16. package/dist/compaction/index.d.ts.map +1 -0
  17. package/dist/compaction/index.js +979 -0
  18. package/dist/compaction/index.js.map +1 -0
  19. package/dist/compaction/microcompaction.d.ts +219 -0
  20. package/dist/compaction/microcompaction.d.ts.map +1 -0
  21. package/dist/compaction/microcompaction.js +536 -0
  22. package/dist/compaction/microcompaction.js.map +1 -0
  23. package/dist/compaction/observational/buffering.d.ts +225 -0
  24. package/dist/compaction/observational/buffering.d.ts.map +1 -0
  25. package/dist/compaction/observational/buffering.js +354 -0
  26. package/dist/compaction/observational/buffering.js.map +1 -0
  27. package/dist/compaction/observational/constants.d.ts +70 -0
  28. package/dist/compaction/observational/constants.d.ts.map +1 -0
  29. package/dist/compaction/observational/constants.js +507 -0
  30. package/dist/compaction/observational/constants.js.map +1 -0
  31. package/dist/compaction/observational/index.d.ts +219 -0
  32. package/dist/compaction/observational/index.d.ts.map +1 -0
  33. package/dist/compaction/observational/index.js +641 -0
  34. package/dist/compaction/observational/index.js.map +1 -0
  35. package/dist/compaction/observational/observer.d.ts +97 -0
  36. package/dist/compaction/observational/observer.d.ts.map +1 -0
  37. package/dist/compaction/observational/observer.js +424 -0
  38. package/dist/compaction/observational/observer.js.map +1 -0
  39. package/dist/compaction/observational/recall-tool.d.ts +27 -0
  40. package/dist/compaction/observational/recall-tool.d.ts.map +1 -0
  41. package/dist/compaction/observational/recall-tool.js +93 -0
  42. package/dist/compaction/observational/recall-tool.js.map +1 -0
  43. package/dist/compaction/observational/reflector.d.ts +94 -0
  44. package/dist/compaction/observational/reflector.d.ts.map +1 -0
  45. package/dist/compaction/observational/reflector.js +167 -0
  46. package/dist/compaction/observational/reflector.js.map +1 -0
  47. package/dist/compaction/observational/types.d.ts +271 -0
  48. package/dist/compaction/observational/types.d.ts.map +1 -0
  49. package/dist/compaction/observational/types.js +15 -0
  50. package/dist/compaction/observational/types.js.map +1 -0
  51. package/dist/context-manager.d.ts +134 -0
  52. package/dist/context-manager.d.ts.map +1 -0
  53. package/dist/context-manager.js +170 -0
  54. package/dist/context-manager.js.map +1 -0
  55. package/dist/cortex-agent.d.ts +1020 -0
  56. package/dist/cortex-agent.d.ts.map +1 -0
  57. package/dist/cortex-agent.js +3589 -0
  58. package/dist/cortex-agent.js.map +1 -0
  59. package/dist/error-classifier.d.ts +48 -0
  60. package/dist/error-classifier.d.ts.map +1 -0
  61. package/dist/error-classifier.js +152 -0
  62. package/dist/error-classifier.js.map +1 -0
  63. package/dist/event-bridge.d.ts +166 -0
  64. package/dist/event-bridge.d.ts.map +1 -0
  65. package/dist/event-bridge.js +381 -0
  66. package/dist/event-bridge.js.map +1 -0
  67. package/dist/index.d.ts +55 -0
  68. package/dist/index.d.ts.map +1 -0
  69. package/dist/index.js +57 -0
  70. package/dist/index.js.map +1 -0
  71. package/dist/mcp-client.d.ts +119 -0
  72. package/dist/mcp-client.d.ts.map +1 -0
  73. package/dist/mcp-client.js +474 -0
  74. package/dist/mcp-client.js.map +1 -0
  75. package/dist/model-wrapper.d.ts +58 -0
  76. package/dist/model-wrapper.d.ts.map +1 -0
  77. package/dist/model-wrapper.js +86 -0
  78. package/dist/model-wrapper.js.map +1 -0
  79. package/dist/noop-logger.d.ts +4 -0
  80. package/dist/noop-logger.d.ts.map +1 -0
  81. package/dist/noop-logger.js +8 -0
  82. package/dist/noop-logger.js.map +1 -0
  83. package/dist/prompt-diagnostics.d.ts +47 -0
  84. package/dist/prompt-diagnostics.d.ts.map +1 -0
  85. package/dist/prompt-diagnostics.js +230 -0
  86. package/dist/prompt-diagnostics.js.map +1 -0
  87. package/dist/provider-manager.d.ts +224 -0
  88. package/dist/provider-manager.d.ts.map +1 -0
  89. package/dist/provider-manager.js +563 -0
  90. package/dist/provider-manager.js.map +1 -0
  91. package/dist/provider-registry.d.ts +115 -0
  92. package/dist/provider-registry.d.ts.map +1 -0
  93. package/dist/provider-registry.js +305 -0
  94. package/dist/provider-registry.js.map +1 -0
  95. package/dist/schema-converter.d.ts +20 -0
  96. package/dist/schema-converter.d.ts.map +1 -0
  97. package/dist/schema-converter.js +48 -0
  98. package/dist/schema-converter.js.map +1 -0
  99. package/dist/skill-preprocessor.d.ts +46 -0
  100. package/dist/skill-preprocessor.d.ts.map +1 -0
  101. package/dist/skill-preprocessor.js +237 -0
  102. package/dist/skill-preprocessor.js.map +1 -0
  103. package/dist/skill-registry.d.ts +107 -0
  104. package/dist/skill-registry.d.ts.map +1 -0
  105. package/dist/skill-registry.js +330 -0
  106. package/dist/skill-registry.js.map +1 -0
  107. package/dist/skill-tool.d.ts +54 -0
  108. package/dist/skill-tool.d.ts.map +1 -0
  109. package/dist/skill-tool.js +88 -0
  110. package/dist/skill-tool.js.map +1 -0
  111. package/dist/sub-agent-manager.d.ts +90 -0
  112. package/dist/sub-agent-manager.d.ts.map +1 -0
  113. package/dist/sub-agent-manager.js +192 -0
  114. package/dist/sub-agent-manager.js.map +1 -0
  115. package/dist/token-estimator.d.ts +23 -0
  116. package/dist/token-estimator.d.ts.map +1 -0
  117. package/dist/token-estimator.js +27 -0
  118. package/dist/token-estimator.js.map +1 -0
  119. package/dist/tool-contract.d.ts +68 -0
  120. package/dist/tool-contract.d.ts.map +1 -0
  121. package/dist/tool-contract.js +35 -0
  122. package/dist/tool-contract.js.map +1 -0
  123. package/dist/tool-result-persistence.d.ts +89 -0
  124. package/dist/tool-result-persistence.d.ts.map +1 -0
  125. package/dist/tool-result-persistence.js +152 -0
  126. package/dist/tool-result-persistence.js.map +1 -0
  127. package/dist/tools/bash/index.d.ts +71 -0
  128. package/dist/tools/bash/index.d.ts.map +1 -0
  129. package/dist/tools/bash/index.js +485 -0
  130. package/dist/tools/bash/index.js.map +1 -0
  131. package/dist/tools/bash/interactive.d.ts +47 -0
  132. package/dist/tools/bash/interactive.d.ts.map +1 -0
  133. package/dist/tools/bash/interactive.js +262 -0
  134. package/dist/tools/bash/interactive.js.map +1 -0
  135. package/dist/tools/bash/safety.d.ts +149 -0
  136. package/dist/tools/bash/safety.d.ts.map +1 -0
  137. package/dist/tools/bash/safety.js +1116 -0
  138. package/dist/tools/bash/safety.js.map +1 -0
  139. package/dist/tools/edit.d.ts +57 -0
  140. package/dist/tools/edit.d.ts.map +1 -0
  141. package/dist/tools/edit.js +310 -0
  142. package/dist/tools/edit.js.map +1 -0
  143. package/dist/tools/glob.d.ts +34 -0
  144. package/dist/tools/glob.d.ts.map +1 -0
  145. package/dist/tools/glob.js +268 -0
  146. package/dist/tools/glob.js.map +1 -0
  147. package/dist/tools/grep.d.ts +53 -0
  148. package/dist/tools/grep.d.ts.map +1 -0
  149. package/dist/tools/grep.js +673 -0
  150. package/dist/tools/grep.js.map +1 -0
  151. package/dist/tools/index.d.ts +62 -0
  152. package/dist/tools/index.d.ts.map +1 -0
  153. package/dist/tools/index.js +52 -0
  154. package/dist/tools/index.js.map +1 -0
  155. package/dist/tools/read.d.ts +43 -0
  156. package/dist/tools/read.d.ts.map +1 -0
  157. package/dist/tools/read.js +459 -0
  158. package/dist/tools/read.js.map +1 -0
  159. package/dist/tools/runtime.d.ts +62 -0
  160. package/dist/tools/runtime.d.ts.map +1 -0
  161. package/dist/tools/runtime.js +116 -0
  162. package/dist/tools/runtime.js.map +1 -0
  163. package/dist/tools/shared/cwd-tracker.d.ts +32 -0
  164. package/dist/tools/shared/cwd-tracker.d.ts.map +1 -0
  165. package/dist/tools/shared/cwd-tracker.js +44 -0
  166. package/dist/tools/shared/cwd-tracker.js.map +1 -0
  167. package/dist/tools/shared/edit-history.d.ts +55 -0
  168. package/dist/tools/shared/edit-history.d.ts.map +1 -0
  169. package/dist/tools/shared/edit-history.js +72 -0
  170. package/dist/tools/shared/edit-history.js.map +1 -0
  171. package/dist/tools/shared/edit-matcher.d.ts +83 -0
  172. package/dist/tools/shared/edit-matcher.d.ts.map +1 -0
  173. package/dist/tools/shared/edit-matcher.js +359 -0
  174. package/dist/tools/shared/edit-matcher.js.map +1 -0
  175. package/dist/tools/shared/file-mutation-lock.d.ts +22 -0
  176. package/dist/tools/shared/file-mutation-lock.d.ts.map +1 -0
  177. package/dist/tools/shared/file-mutation-lock.js +35 -0
  178. package/dist/tools/shared/file-mutation-lock.js.map +1 -0
  179. package/dist/tools/shared/gitignore.d.ts +17 -0
  180. package/dist/tools/shared/gitignore.d.ts.map +1 -0
  181. package/dist/tools/shared/gitignore.js +59 -0
  182. package/dist/tools/shared/gitignore.js.map +1 -0
  183. package/dist/tools/shared/pdf-extractor.d.ts +96 -0
  184. package/dist/tools/shared/pdf-extractor.d.ts.map +1 -0
  185. package/dist/tools/shared/pdf-extractor.js +196 -0
  186. package/dist/tools/shared/pdf-extractor.js.map +1 -0
  187. package/dist/tools/shared/read-registry.d.ts +66 -0
  188. package/dist/tools/shared/read-registry.d.ts.map +1 -0
  189. package/dist/tools/shared/read-registry.js +65 -0
  190. package/dist/tools/shared/read-registry.js.map +1 -0
  191. package/dist/tools/shared/safe-env.d.ts +18 -0
  192. package/dist/tools/shared/safe-env.d.ts.map +1 -0
  193. package/dist/tools/shared/safe-env.js +70 -0
  194. package/dist/tools/shared/safe-env.js.map +1 -0
  195. package/dist/tools/sub-agent.d.ts +91 -0
  196. package/dist/tools/sub-agent.d.ts.map +1 -0
  197. package/dist/tools/sub-agent.js +89 -0
  198. package/dist/tools/sub-agent.js.map +1 -0
  199. package/dist/tools/task-output.d.ts +38 -0
  200. package/dist/tools/task-output.d.ts.map +1 -0
  201. package/dist/tools/task-output.js +186 -0
  202. package/dist/tools/task-output.js.map +1 -0
  203. package/dist/tools/tool-search/index.d.ts +40 -0
  204. package/dist/tools/tool-search/index.d.ts.map +1 -0
  205. package/dist/tools/tool-search/index.js +110 -0
  206. package/dist/tools/tool-search/index.js.map +1 -0
  207. package/dist/tools/tool-search/registry.d.ts +82 -0
  208. package/dist/tools/tool-search/registry.d.ts.map +1 -0
  209. package/dist/tools/tool-search/registry.js +238 -0
  210. package/dist/tools/tool-search/registry.js.map +1 -0
  211. package/dist/tools/undo-edit.d.ts +51 -0
  212. package/dist/tools/undo-edit.d.ts.map +1 -0
  213. package/dist/tools/undo-edit.js +231 -0
  214. package/dist/tools/undo-edit.js.map +1 -0
  215. package/dist/tools/web-fetch/cache.d.ts +49 -0
  216. package/dist/tools/web-fetch/cache.d.ts.map +1 -0
  217. package/dist/tools/web-fetch/cache.js +89 -0
  218. package/dist/tools/web-fetch/cache.js.map +1 -0
  219. package/dist/tools/web-fetch/index.d.ts +53 -0
  220. package/dist/tools/web-fetch/index.d.ts.map +1 -0
  221. package/dist/tools/web-fetch/index.js +513 -0
  222. package/dist/tools/web-fetch/index.js.map +1 -0
  223. package/dist/tools/write.d.ts +59 -0
  224. package/dist/tools/write.d.ts.map +1 -0
  225. package/dist/tools/write.js +316 -0
  226. package/dist/tools/write.js.map +1 -0
  227. package/dist/types.d.ts +881 -0
  228. package/dist/types.d.ts.map +1 -0
  229. package/dist/types.js +16 -0
  230. package/dist/types.js.map +1 -0
  231. package/dist/working-tags.d.ts +44 -0
  232. package/dist/working-tags.d.ts.map +1 -0
  233. package/dist/working-tags.js +103 -0
  234. package/dist/working-tags.js.map +1 -0
  235. package/package.json +87 -0
  236. package/src/budget-guard.ts +170 -0
  237. package/src/compaction/compaction.ts +386 -0
  238. package/src/compaction/failsafe.ts +185 -0
  239. package/src/compaction/index.ts +1199 -0
  240. package/src/compaction/microcompaction.ts +709 -0
  241. package/src/compaction/observational/buffering.ts +430 -0
  242. package/src/compaction/observational/constants.ts +532 -0
  243. package/src/compaction/observational/index.ts +837 -0
  244. package/src/compaction/observational/observer.ts +510 -0
  245. package/src/compaction/observational/recall-tool.ts +130 -0
  246. package/src/compaction/observational/reflector.ts +221 -0
  247. package/src/compaction/observational/types.ts +343 -0
  248. package/src/context-manager.ts +237 -0
  249. package/src/cortex-agent.ts +4297 -0
  250. package/src/error-classifier.ts +199 -0
  251. package/src/event-bridge.ts +508 -0
  252. package/src/index.ts +292 -0
  253. package/src/mcp-client.ts +582 -0
  254. package/src/model-wrapper.ts +128 -0
  255. package/src/noop-logger.ts +9 -0
  256. package/src/prompt-diagnostics.ts +296 -0
  257. package/src/provider-manager.ts +823 -0
  258. package/src/provider-registry.ts +386 -0
  259. package/src/schema-converter.ts +51 -0
  260. package/src/skill-preprocessor.ts +314 -0
  261. package/src/skill-registry.ts +378 -0
  262. package/src/skill-tool.ts +130 -0
  263. package/src/sub-agent-manager.ts +236 -0
  264. package/src/token-estimator.ts +26 -0
  265. package/src/tool-contract.ts +113 -0
  266. package/src/tool-result-persistence.ts +197 -0
  267. package/src/tools/bash/index.ts +633 -0
  268. package/src/tools/bash/interactive.ts +302 -0
  269. package/src/tools/bash/safety.ts +1297 -0
  270. package/src/tools/edit.ts +422 -0
  271. package/src/tools/glob.ts +330 -0
  272. package/src/tools/grep.ts +819 -0
  273. package/src/tools/index.ts +110 -0
  274. package/src/tools/read.ts +580 -0
  275. package/src/tools/runtime.ts +173 -0
  276. package/src/tools/shared/cwd-tracker.ts +50 -0
  277. package/src/tools/shared/edit-history.ts +96 -0
  278. package/src/tools/shared/edit-matcher.ts +457 -0
  279. package/src/tools/shared/file-mutation-lock.ts +40 -0
  280. package/src/tools/shared/gitignore.ts +61 -0
  281. package/src/tools/shared/pdf-extractor.ts +290 -0
  282. package/src/tools/shared/read-registry.ts +93 -0
  283. package/src/tools/shared/safe-env.ts +82 -0
  284. package/src/tools/sub-agent.ts +171 -0
  285. package/src/tools/task-output.ts +236 -0
  286. package/src/tools/tool-search/index.ts +167 -0
  287. package/src/tools/tool-search/registry.ts +278 -0
  288. package/src/tools/undo-edit.ts +314 -0
  289. package/src/tools/web-fetch/cache.ts +112 -0
  290. package/src/tools/web-fetch/index.ts +604 -0
  291. package/src/tools/write.ts +385 -0
  292. package/src/types.ts +1057 -0
  293. package/src/working-tags.ts +118 -0
@@ -0,0 +1,302 @@
1
+ /**
2
+ * Layer 2: Conversation Summarization.
3
+ *
4
+ * Replaces older conversation history with an LLM-generated summary
5
+ * while preserving a tail of recent turns. Uses the primary model
6
+ * for summarization quality (the conversation history is structurally
7
+ * complex with interleaved tool calls and multi-turn reasoning).
8
+ *
9
+ * Fires at 70% of context window (configurable). Emits lifecycle
10
+ * events (onBeforeCompaction, onPostCompaction) for consumer
11
+ * coordination (e.g., observational memory flush).
12
+ *
13
+ * References:
14
+ * - compaction-strategy.md (Layer 2: Conversation Summarization)
15
+ * - phase-5-compaction.md (5.3)
16
+ */
17
+ import { estimateTokens } from '../token-estimator.js';
18
+ import { extractTextContent, isToolUseMessage, isToolResultMessage } from './microcompaction.js';
19
+ // ---------------------------------------------------------------------------
20
+ // Defaults
21
+ // ---------------------------------------------------------------------------
22
+ export const COMPACTION_DEFAULTS = {
23
+ threshold: 0.70,
24
+ preserveRecentTurns: 6,
25
+ };
26
+ // ---------------------------------------------------------------------------
27
+ // Summarization prompt
28
+ // ---------------------------------------------------------------------------
29
+ const DEFAULT_SUMMARIZATION_PROMPT = `Your task is to create a detailed summary of the conversation so far. This summary will replace the conversation history, so it must capture everything needed to continue work without losing context. A small tail of the most recent turns is preserved separately and does not need to be repeated.
30
+
31
+ Before writing your summary, analyze the conversation inside <analysis> tags. Walk through the conversation chronologically and note:
32
+ - Each user request and how it was addressed
33
+ - Key decisions and their rationale
34
+ - Tool calls made, what they returned, and any errors
35
+ - User feedback or corrections (especially when you were told to do something differently)
36
+ - What was being worked on most recently
37
+
38
+ The <analysis> block is a private scratchpad. Keep it concise (a line or two per point). Save all detail for the <summary> block.
39
+
40
+ Then write your summary inside <summary> tags with the following sections:
41
+
42
+ 1. Primary Request and Intent
43
+ Capture all user requests and intents in detail. Preserve the user's exact words for directives, preferences, and constraints.
44
+
45
+ 2. Key Technical Concepts
46
+ List all important technical concepts, technologies, and frameworks discussed.
47
+
48
+ 3. Files and Code Sections
49
+ Enumerate specific files and code sections examined, modified, or created. Include file paths and relevant code snippets. For each file, summarize why it was read or edited and what changed.
50
+
51
+ 4. Tool Call Outcomes
52
+ What tools were called, what they found, and what failed. Include specific file paths, function names, URLs, error messages, and return values. Pay special attention to tool results that informed later decisions.
53
+
54
+ 5. Errors and Fixes
55
+ List all errors encountered and how they were resolved. Include specific user feedback received, especially corrections or redirections.
56
+
57
+ 6. All User Messages
58
+ List ALL user messages that are not tool results. These are critical for understanding the user's feedback and changing intent. Preserve the user's exact words.
59
+
60
+ 7. Problem Solving
61
+ Document problems solved and any ongoing troubleshooting efforts.
62
+
63
+ 8. Pending Tasks
64
+ Outline any pending tasks that have been explicitly requested but not yet completed.
65
+
66
+ 9. Current Work
67
+ Describe precisely what was being worked on immediately before this summary. Include file names, code snippets, and the specific state of the work. This section is the most important for seamless continuation.
68
+
69
+ 10. Key Decisions (Cumulative)
70
+ If a previous compaction summary exists in the conversation, carry forward its Key Decisions section and append any new decisions from this cycle. This section grows across compactions to prevent progressive loss of important decisions.
71
+
72
+ 11. Optional Next Step
73
+ List the next step related to the most recent work, but ONLY if it is directly in line with the user's most recent explicit request. If the last task was concluded, do not suggest tangential work. Include direct quotes from the conversation showing exactly what task was in progress.
74
+
75
+ When preserving details, extract and retain exact values rather than paraphrasing:
76
+ - File paths, directory names, and line numbers
77
+ - URLs, API endpoints, and query parameters
78
+ - Function names, class names, variable names
79
+ - IDs, hashes, version numbers, and configuration values
80
+ - Error messages and status codes
81
+ - Specific quantities, dates, and thresholds
82
+
83
+ Be thorough. Err on the side of including information that would prevent duplicate work or repeated mistakes.`;
84
+ // ---------------------------------------------------------------------------
85
+ // Summary extraction
86
+ // ---------------------------------------------------------------------------
87
+ /**
88
+ * Extract the <summary> content from the LLM's compaction output.
89
+ * The prompt asks for <analysis> (scratchpad) then <summary> (the actual summary).
90
+ * We strip the analysis and keep only the summary content.
91
+ * If no <summary> tags are found, return the full output (the model may
92
+ * have skipped the tags but still produced useful content).
93
+ */
94
+ export function extractSummaryContent(raw) {
95
+ const match = raw.match(/<summary>([\s\S]*?)<\/summary>/);
96
+ if (match?.[1]) {
97
+ return match[1].trim();
98
+ }
99
+ // Fallback: strip <analysis> block if present, return the rest
100
+ const stripped = raw.replace(/<analysis>[\s\S]*?<\/analysis>/g, '').trim();
101
+ return stripped || raw.trim();
102
+ }
103
+ // ---------------------------------------------------------------------------
104
+ // Summarization
105
+ // ---------------------------------------------------------------------------
106
+ /**
107
+ * Partition conversation history into compaction target and preserved tail.
108
+ *
109
+ * @param history - The full conversation history (post-slot region)
110
+ * @param preserveRecentTurns - Number of recent turns to preserve
111
+ * @returns [target, preserved] where target is summarized and preserved is kept verbatim
112
+ */
113
+ export function partitionHistory(history, preserveRecentTurns) {
114
+ if (history.length <= preserveRecentTurns) {
115
+ return [[], history];
116
+ }
117
+ let splitPoint = history.length - preserveRecentTurns;
118
+ // Never split between a tool_use (assistant) and its tool_result (user).
119
+ // If the split lands on a tool_result whose preceding message is a tool_use,
120
+ // move the split back one so the entire pair goes into the preserved tail.
121
+ if (splitPoint > 0 &&
122
+ splitPoint < history.length &&
123
+ isToolResultMessage(history[splitPoint]) &&
124
+ isToolUseMessage(history[splitPoint - 1])) {
125
+ splitPoint -= 1;
126
+ }
127
+ // Guard: don't create an empty target from the adjustment
128
+ if (splitPoint <= 0) {
129
+ return [[], history];
130
+ }
131
+ return [history.slice(0, splitPoint), history.slice(splitPoint)];
132
+ }
133
+ /**
134
+ * Build the compaction summary message wrapping it in XML tags.
135
+ *
136
+ * @param summary - The LLM-generated summary text
137
+ * @param turnsCompacted - Number of turns that were summarized
138
+ * @returns A user-role message containing the tagged summary
139
+ */
140
+ export function buildSummaryMessage(summary, turnsCompacted) {
141
+ const timestamp = new Date().toISOString();
142
+ const content = `<compaction-summary generated="${timestamp}" turns-summarized="${turnsCompacted}">\n${summary}\n</compaction-summary>`;
143
+ return { role: 'user', content, timestamp: Date.now() };
144
+ }
145
+ /**
146
+ * Format conversation turns for the summarization prompt.
147
+ * Extracts text content and labels each turn with role.
148
+ */
149
+ export function formatTurnsForSummarization(turns) {
150
+ // No per-turn truncation. The compaction target is already bounded by
151
+ // partitionHistory (everything minus the preserved tail), and the
152
+ // summarizer needs access to full turn content for high-quality
153
+ // compression. See compaction-strategy.md Layer 2.
154
+ return turns
155
+ .map((msg, i) => {
156
+ const text = extractTextContent(msg);
157
+ return `[Turn ${i + 1}] ${msg.role}:\n${text}`;
158
+ })
159
+ .join('\n\n---\n\n');
160
+ }
161
+ /**
162
+ * Run Layer 2 conversation summarization.
163
+ *
164
+ * Steps:
165
+ * 1. Partition history into target and preserved tail
166
+ * 2. Emit onBeforeCompaction (awaited)
167
+ * 3. Generate summary via LLM
168
+ * 4. Build new history: [summary message] + [preserved tail]
169
+ * 5. Emit onPostCompaction
170
+ *
171
+ * @param history - Current conversation history (post-slot region)
172
+ * @param config - Compaction configuration
173
+ * @param complete - LLM completion function
174
+ * @param handlers - Consumer lifecycle handlers
175
+ * @returns The new conversation history and compaction result
176
+ */
177
+ export async function runCompaction(history, config, complete, handlers = {},
178
+ /** Actual full-context token count (includes system prompt, slots, tools). When provided, used as tokensBefore instead of text-only heuristic. */
179
+ actualContextTokens) {
180
+ const [target, preserved] = partitionHistory(history, config.preserveRecentTurns);
181
+ if (target.length === 0) {
182
+ // Nothing to compact; not enough history
183
+ throw new Error('Not enough conversation history to compact');
184
+ }
185
+ // Compute text-only heuristic for history content.
186
+ const historyTextTokens = estimateTokens(history.map(m => extractTextContent(m)).join('\n'));
187
+ // Use actual full-context token count when provided (includes system prompt,
188
+ // slots, tool definitions); fall back to text-only heuristic for backward compat.
189
+ const tokensBefore = actualContextTokens ?? historyTextTokens;
190
+ // Overhead = system prompt + slots + tool definitions (everything except history text).
191
+ // Used to compute tokensAfter on the same basis as tokensBefore.
192
+ const overhead = actualContextTokens ? Math.max(0, actualContextTokens - historyTextTokens) : 0;
193
+ // Build compaction target info for the event
194
+ const targetInfo = {
195
+ turnsToCompact: target.length,
196
+ estimatedTokens: estimateTokens(target.map(m => extractTextContent(m)).join('\n')),
197
+ };
198
+ // Emit onBeforeCompaction (awaited)
199
+ if (handlers.onBeforeCompaction) {
200
+ for (const handler of handlers.onBeforeCompaction) {
201
+ await handler(targetInfo);
202
+ }
203
+ }
204
+ // Generate summary via LLM
205
+ const prompt = config.customPrompt ?? DEFAULT_SUMMARIZATION_PROMPT;
206
+ const turnsText = formatTurnsForSummarization(target);
207
+ let summary;
208
+ try {
209
+ summary = await complete({
210
+ systemPrompt: prompt,
211
+ messages: [
212
+ {
213
+ role: 'user',
214
+ content: `Here are the conversation turns to summarize:\n\n${turnsText}`,
215
+ },
216
+ ],
217
+ });
218
+ }
219
+ catch (err) {
220
+ const error = err instanceof Error ? err : new Error(String(err));
221
+ // Emit compaction error
222
+ if (handlers.onCompactionError) {
223
+ for (const handler of handlers.onCompactionError) {
224
+ try {
225
+ handler(error);
226
+ }
227
+ catch {
228
+ // Swallow handler errors
229
+ }
230
+ }
231
+ }
232
+ throw error;
233
+ }
234
+ // Extract summary content from <summary> tags, stripping <analysis>
235
+ const parsedSummary = extractSummaryContent(summary);
236
+ // Build new history
237
+ const summaryMessage = buildSummaryMessage(parsedSummary, target.length);
238
+ const newHistory = [summaryMessage, ...preserved];
239
+ // Calculate result metrics. Include the same overhead (system prompt, slots,
240
+ // tool definitions) so tokensBefore and tokensAfter are on the same basis.
241
+ const newHistoryTextTokens = estimateTokens(newHistory.map(m => extractTextContent(m)).join('\n'));
242
+ const tokensAfter = overhead + newHistoryTextTokens;
243
+ const summaryTokens = estimateTokens(parsedSummary);
244
+ // The oldest preserved turn's index in the original history.
245
+ // target.length is the split point: all turns before it were compacted.
246
+ const oldestPreservedIndex = target.length;
247
+ // Attempt to find a timestamp in the preserved messages; null if not found.
248
+ const oldestPreservedTimestamp = findOldestTimestamp(preserved);
249
+ const result = {
250
+ tokensBefore,
251
+ tokensAfter,
252
+ turnsCompacted: target.length,
253
+ turnsPreserved: preserved.length,
254
+ summaryTokens,
255
+ oldestPreservedTimestamp,
256
+ oldestPreservedIndex,
257
+ summary: parsedSummary,
258
+ };
259
+ // Emit onPostCompaction
260
+ if (handlers.onPostCompaction) {
261
+ for (const handler of handlers.onPostCompaction) {
262
+ try {
263
+ handler(result);
264
+ }
265
+ catch {
266
+ // Swallow handler errors
267
+ }
268
+ }
269
+ }
270
+ return { newHistory, result };
271
+ }
272
+ /**
273
+ * Attempt to find the oldest timestamp in a set of messages.
274
+ *
275
+ * Scans message content for ISO date patterns. Returns the first match
276
+ * or null if none found. This is a best-effort heuristic; the consumer
277
+ * should prefer `oldestPreservedIndex` from CompactionResult for
278
+ * reliable timestamp resolution via their own database.
279
+ */
280
+ function findOldestTimestamp(messages) {
281
+ const isoPattern = /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/;
282
+ for (const msg of messages) {
283
+ const text = extractTextContent(msg);
284
+ const match = isoPattern.exec(text);
285
+ if (match) {
286
+ return match[0];
287
+ }
288
+ }
289
+ // No ISO timestamp found in preserved messages. Return null rather
290
+ // than Date.now() so the consumer knows no timestamp was found and
291
+ // can fall back to oldestPreservedIndex for database-level resolution.
292
+ return null;
293
+ }
294
+ /**
295
+ * Check if compaction should trigger based on token count and threshold.
296
+ */
297
+ export function shouldCompact(currentTokens, contextWindow, threshold) {
298
+ if (contextWindow <= 0)
299
+ return false;
300
+ return (currentTokens / contextWindow) >= threshold;
301
+ }
302
+ //# sourceMappingURL=compaction.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compaction.js","sourceRoot":"","sources":["../../src/compaction/compaction.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Layer 3: Emergency Truncation (Failsafe).
3
+ *
4
+ * Last-resort truncation when Layer 2 fails or context is still too large.
5
+ * Drops the oldest conversation turns purely mechanically (no LLM call).
6
+ * Preserves structural integrity: tool_use/tool_result pairs are dropped together.
7
+ *
8
+ * Triggers at 90% of context window (configurable), or reactively when
9
+ * the API returns a context overflow error.
10
+ *
11
+ * This layer also serves as a mid-loop safety valve: it fires inside
12
+ * transformContext during the agentic loop when estimated token count
13
+ * exceeds 90%. Mid-loop truncation does NOT emit onBeforeCompaction
14
+ * (no observational memory processing mid-loop).
15
+ *
16
+ * References:
17
+ * - compaction-strategy.md (Layer 3: Emergency Truncation)
18
+ * - phase-5-compaction.md (5.4)
19
+ */
20
+ import type { AgentMessage } from '../context-manager.js';
21
+ import type { FailsafeConfig } from '../types.js';
22
+ export declare const FAILSAFE_DEFAULTS: FailsafeConfig;
23
+ /**
24
+ * Result of an emergency truncation operation.
25
+ */
26
+ export interface FailsafeTruncationResult {
27
+ /** The truncated conversation history. */
28
+ newHistory: AgentMessage[];
29
+ /** Number of turns removed. */
30
+ turnsRemoved: number;
31
+ /** Estimated tokens after truncation. */
32
+ tokensAfter: number;
33
+ }
34
+ /**
35
+ * Perform emergency truncation on conversation history.
36
+ *
37
+ * Drops the oldest turns (preserving structural pairs) until the
38
+ * estimated token count drops below the threshold, or until only
39
+ * the preserved tail remains.
40
+ *
41
+ * @param history - Conversation history (post-slot region)
42
+ * @param contextWindow - Total context window size in tokens
43
+ * @param slotTokens - Estimated tokens used by slots
44
+ * @param threshold - Usage ratio threshold (default 0.90)
45
+ * @returns Truncation result with new history and metrics
46
+ */
47
+ export declare function emergencyTruncate(history: AgentMessage[], contextWindow: number, slotTokens: number, threshold?: number): FailsafeTruncationResult;
48
+ /**
49
+ * Check if emergency truncation should fire based on token count.
50
+ */
51
+ export declare function shouldTruncate(currentTokens: number, contextWindow: number, threshold?: number): boolean;
52
+ /**
53
+ * Check if an error represents a context overflow.
54
+ * Matches common API error patterns from various providers.
55
+ */
56
+ export declare function isContextOverflow(error: Error): boolean;
57
+ //# sourceMappingURL=failsafe.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"failsafe.d.ts","sourceRoot":"","sources":["../../src/compaction/failsafe.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAC1D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAQlD,eAAO,MAAM,iBAAiB,EAAE,cAE/B,CAAC;AAYF;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACvC,0CAA0C;IAC1C,UAAU,EAAE,YAAY,EAAE,CAAC;IAC3B,+BAA+B;IAC/B,YAAY,EAAE,MAAM,CAAC;IACrB,yCAAyC;IACzC,WAAW,EAAE,MAAM,CAAC;CACrB;AA8BD;;;;;;;;;;;;GAYG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,YAAY,EAAE,EACvB,aAAa,EAAE,MAAM,EACrB,UAAU,EAAE,MAAM,EAClB,SAAS,GAAE,MAAoC,GAC9C,wBAAwB,CAqD1B;AAED;;GAEG;AACH,wBAAgB,cAAc,CAC5B,aAAa,EAAE,MAAM,EACrB,aAAa,EAAE,MAAM,EACrB,SAAS,GAAE,MAAoC,GAC9C,OAAO,CAGT;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,KAAK,GAAG,OAAO,CAYvD"}
@@ -0,0 +1,135 @@
1
+ /**
2
+ * Layer 3: Emergency Truncation (Failsafe).
3
+ *
4
+ * Last-resort truncation when Layer 2 fails or context is still too large.
5
+ * Drops the oldest conversation turns purely mechanically (no LLM call).
6
+ * Preserves structural integrity: tool_use/tool_result pairs are dropped together.
7
+ *
8
+ * Triggers at 90% of context window (configurable), or reactively when
9
+ * the API returns a context overflow error.
10
+ *
11
+ * This layer also serves as a mid-loop safety valve: it fires inside
12
+ * transformContext during the agentic loop when estimated token count
13
+ * exceeds 90%. Mid-loop truncation does NOT emit onBeforeCompaction
14
+ * (no observational memory processing mid-loop).
15
+ *
16
+ * References:
17
+ * - compaction-strategy.md (Layer 3: Emergency Truncation)
18
+ * - phase-5-compaction.md (5.4)
19
+ */
20
+ import { estimateTokens } from '../token-estimator.js';
21
+ import { isToolResultMessage, isToolUseMessage, extractTextContent } from './microcompaction.js';
22
+ // ---------------------------------------------------------------------------
23
+ // Defaults
24
+ // ---------------------------------------------------------------------------
25
+ export const FAILSAFE_DEFAULTS = {
26
+ threshold: 0.90,
27
+ };
28
+ /**
29
+ * Minimum number of recent turns to preserve during emergency truncation.
30
+ * Fewer turns preserved than Layer 2 since this is a last resort.
31
+ */
32
+ const FAILSAFE_PRESERVE_TURNS = 3;
33
+ /**
34
+ * Find structural pairs in conversation history.
35
+ * A tool_use message and its corresponding tool_result form a pair.
36
+ * When dropping one, we must drop both.
37
+ *
38
+ * Returns indices that should be dropped together for each index.
39
+ * If a message at index i is part of a pair, pairMap[i] contains
40
+ * all indices in that pair.
41
+ */
42
+ function findStructuralPairs(history) {
43
+ const pairMap = new Map();
44
+ for (let i = 0; i < history.length; i++) {
45
+ const msg = history[i];
46
+ if (isToolUseMessage(msg)) {
47
+ // Look for the corresponding tool_result in the next message
48
+ if (i + 1 < history.length && isToolResultMessage(history[i + 1])) {
49
+ const pair = [i, i + 1];
50
+ pairMap.set(i, pair);
51
+ pairMap.set(i + 1, pair);
52
+ }
53
+ }
54
+ }
55
+ return pairMap;
56
+ }
57
+ /**
58
+ * Perform emergency truncation on conversation history.
59
+ *
60
+ * Drops the oldest turns (preserving structural pairs) until the
61
+ * estimated token count drops below the threshold, or until only
62
+ * the preserved tail remains.
63
+ *
64
+ * @param history - Conversation history (post-slot region)
65
+ * @param contextWindow - Total context window size in tokens
66
+ * @param slotTokens - Estimated tokens used by slots
67
+ * @param threshold - Usage ratio threshold (default 0.90)
68
+ * @returns Truncation result with new history and metrics
69
+ */
70
+ export function emergencyTruncate(history, contextWindow, slotTokens, threshold = FAILSAFE_DEFAULTS.threshold) {
71
+ if (history.length === 0) {
72
+ return { newHistory: [], turnsRemoved: 0, tokensAfter: slotTokens };
73
+ }
74
+ const targetTokens = contextWindow * threshold;
75
+ const pairMap = findStructuralPairs(history);
76
+ const dropped = new Set();
77
+ // Calculate initial token estimate
78
+ let currentTokens = slotTokens + estimateTokens(history.map(m => extractTextContent(m)).join('\n'));
79
+ // Drop from the front, but respect the preserved tail
80
+ const preserveFrom = Math.max(0, history.length - FAILSAFE_PRESERVE_TURNS);
81
+ let i = 0;
82
+ while (currentTokens > targetTokens && i < preserveFrom) {
83
+ if (dropped.has(i)) {
84
+ i++;
85
+ continue;
86
+ }
87
+ // Get all indices that must be dropped together
88
+ const pair = pairMap.get(i);
89
+ const indicesToDrop = pair ?? [i];
90
+ // Check that none of the pair indices are in the preserved tail
91
+ const canDrop = indicesToDrop.every(idx => idx < preserveFrom);
92
+ if (!canDrop) {
93
+ i++;
94
+ continue;
95
+ }
96
+ // Drop the turn(s)
97
+ for (const idx of indicesToDrop) {
98
+ const msgTokens = estimateTokens(extractTextContent(history[idx]));
99
+ currentTokens -= msgTokens;
100
+ dropped.add(idx);
101
+ }
102
+ i++;
103
+ }
104
+ // Build new history excluding dropped messages
105
+ const newHistory = history.filter((_, idx) => !dropped.has(idx));
106
+ return {
107
+ newHistory,
108
+ turnsRemoved: dropped.size,
109
+ tokensAfter: currentTokens,
110
+ };
111
+ }
112
+ /**
113
+ * Check if emergency truncation should fire based on token count.
114
+ */
115
+ export function shouldTruncate(currentTokens, contextWindow, threshold = FAILSAFE_DEFAULTS.threshold) {
116
+ if (contextWindow <= 0)
117
+ return false;
118
+ return (currentTokens / contextWindow) >= threshold;
119
+ }
120
+ /**
121
+ * Check if an error represents a context overflow.
122
+ * Matches common API error patterns from various providers.
123
+ */
124
+ export function isContextOverflow(error) {
125
+ const msg = error.message.toLowerCase();
126
+ return (msg.includes('context_length_exceeded') ||
127
+ msg.includes('context window') ||
128
+ msg.includes('maximum context length') ||
129
+ msg.includes('token limit') ||
130
+ msg.includes('too many tokens') ||
131
+ msg.includes('request too large') ||
132
+ msg.includes('prompt is too long') ||
133
+ msg.includes('input too long'));
134
+ }
135
+ //# sourceMappingURL=failsafe.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"failsafe.js","sourceRoot":"","sources":["../../src/compaction/failsafe.ts"],"names":[],"mappings":"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"}