qlogicagent 0.2.1 → 0.3.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 (229) hide show
  1. package/dist/agent.js +1 -0
  2. package/dist/cli.js +9 -0
  3. package/dist/contracts.js +1 -0
  4. package/dist/index.js +5 -15
  5. package/dist/orchestration.js +118 -0
  6. package/package.json +56 -42
  7. package/dist/agent/agent.js +0 -113
  8. package/dist/agent/tool-loop.js +0 -575
  9. package/dist/agent/types.js +0 -14
  10. package/dist/cli/main.js +0 -23
  11. package/dist/cli/stdio-server.js +0 -463
  12. package/dist/config/config.js +0 -21
  13. package/dist/contracts/hooks.js +0 -7
  14. package/dist/contracts/index.js +0 -10
  15. package/dist/contracts/planner.js +0 -2
  16. package/dist/contracts/skill-candidate.js +0 -195
  17. package/dist/contracts/todo.js +0 -9
  18. package/dist/llm/builtin-providers.js +0 -531
  19. package/dist/llm/index.js +0 -14
  20. package/dist/llm/llm-client.js +0 -67
  21. package/dist/llm/model-catalog.js +0 -191
  22. package/dist/llm/provider-def.js +0 -12
  23. package/dist/llm/provider-registry.js +0 -147
  24. package/dist/llm/transport.js +0 -27
  25. package/dist/llm/transports/anthropic-messages.js +0 -293
  26. package/dist/llm/transports/openai-chat.js +0 -165
  27. package/dist/orchestration/agent-registry.js +0 -116
  28. package/dist/orchestration/approval-aware-tool-plan.js +0 -87
  29. package/dist/orchestration/context-compression.js +0 -583
  30. package/dist/orchestration/conversation-repair.js +0 -429
  31. package/dist/orchestration/curator-scheduler.js +0 -135
  32. package/dist/orchestration/embedded-failover-policy.js +0 -168
  33. package/dist/orchestration/error-classification.js +0 -77
  34. package/dist/orchestration/failover-classification.js +0 -381
  35. package/dist/orchestration/failover-error.js +0 -198
  36. package/dist/orchestration/fork-subagent.js +0 -98
  37. package/dist/orchestration/index.js +0 -267
  38. package/dist/orchestration/memory-flush-policy.js +0 -85
  39. package/dist/orchestration/memory-provider.js +0 -2
  40. package/dist/orchestration/parallel-tool-calls.js +0 -59
  41. package/dist/orchestration/prompt-cache-strategy.js +0 -228
  42. package/dist/orchestration/reactive-compact.js +0 -78
  43. package/dist/orchestration/retry-loop.js +0 -24
  44. package/dist/orchestration/skill-candidate.js +0 -141
  45. package/dist/orchestration/skill-consolidation.js +0 -220
  46. package/dist/orchestration/skill-improvement.js +0 -66
  47. package/dist/orchestration/skill-similarity.js +0 -131
  48. package/dist/orchestration/streaming-tool-executor.js +0 -96
  49. package/dist/orchestration/team-orchestration.js +0 -369
  50. package/dist/orchestration/team-tool-loop-wiring.js +0 -147
  51. package/dist/orchestration/tool-choice-policy.js +0 -164
  52. package/dist/orchestration/tool-loop-state.js +0 -133
  53. package/dist/orchestration/tool-schema.js +0 -297
  54. package/dist/orchestration/transcript-repair.js +0 -426
  55. package/dist/orchestration/turn-loop-guard.js +0 -92
  56. package/dist/orchestration/web-browser-policy.js +0 -39
  57. package/dist/runtime/context-compression.js +0 -274
  58. package/dist/runtime/hook-registry.js +0 -53
  59. package/dist/runtime/memory-hooks.js +0 -65
  60. package/dist/runtime/tool-eligibility.js +0 -111
  61. package/dist/skills/index.js +0 -82
  62. package/dist/skills/memory-extractor.js +0 -173
  63. package/dist/skills/memory-query-tool.js +0 -127
  64. package/dist/skills/memory-store.js +0 -228
  65. package/dist/skills/memory-tool.js +0 -192
  66. package/dist/skills/portable-tool.js +0 -14
  67. package/dist/skills/qmemory-adapter.js +0 -165
  68. package/dist/skills/skill-frontmatter.js +0 -344
  69. package/dist/skills/skill-guard.js +0 -229
  70. package/dist/skills/skill-loader.js +0 -303
  71. package/dist/skills/skill-source.js +0 -126
  72. package/dist/skills/skill-types.js +0 -6
  73. package/dist/skills/think-tool.js +0 -59
  74. package/dist/skills/todo-tool.js +0 -114
  75. package/dist/skills/tools/agent-tool.js +0 -142
  76. package/dist/skills/tools/apply-patch-tool.js +0 -184
  77. package/dist/skills/tools/ask-user-tool.js +0 -121
  78. package/dist/skills/tools/brief-tool.js +0 -95
  79. package/dist/skills/tools/browser-tool.js +0 -155
  80. package/dist/skills/tools/checkpoint-tool.js +0 -102
  81. package/dist/skills/tools/config-tool.js +0 -143
  82. package/dist/skills/tools/cron-tool.js +0 -175
  83. package/dist/skills/tools/edit-tool.js +0 -70
  84. package/dist/skills/tools/exec-tool.js +0 -133
  85. package/dist/skills/tools/image-generate-tool.js +0 -67
  86. package/dist/skills/tools/instructions-tool.js +0 -187
  87. package/dist/skills/tools/lsp-tool.js +0 -227
  88. package/dist/skills/tools/mcp-client-types.js +0 -53
  89. package/dist/skills/tools/mcp-tool.js +0 -503
  90. package/dist/skills/tools/memory-tool.js +0 -88
  91. package/dist/skills/tools/monitor-tool.js +0 -131
  92. package/dist/skills/tools/music-generate-tool.js +0 -62
  93. package/dist/skills/tools/notify-tool.js +0 -62
  94. package/dist/skills/tools/patch-tool.js +0 -505
  95. package/dist/skills/tools/pdf-tool.js +0 -88
  96. package/dist/skills/tools/plan-mode-tool.js +0 -122
  97. package/dist/skills/tools/read-tool.js +0 -84
  98. package/dist/skills/tools/repl-tool.js +0 -69
  99. package/dist/skills/tools/search-tool.js +0 -225
  100. package/dist/skills/tools/send-message-tool.js +0 -76
  101. package/dist/skills/tools/skill-list-tool.js +0 -54
  102. package/dist/skills/tools/skill-manage-tool.js +0 -153
  103. package/dist/skills/tools/skill-view-tool.js +0 -72
  104. package/dist/skills/tools/sleep-tool.js +0 -81
  105. package/dist/skills/tools/structured-output-tool.js +0 -176
  106. package/dist/skills/tools/task-tool.js +0 -161
  107. package/dist/skills/tools/team-tool.js +0 -105
  108. package/dist/skills/tools/tool-search-tool.js +0 -110
  109. package/dist/skills/tools/tts-tool.js +0 -45
  110. package/dist/skills/tools/video-edit-tool.js +0 -74
  111. package/dist/skills/tools/video-generate-tool.js +0 -66
  112. package/dist/skills/tools/video-merge-tool.js +0 -92
  113. package/dist/skills/tools/video-upscale-tool.js +0 -52
  114. package/dist/skills/tools/web-fetch-tool.js +0 -92
  115. package/dist/skills/tools/web-search-tool.js +0 -86
  116. package/dist/skills/tools/worktree-tool.js +0 -147
  117. package/dist/skills/tools/write-tool.js +0 -81
  118. /package/dist/{agent → types/agent}/agent.d.ts +0 -0
  119. /package/dist/{agent → types/agent}/tool-loop.d.ts +0 -0
  120. /package/dist/{agent → types/agent}/types.d.ts +0 -0
  121. /package/dist/{cli → types/cli}/main.d.ts +0 -0
  122. /package/dist/{cli → types/cli}/stdio-server.d.ts +0 -0
  123. /package/dist/{config → types/config}/config.d.ts +0 -0
  124. /package/dist/{contracts → types/contracts}/hooks.d.ts +0 -0
  125. /package/dist/{contracts → types/contracts}/index.d.ts +0 -0
  126. /package/dist/{contracts → types/contracts}/planner.d.ts +0 -0
  127. /package/dist/{contracts → types/contracts}/skill-candidate.d.ts +0 -0
  128. /package/dist/{contracts → types/contracts}/todo.d.ts +0 -0
  129. /package/dist/{index.d.ts → types/index.d.ts} +0 -0
  130. /package/dist/{llm → types/llm}/builtin-providers.d.ts +0 -0
  131. /package/dist/{llm → types/llm}/index.d.ts +0 -0
  132. /package/dist/{llm → types/llm}/llm-client.d.ts +0 -0
  133. /package/dist/{llm → types/llm}/model-catalog.d.ts +0 -0
  134. /package/dist/{llm → types/llm}/provider-def.d.ts +0 -0
  135. /package/dist/{llm → types/llm}/provider-registry.d.ts +0 -0
  136. /package/dist/{llm → types/llm}/transport.d.ts +0 -0
  137. /package/dist/{llm → types/llm}/transports/anthropic-messages.d.ts +0 -0
  138. /package/dist/{llm → types/llm}/transports/openai-chat.d.ts +0 -0
  139. /package/dist/{orchestration → types/orchestration}/agent-registry.d.ts +0 -0
  140. /package/dist/{orchestration → types/orchestration}/approval-aware-tool-plan.d.ts +0 -0
  141. /package/dist/{orchestration → types/orchestration}/context-compression.d.ts +0 -0
  142. /package/dist/{orchestration → types/orchestration}/conversation-repair.d.ts +0 -0
  143. /package/dist/{orchestration → types/orchestration}/curator-scheduler.d.ts +0 -0
  144. /package/dist/{orchestration → types/orchestration}/embedded-failover-policy.d.ts +0 -0
  145. /package/dist/{orchestration → types/orchestration}/error-classification.d.ts +0 -0
  146. /package/dist/{orchestration → types/orchestration}/failover-classification.d.ts +0 -0
  147. /package/dist/{orchestration → types/orchestration}/failover-error.d.ts +0 -0
  148. /package/dist/{orchestration → types/orchestration}/fork-subagent.d.ts +0 -0
  149. /package/dist/{orchestration → types/orchestration}/index.d.ts +0 -0
  150. /package/dist/{orchestration → types/orchestration}/memory-flush-policy.d.ts +0 -0
  151. /package/dist/{orchestration → types/orchestration}/memory-provider.d.ts +0 -0
  152. /package/dist/{orchestration → types/orchestration}/parallel-tool-calls.d.ts +0 -0
  153. /package/dist/{orchestration → types/orchestration}/prompt-cache-strategy.d.ts +0 -0
  154. /package/dist/{orchestration → types/orchestration}/reactive-compact.d.ts +0 -0
  155. /package/dist/{orchestration → types/orchestration}/retry-loop.d.ts +0 -0
  156. /package/dist/{orchestration → types/orchestration}/skill-candidate.d.ts +0 -0
  157. /package/dist/{orchestration → types/orchestration}/skill-consolidation.d.ts +0 -0
  158. /package/dist/{orchestration → types/orchestration}/skill-improvement.d.ts +0 -0
  159. /package/dist/{orchestration → types/orchestration}/skill-similarity.d.ts +0 -0
  160. /package/dist/{orchestration → types/orchestration}/streaming-tool-executor.d.ts +0 -0
  161. /package/dist/{orchestration → types/orchestration}/team-orchestration.d.ts +0 -0
  162. /package/dist/{orchestration → types/orchestration}/team-tool-loop-wiring.d.ts +0 -0
  163. /package/dist/{orchestration → types/orchestration}/tool-choice-policy.d.ts +0 -0
  164. /package/dist/{orchestration → types/orchestration}/tool-loop-state.d.ts +0 -0
  165. /package/dist/{orchestration → types/orchestration}/tool-schema.d.ts +0 -0
  166. /package/dist/{orchestration → types/orchestration}/transcript-repair.d.ts +0 -0
  167. /package/dist/{orchestration → types/orchestration}/turn-loop-guard.d.ts +0 -0
  168. /package/dist/{orchestration → types/orchestration}/web-browser-policy.d.ts +0 -0
  169. /package/dist/{runtime → types/runtime}/context-compression.d.ts +0 -0
  170. /package/dist/{runtime → types/runtime}/hook-registry.d.ts +0 -0
  171. /package/dist/{runtime → types/runtime}/memory-hooks.d.ts +0 -0
  172. /package/dist/{runtime → types/runtime}/tool-eligibility.d.ts +0 -0
  173. /package/dist/{skills → types/skills}/index.d.ts +0 -0
  174. /package/dist/{skills → types/skills}/memory-extractor.d.ts +0 -0
  175. /package/dist/{skills → types/skills}/memory-query-tool.d.ts +0 -0
  176. /package/dist/{skills → types/skills}/memory-store.d.ts +0 -0
  177. /package/dist/{skills → types/skills}/memory-tool.d.ts +0 -0
  178. /package/dist/{skills → types/skills}/portable-tool.d.ts +0 -0
  179. /package/dist/{skills → types/skills}/qmemory-adapter.d.ts +0 -0
  180. /package/dist/{skills → types/skills}/skill-frontmatter.d.ts +0 -0
  181. /package/dist/{skills → types/skills}/skill-guard.d.ts +0 -0
  182. /package/dist/{skills → types/skills}/skill-loader.d.ts +0 -0
  183. /package/dist/{skills → types/skills}/skill-source.d.ts +0 -0
  184. /package/dist/{skills → types/skills}/skill-types.d.ts +0 -0
  185. /package/dist/{skills → types/skills}/think-tool.d.ts +0 -0
  186. /package/dist/{skills → types/skills}/todo-tool.d.ts +0 -0
  187. /package/dist/{skills → types/skills}/tools/agent-tool.d.ts +0 -0
  188. /package/dist/{skills → types/skills}/tools/apply-patch-tool.d.ts +0 -0
  189. /package/dist/{skills → types/skills}/tools/ask-user-tool.d.ts +0 -0
  190. /package/dist/{skills → types/skills}/tools/brief-tool.d.ts +0 -0
  191. /package/dist/{skills → types/skills}/tools/browser-tool.d.ts +0 -0
  192. /package/dist/{skills → types/skills}/tools/checkpoint-tool.d.ts +0 -0
  193. /package/dist/{skills → types/skills}/tools/config-tool.d.ts +0 -0
  194. /package/dist/{skills → types/skills}/tools/cron-tool.d.ts +0 -0
  195. /package/dist/{skills → types/skills}/tools/edit-tool.d.ts +0 -0
  196. /package/dist/{skills → types/skills}/tools/exec-tool.d.ts +0 -0
  197. /package/dist/{skills → types/skills}/tools/image-generate-tool.d.ts +0 -0
  198. /package/dist/{skills → types/skills}/tools/instructions-tool.d.ts +0 -0
  199. /package/dist/{skills → types/skills}/tools/lsp-tool.d.ts +0 -0
  200. /package/dist/{skills → types/skills}/tools/mcp-client-types.d.ts +0 -0
  201. /package/dist/{skills → types/skills}/tools/mcp-tool.d.ts +0 -0
  202. /package/dist/{skills → types/skills}/tools/memory-tool.d.ts +0 -0
  203. /package/dist/{skills → types/skills}/tools/monitor-tool.d.ts +0 -0
  204. /package/dist/{skills → types/skills}/tools/music-generate-tool.d.ts +0 -0
  205. /package/dist/{skills → types/skills}/tools/notify-tool.d.ts +0 -0
  206. /package/dist/{skills → types/skills}/tools/patch-tool.d.ts +0 -0
  207. /package/dist/{skills → types/skills}/tools/pdf-tool.d.ts +0 -0
  208. /package/dist/{skills → types/skills}/tools/plan-mode-tool.d.ts +0 -0
  209. /package/dist/{skills → types/skills}/tools/read-tool.d.ts +0 -0
  210. /package/dist/{skills → types/skills}/tools/repl-tool.d.ts +0 -0
  211. /package/dist/{skills → types/skills}/tools/search-tool.d.ts +0 -0
  212. /package/dist/{skills → types/skills}/tools/send-message-tool.d.ts +0 -0
  213. /package/dist/{skills → types/skills}/tools/skill-list-tool.d.ts +0 -0
  214. /package/dist/{skills → types/skills}/tools/skill-manage-tool.d.ts +0 -0
  215. /package/dist/{skills → types/skills}/tools/skill-view-tool.d.ts +0 -0
  216. /package/dist/{skills → types/skills}/tools/sleep-tool.d.ts +0 -0
  217. /package/dist/{skills → types/skills}/tools/structured-output-tool.d.ts +0 -0
  218. /package/dist/{skills → types/skills}/tools/task-tool.d.ts +0 -0
  219. /package/dist/{skills → types/skills}/tools/team-tool.d.ts +0 -0
  220. /package/dist/{skills → types/skills}/tools/tool-search-tool.d.ts +0 -0
  221. /package/dist/{skills → types/skills}/tools/tts-tool.d.ts +0 -0
  222. /package/dist/{skills → types/skills}/tools/video-edit-tool.d.ts +0 -0
  223. /package/dist/{skills → types/skills}/tools/video-generate-tool.d.ts +0 -0
  224. /package/dist/{skills → types/skills}/tools/video-merge-tool.d.ts +0 -0
  225. /package/dist/{skills → types/skills}/tools/video-upscale-tool.d.ts +0 -0
  226. /package/dist/{skills → types/skills}/tools/web-fetch-tool.d.ts +0 -0
  227. /package/dist/{skills → types/skills}/tools/web-search-tool.d.ts +0 -0
  228. /package/dist/{skills → types/skills}/tools/worktree-tool.d.ts +0 -0
  229. /package/dist/{skills → types/skills}/tools/write-tool.d.ts +0 -0
@@ -1,583 +0,0 @@
1
- // ============================================================
2
- // Context compression strategies — reduce message history
3
- // to fit within a token budget.
4
- //
5
- // Phase 1: ToolResultTrim + SlidingWindow (sync, no LLM)
6
- // Phase 2: Structured LLM summarization + head/tail protection
7
- // Phase 3: Prompt cache awareness
8
- // Phase 4: Adaptive threshold + metrics + engine plugin
9
- // ============================================================
10
- export function isAsyncCompressionStrategy(s) {
11
- return typeof s.compressAsync === "function";
12
- }
13
- // ── Sliding Window ──────────────────────────────────────────
14
- // Keep the system prompt(s) + last N messages that fit the budget.
15
- export class SlidingWindowStrategy {
16
- estimateTokens;
17
- constructor(estimateTokens) {
18
- this.estimateTokens = estimateTokens;
19
- }
20
- compress(messages, budget) {
21
- const systemMessages = [];
22
- const nonSystem = [];
23
- for (const msg of messages) {
24
- if (msg.role === "system") {
25
- systemMessages.push(msg);
26
- }
27
- else {
28
- nonSystem.push(msg);
29
- }
30
- }
31
- let remaining = budget;
32
- for (const sys of systemMessages) {
33
- remaining -= this.estimateTokens(sys);
34
- }
35
- if (remaining <= 0) {
36
- return { messages: systemMessages, droppedCount: nonSystem.length, strategy: "sliding-window" };
37
- }
38
- const kept = [];
39
- for (let i = nonSystem.length - 1; i >= 0; i--) {
40
- const cost = this.estimateTokens(nonSystem[i]);
41
- if (remaining - cost < 0)
42
- break;
43
- remaining -= cost;
44
- kept.unshift(nonSystem[i]);
45
- }
46
- return {
47
- messages: [...systemMessages, ...kept],
48
- droppedCount: nonSystem.length - kept.length,
49
- strategy: "sliding-window",
50
- };
51
- }
52
- }
53
- // ── Summarize Old ───────────────────────────────────────────
54
- // Keep last `recentCount` messages as-is, replace older ones
55
- // with a single system-role summary placeholder.
56
- export class SummarizeOldStrategy {
57
- recentCount;
58
- summarize;
59
- constructor(recentCount, summarize) {
60
- this.recentCount = recentCount;
61
- this.summarize = summarize;
62
- }
63
- compress(messages, _budget) {
64
- const systemMessages = messages.filter((m) => m.role === "system");
65
- const nonSystem = messages.filter((m) => m.role !== "system");
66
- if (nonSystem.length <= this.recentCount) {
67
- return { messages, droppedCount: 0, strategy: "summarize-old" };
68
- }
69
- const oldMessages = nonSystem.slice(0, nonSystem.length - this.recentCount);
70
- const recentMessages = nonSystem.slice(nonSystem.length - this.recentCount);
71
- const summary = this.summarize(oldMessages);
72
- return {
73
- messages: [
74
- ...systemMessages,
75
- { role: "system", content: `[Conversation summary]\n${summary}` },
76
- ...recentMessages,
77
- ],
78
- droppedCount: oldMessages.length,
79
- strategy: "summarize-old",
80
- };
81
- }
82
- }
83
- // ── Tool Result Trim ────────────────────────────────────────
84
- // Truncate oversized tool results to a max character length.
85
- export class ToolResultTrimStrategy {
86
- maxToolResultChars;
87
- constructor(maxToolResultChars = 8000) {
88
- this.maxToolResultChars = maxToolResultChars;
89
- }
90
- compress(messages, _budget) {
91
- let trimmedCount = 0;
92
- const result = messages.map((msg) => {
93
- if (msg.role !== "tool" || typeof msg.content !== "string")
94
- return msg;
95
- if (msg.content.length <= this.maxToolResultChars)
96
- return msg;
97
- trimmedCount++;
98
- return {
99
- ...msg,
100
- content: msg.content.slice(0, this.maxToolResultChars) + "\n[...truncated]",
101
- };
102
- });
103
- return {
104
- messages: result,
105
- droppedCount: trimmedCount,
106
- strategy: "tool-result-trim",
107
- };
108
- }
109
- }
110
- // ── Composite ───────────────────────────────────────────────
111
- // Apply multiple strategies in sequence.
112
- export function composeStrategies(...strategies) {
113
- return {
114
- compress(messages, budget) {
115
- let current = messages;
116
- let totalDropped = 0;
117
- const names = [];
118
- for (const strategy of strategies) {
119
- const result = strategy.compress(current, budget);
120
- current = result.messages;
121
- totalDropped += result.droppedCount;
122
- if (result.droppedCount > 0) {
123
- names.push(result.strategy);
124
- }
125
- }
126
- return {
127
- messages: current,
128
- droppedCount: totalDropped,
129
- strategy: names.length > 0 ? names.join("+") : "none",
130
- };
131
- },
132
- };
133
- }
134
- /**
135
- * Compose strategies with async support — if any strategy is async,
136
- * the pipeline becomes async.
137
- */
138
- export function composeAsyncStrategies(...strategies) {
139
- return {
140
- compress(messages, budget) {
141
- // Sync fallback: skip async strategies
142
- let current = messages;
143
- let totalDropped = 0;
144
- const names = [];
145
- for (const strategy of strategies) {
146
- const result = strategy.compress(current, budget);
147
- current = result.messages;
148
- totalDropped += result.droppedCount;
149
- if (result.droppedCount > 0)
150
- names.push(result.strategy);
151
- }
152
- return {
153
- messages: current,
154
- droppedCount: totalDropped,
155
- strategy: names.length > 0 ? names.join("+") : "none",
156
- };
157
- },
158
- async compressAsync(messages, budget) {
159
- let current = messages;
160
- let totalDropped = 0;
161
- const names = [];
162
- let totalLatency = 0;
163
- let anyLlm = false;
164
- let anyCacheInvalidated = false;
165
- for (const strategy of strategies) {
166
- const result = isAsyncCompressionStrategy(strategy)
167
- ? await strategy.compressAsync(current, budget)
168
- : strategy.compress(current, budget);
169
- current = result.messages;
170
- totalDropped += result.droppedCount;
171
- if (result.droppedCount > 0)
172
- names.push(result.strategy);
173
- if (result.metrics) {
174
- totalLatency += result.metrics.latencyMs;
175
- anyLlm = anyLlm || result.metrics.usedLlm;
176
- anyCacheInvalidated = anyCacheInvalidated || !!result.metrics.cacheInvalidated;
177
- }
178
- }
179
- return {
180
- messages: current,
181
- droppedCount: totalDropped,
182
- strategy: names.length > 0 ? names.join("+") : "none",
183
- metrics: totalLatency > 0 || anyLlm
184
- ? { tokensBefore: 0, tokensAfter: 0, compressionRatio: 0, latencyMs: totalLatency, usedLlm: anyLlm, cacheInvalidated: anyCacheInvalidated }
185
- : undefined,
186
- };
187
- },
188
- };
189
- }
190
- // ════════════════════════════════════════════════════════════
191
- // Phase 2: LLM Summarization Strategies
192
- // ════════════════════════════════════════════════════════════
193
- // ── 2.1 Structured Summary Prompt (Claude Code style) ──────
194
- /**
195
- * Build the structured 9-section summary instruction for the LLM.
196
- * Based on Claude Code's Full Compact mode, adapted for Hub.
197
- */
198
- export function buildStructuredSummaryPrompt(messagesToSummarize, opts) {
199
- const userMsgs = messagesToSummarize.filter((m) => m.role === "user");
200
- const toolCalls = messagesToSummarize.filter((m) => m.tool_calls != null);
201
- const toolResults = messagesToSummarize.filter((m) => m.role === "tool");
202
- const sections = [
203
- "You are a conversation summarizer. Produce a structured summary of the conversation history below.",
204
- "",
205
- "## Instructions",
206
- "Analyze the conversation and produce a summary with these sections:",
207
- "",
208
- "### 1. Primary Objective",
209
- "What is the user's main goal or task? State it in one sentence.",
210
- "",
211
- "### 2. Key Decisions Made",
212
- "List the important decisions, choices, or conclusions reached during the conversation.",
213
- "",
214
- "### 3. Current Progress",
215
- `Describe the current state. ${toolCalls.length > 0 ? `${toolCalls.length} tool calls and ${toolResults.length} tool results were exchanged.` : "No tools were used."}`,
216
- "",
217
- "### 4. Pending Tasks",
218
- "List any tasks that are in-progress or planned but not yet completed.",
219
- "",
220
- "### 5. Important Context",
221
- "Note any critical facts, constraints, or preferences the user mentioned that must be preserved.",
222
- "",
223
- "### 6. Error & Recovery History",
224
- "Summarize any errors encountered and how they were resolved.",
225
- "",
226
- "### 7. User Preferences Expressed",
227
- `The user sent ${userMsgs.length} messages. Note any stated preferences about style, approach, or constraints.`,
228
- "",
229
- "### 8. Technical State",
230
- "Note file paths, variable names, API endpoints, or configuration values that were discussed.",
231
- "",
232
- "### 9. Conversation Flow",
233
- "Briefly describe the overall flow: what happened first, what changed, where we are now.",
234
- ];
235
- if (opts?.taskContext) {
236
- sections.push("", `## Additional Context`, opts.taskContext);
237
- }
238
- sections.push("", "## Conversation to Summarize", "", ...messagesToSummarize.map((m) => {
239
- const content = typeof m.content === "string" ? m.content : JSON.stringify(m.content ?? "");
240
- const truncated = content.length > 2000 ? content.slice(0, 2000) + "..." : content;
241
- return `[${m.role}]: ${truncated}`;
242
- }), "", "## Output Format", "Respond with a concise summary covering all 9 sections above. Use markdown headers.", "Keep the total summary under 800 words. Focus on actionable information.");
243
- return sections.join("\n");
244
- }
245
- /**
246
- * Phase 2.2: Head/Tail protected summarization.
247
- *
248
- * Protects: system messages + first exchange + last N messages.
249
- * Compresses: middle section via LLM summarization.
250
- */
251
- export class HeadTailProtectedStrategy {
252
- config;
253
- constructor(config) {
254
- this.config = {
255
- protectedHeadExchanges: config.protectedHeadExchanges,
256
- protectedTailMessages: config.protectedTailMessages,
257
- summarize: config.summarize,
258
- estimateTokens: config.estimateTokens ?? defaultEstimateTokens,
259
- taskContext: config.taskContext,
260
- };
261
- }
262
- compress(messages, _budget) {
263
- // Sync fallback: just pass through (cannot call LLM synchronously)
264
- return { messages, droppedCount: 0, strategy: "head-tail-protected" };
265
- }
266
- async compressAsync(messages, budget) {
267
- const start = Date.now();
268
- const { system, nonSystem } = splitSystemMessages(messages);
269
- // Check if compression is needed
270
- const totalTokens = messages.reduce((sum, m) => sum + this.config.estimateTokens(m), 0);
271
- if (totalTokens <= budget) {
272
- return { messages, droppedCount: 0, strategy: "head-tail-protected" };
273
- }
274
- // Identify protected head: first N user+assistant exchanges
275
- let headEnd = 0;
276
- let exchangeCount = 0;
277
- for (let i = 0; i < nonSystem.length; i++) {
278
- if (nonSystem[i].role === "user")
279
- exchangeCount++;
280
- if (exchangeCount > this.config.protectedHeadExchanges)
281
- break;
282
- headEnd = i + 1;
283
- }
284
- // Protected tail
285
- const tailStart = Math.max(headEnd, nonSystem.length - this.config.protectedTailMessages);
286
- // If there's nothing to compress in the middle, pass through
287
- if (tailStart <= headEnd) {
288
- return { messages, droppedCount: 0, strategy: "head-tail-protected" };
289
- }
290
- const headMessages = nonSystem.slice(0, headEnd);
291
- const middleMessages = nonSystem.slice(headEnd, tailStart);
292
- const tailMessages = nonSystem.slice(tailStart);
293
- // Summarize middle section via LLM
294
- const instruction = buildStructuredSummaryPrompt(middleMessages, {
295
- taskContext: this.config.taskContext,
296
- });
297
- const summary = await this.config.summarize(middleMessages, instruction);
298
- const summaryMessage = {
299
- role: "system",
300
- content: `[Conversation summary — ${middleMessages.length} messages compressed]\n\n${summary}`,
301
- };
302
- const resultMessages = [
303
- ...system,
304
- ...headMessages,
305
- summaryMessage,
306
- ...tailMessages,
307
- ];
308
- const latencyMs = Date.now() - start;
309
- const tokensAfter = resultMessages.reduce((sum, m) => sum + this.config.estimateTokens(m), 0);
310
- return {
311
- messages: resultMessages,
312
- droppedCount: middleMessages.length,
313
- strategy: "head-tail-protected",
314
- metrics: {
315
- tokensBefore: totalTokens,
316
- tokensAfter,
317
- compressionRatio: totalTokens > 0 ? tokensAfter / totalTokens : 1,
318
- latencyMs,
319
- usedLlm: true,
320
- cacheInvalidated: true,
321
- },
322
- };
323
- }
324
- }
325
- /**
326
- * Phase 2.3: Incremental (partial) compaction.
327
- *
328
- * Only summarizes the oldest messages beyond the preserve window.
329
- * Avoids repeatedly re-summarizing already-compressed content.
330
- * If a previous summary marker exists, only new old messages are compressed.
331
- */
332
- export class IncrementalCompactStrategy {
333
- config;
334
- constructor(config) {
335
- this.config = {
336
- preserveRecentCount: config.preserveRecentCount,
337
- summarize: config.summarize,
338
- estimateTokens: config.estimateTokens ?? defaultEstimateTokens,
339
- };
340
- }
341
- compress(messages, _budget) {
342
- return { messages, droppedCount: 0, strategy: "incremental-compact" };
343
- }
344
- async compressAsync(messages, budget) {
345
- const start = Date.now();
346
- const { system, nonSystem } = splitSystemMessages(messages);
347
- // Find existing summary marker
348
- const existingSummaryIdx = system.findIndex((m) => typeof m.content === "string" && m.content.startsWith("[Conversation summary"));
349
- const existingSummary = existingSummaryIdx >= 0 ? system[existingSummaryIdx] : undefined;
350
- const systemWithoutOldSummary = existingSummaryIdx >= 0
351
- ? [...system.slice(0, existingSummaryIdx), ...system.slice(existingSummaryIdx + 1)]
352
- : system;
353
- // How many messages to preserve
354
- const preserveStart = Math.max(0, nonSystem.length - this.config.preserveRecentCount);
355
- if (preserveStart <= 0) {
356
- return { messages, droppedCount: 0, strategy: "incremental-compact" };
357
- }
358
- const totalTokens = messages.reduce((sum, m) => sum + this.config.estimateTokens(m), 0);
359
- if (totalTokens <= budget) {
360
- return { messages, droppedCount: 0, strategy: "incremental-compact" };
361
- }
362
- const oldMessages = nonSystem.slice(0, preserveStart);
363
- const recentMessages = nonSystem.slice(preserveStart);
364
- // Build summary instruction including existing summary context
365
- const contextPrefix = existingSummary && typeof existingSummary.content === "string"
366
- ? `Previous summary:\n${existingSummary.content}\n\nNew messages to integrate:`
367
- : undefined;
368
- const instruction = buildStructuredSummaryPrompt(oldMessages, { taskContext: contextPrefix });
369
- const summary = await this.config.summarize(oldMessages, instruction);
370
- const summaryMessage = {
371
- role: "system",
372
- content: `[Conversation summary — ${oldMessages.length} messages compressed]\n\n${summary}`,
373
- };
374
- const resultMessages = [...systemWithoutOldSummary, summaryMessage, ...recentMessages];
375
- const latencyMs = Date.now() - start;
376
- const tokensAfter = resultMessages.reduce((sum, m) => sum + this.config.estimateTokens(m), 0);
377
- return {
378
- messages: resultMessages,
379
- droppedCount: oldMessages.length,
380
- strategy: "incremental-compact",
381
- metrics: {
382
- tokensBefore: totalTokens,
383
- tokensAfter,
384
- compressionRatio: totalTokens > 0 ? tokensAfter / totalTokens : 1,
385
- latencyMs,
386
- usedLlm: true,
387
- cacheInvalidated: true,
388
- },
389
- };
390
- }
391
- }
392
- /**
393
- * Phase 3: Cache-aware wrapper.
394
- *
395
- * Wraps any strategy and tracks whether compression invalidated the
396
- * provider prompt cache. System prompt prefix stability is preserved
397
- * by never modifying system[0] (the original system prompt).
398
- */
399
- export class CacheAwareCompressionStrategy {
400
- config;
401
- constructor(config) {
402
- this.config = config;
403
- }
404
- compress(messages, budget) {
405
- const beforeHash = computeMessagePrefixHash(messages);
406
- const result = this.config.inner.compress(messages, budget);
407
- const afterHash = computeMessagePrefixHash(result.messages);
408
- const cacheInvalidated = beforeHash !== afterHash && result.droppedCount > 0;
409
- if (cacheInvalidated) {
410
- this.config.onCacheInvalidated?.({ droppedCount: result.droppedCount, strategy: result.strategy });
411
- }
412
- return {
413
- ...result,
414
- metrics: {
415
- ...(result.metrics ?? {
416
- tokensBefore: 0, tokensAfter: 0, compressionRatio: 0, latencyMs: 0, usedLlm: false,
417
- }),
418
- cacheInvalidated,
419
- },
420
- };
421
- }
422
- async compressAsync(messages, budget) {
423
- const beforeHash = computeMessagePrefixHash(messages);
424
- const result = isAsyncCompressionStrategy(this.config.inner)
425
- ? await this.config.inner.compressAsync(messages, budget)
426
- : this.config.inner.compress(messages, budget);
427
- const afterHash = computeMessagePrefixHash(result.messages);
428
- const cacheInvalidated = beforeHash !== afterHash && result.droppedCount > 0;
429
- if (cacheInvalidated) {
430
- this.config.onCacheInvalidated?.({ droppedCount: result.droppedCount, strategy: result.strategy });
431
- }
432
- return {
433
- ...result,
434
- metrics: {
435
- ...(result.metrics ?? {
436
- tokensBefore: 0, tokensAfter: 0, compressionRatio: 0, latencyMs: 0, usedLlm: false,
437
- }),
438
- cacheInvalidated,
439
- },
440
- };
441
- }
442
- }
443
- export const DEFAULT_ADAPTIVE_BUDGET_CONFIG = {
444
- modelContextWindow: 128_000,
445
- targetUsageRatio: 0.75,
446
- minBudget: 16_000,
447
- maxBudget: 120_000,
448
- };
449
- /**
450
- * Compute the adaptive token budget for a given model + message history.
451
- *
452
- * Adjusts based on model context window, and uses the target ratio
453
- * so compression triggers before hitting the hard limit.
454
- */
455
- export function computeAdaptiveBudget(config = {}) {
456
- const c = { ...DEFAULT_ADAPTIVE_BUDGET_CONFIG, ...config };
457
- const target = Math.floor(c.modelContextWindow * c.targetUsageRatio);
458
- return Math.max(c.minBudget, Math.min(target, c.maxBudget));
459
- }
460
- export function selectCompressionTier(currentTokens, budget) {
461
- const ratio = currentTokens / budget;
462
- if (ratio <= 0.8)
463
- return "none";
464
- if (ratio <= 1.0)
465
- return "trim-only";
466
- if (ratio <= 1.5)
467
- return "sliding-window";
468
- return "llm-summarize";
469
- }
470
- export class CompressionMetricsCollector {
471
- events = [];
472
- maxEvents;
473
- constructor(maxEvents = 100) {
474
- this.maxEvents = maxEvents;
475
- }
476
- record(event) {
477
- this.events.push(event);
478
- if (this.events.length > this.maxEvents) {
479
- this.events.shift();
480
- }
481
- }
482
- snapshot() {
483
- const total = this.events.length;
484
- if (total === 0) {
485
- return {
486
- totalCompressions: 0,
487
- totalLlmCalls: 0,
488
- totalCacheInvalidations: 0,
489
- averageCompressionRatio: 1,
490
- averageLatencyMs: 0,
491
- totalTokensSaved: 0,
492
- recentEvents: [],
493
- };
494
- }
495
- let sumRatio = 0;
496
- let sumLatency = 0;
497
- let tokensSaved = 0;
498
- let llmCalls = 0;
499
- let cacheInvalidations = 0;
500
- for (const e of this.events) {
501
- sumRatio += e.tokensBefore > 0 ? e.tokensAfter / e.tokensBefore : 1;
502
- sumLatency += e.latencyMs;
503
- tokensSaved += Math.max(0, e.tokensBefore - e.tokensAfter);
504
- if (e.usedLlm)
505
- llmCalls++;
506
- if (e.cacheInvalidated)
507
- cacheInvalidations++;
508
- }
509
- return {
510
- totalCompressions: total,
511
- totalLlmCalls: llmCalls,
512
- totalCacheInvalidations: cacheInvalidations,
513
- averageCompressionRatio: sumRatio / total,
514
- averageLatencyMs: sumLatency / total,
515
- totalTokensSaved: tokensSaved,
516
- recentEvents: this.events.slice(-10),
517
- };
518
- }
519
- reset() {
520
- this.events.length = 0;
521
- }
522
- }
523
- /**
524
- * Registry for context engines. Enforces single-active constraint.
525
- */
526
- export class ContextEngineRegistry {
527
- engines = new Map();
528
- activeId;
529
- register(engine) {
530
- this.engines.set(engine.id, engine);
531
- }
532
- activate(id) {
533
- if (!this.engines.has(id))
534
- return false;
535
- this.activeId = id;
536
- return true;
537
- }
538
- getActive() {
539
- return this.activeId ? this.engines.get(this.activeId) : undefined;
540
- }
541
- listEngines() {
542
- return Array.from(this.engines.values()).map((e) => ({
543
- id: e.id,
544
- label: e.label,
545
- active: e.id === this.activeId,
546
- }));
547
- }
548
- }
549
- // ── Helpers ─────────────────────────────────────────────────
550
- function splitSystemMessages(messages) {
551
- const system = [];
552
- const nonSystem = [];
553
- for (const msg of messages) {
554
- if (msg.role === "system")
555
- system.push(msg);
556
- else
557
- nonSystem.push(msg);
558
- }
559
- return { system, nonSystem };
560
- }
561
- function defaultEstimateTokens(msg) {
562
- const text = typeof msg.content === "string"
563
- ? msg.content
564
- : msg.content != null
565
- ? JSON.stringify(msg.content)
566
- : "";
567
- return Math.ceil(text.length / 4);
568
- }
569
- /**
570
- * Simple hash of the first few messages' role+content prefix.
571
- * Used to detect whether compression changed the prompt prefix
572
- * (which would invalidate provider prompt caching).
573
- */
574
- function computeMessagePrefixHash(messages) {
575
- const prefixCount = Math.min(messages.length, 5);
576
- const parts = [];
577
- for (let i = 0; i < prefixCount; i++) {
578
- const m = messages[i];
579
- const content = typeof m.content === "string" ? m.content.slice(0, 200) : "";
580
- parts.push(`${m.role}:${content}`);
581
- }
582
- return parts.join("|");
583
- }