neuro-cli 4.1.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.
- package/LICENSE +21 -0
- package/README.md +510 -0
- package/dist/advisor/advisor.d.ts +50 -0
- package/dist/advisor/advisor.js +178 -0
- package/dist/agents/base.d.ts +62 -0
- package/dist/agents/base.js +215 -0
- package/dist/agents/orchestrator.d.ts +46 -0
- package/dist/agents/orchestrator.js +192 -0
- package/dist/agents/team.d.ts +51 -0
- package/dist/agents/team.js +210 -0
- package/dist/api/models.d.ts +23 -0
- package/dist/api/models.js +514 -0
- package/dist/api/ollama.d.ts +153 -0
- package/dist/api/ollama.js +751 -0
- package/dist/api/openrouter.d.ts +55 -0
- package/dist/api/openrouter.js +223 -0
- package/dist/commands/commands.d.ts +43 -0
- package/dist/commands/commands.js +308 -0
- package/dist/config/config.d.ts +8 -0
- package/dist/config/config.js +311 -0
- package/dist/context/compaction.d.ts +54 -0
- package/dist/context/compaction.js +251 -0
- package/dist/context/custom-agents.d.ts +107 -0
- package/dist/context/custom-agents.js +397 -0
- package/dist/context/custom-tools.d.ts +120 -0
- package/dist/context/custom-tools.js +564 -0
- package/dist/context/git-checkpoint.d.ts +52 -0
- package/dist/context/git-checkpoint.js +240 -0
- package/dist/context/neuro-md.d.ts +48 -0
- package/dist/context/neuro-md.js +202 -0
- package/dist/context/neuroignore.d.ts +102 -0
- package/dist/context/neuroignore.js +441 -0
- package/dist/context/repo-map.d.ts +38 -0
- package/dist/context/repo-map.js +220 -0
- package/dist/context/skill-standard.d.ts +262 -0
- package/dist/context/skill-standard.js +1156 -0
- package/dist/context/skill-system.d.ts +75 -0
- package/dist/context/skill-system.js +578 -0
- package/dist/context/tree-sitter.d.ts +99 -0
- package/dist/context/tree-sitter.js +1956 -0
- package/dist/core/acp.d.ts +325 -0
- package/dist/core/acp.js +1498 -0
- package/dist/core/api-server.d.ts +143 -0
- package/dist/core/api-server.js +550 -0
- package/dist/core/approval.d.ts +81 -0
- package/dist/core/approval.js +432 -0
- package/dist/core/auto-compact.d.ts +127 -0
- package/dist/core/auto-compact.js +436 -0
- package/dist/core/auto-mode.d.ts +232 -0
- package/dist/core/auto-mode.js +831 -0
- package/dist/core/background-session.d.ts +166 -0
- package/dist/core/background-session.js +696 -0
- package/dist/core/cicd.d.ts +257 -0
- package/dist/core/cicd.js +1443 -0
- package/dist/core/cloud-sync.d.ts +156 -0
- package/dist/core/cloud-sync.js +582 -0
- package/dist/core/code-review.d.ts +132 -0
- package/dist/core/code-review.js +1191 -0
- package/dist/core/completion.d.ts +49 -0
- package/dist/core/completion.js +384 -0
- package/dist/core/context.d.ts +38 -0
- package/dist/core/context.js +144 -0
- package/dist/core/diff-preview.d.ts +35 -0
- package/dist/core/diff-preview.js +173 -0
- package/dist/core/doom-loop.d.ts +51 -0
- package/dist/core/doom-loop.js +179 -0
- package/dist/core/engine.d.ts +183 -0
- package/dist/core/engine.js +942 -0
- package/dist/core/extended-thinking.d.ts +103 -0
- package/dist/core/extended-thinking.js +269 -0
- package/dist/core/fallback.d.ts +54 -0
- package/dist/core/fallback.js +104 -0
- package/dist/core/git-worktree.d.ts +139 -0
- package/dist/core/git-worktree.js +614 -0
- package/dist/core/headless.d.ts +30 -0
- package/dist/core/headless.js +101 -0
- package/dist/core/i18n.d.ts +83 -0
- package/dist/core/i18n.js +739 -0
- package/dist/core/linting.d.ts +129 -0
- package/dist/core/linting.js +699 -0
- package/dist/core/model-router.d.ts +109 -0
- package/dist/core/model-router.js +581 -0
- package/dist/core/multi-model.d.ts +243 -0
- package/dist/core/multi-model.js +1099 -0
- package/dist/core/multi-session.d.ts +144 -0
- package/dist/core/multi-session.js +442 -0
- package/dist/core/multimodal.d.ts +125 -0
- package/dist/core/multimodal.js +286 -0
- package/dist/core/observability.d.ts +93 -0
- package/dist/core/observability.js +737 -0
- package/dist/core/os-sandbox.d.ts +122 -0
- package/dist/core/os-sandbox.js +1193 -0
- package/dist/core/outcome-grading.d.ts +228 -0
- package/dist/core/outcome-grading.js +1123 -0
- package/dist/core/output-styles.d.ts +57 -0
- package/dist/core/output-styles.js +382 -0
- package/dist/core/parallel-agents.d.ts +183 -0
- package/dist/core/parallel-agents.js +563 -0
- package/dist/core/plugin-bundle.d.ts +236 -0
- package/dist/core/plugin-bundle.js +887 -0
- package/dist/core/plugin-sdk.d.ts +139 -0
- package/dist/core/plugin-sdk.js +273 -0
- package/dist/core/prompt-cache.d.ts +163 -0
- package/dist/core/prompt-cache.js +599 -0
- package/dist/core/sandbox.d.ts +127 -0
- package/dist/core/sandbox.js +369 -0
- package/dist/core/scheduled-tasks.d.ts +151 -0
- package/dist/core/scheduled-tasks.js +502 -0
- package/dist/core/security-scanner.d.ts +160 -0
- package/dist/core/security-scanner.js +1494 -0
- package/dist/core/session.d.ts +83 -0
- package/dist/core/session.js +269 -0
- package/dist/core/shell-completion.d.ts +51 -0
- package/dist/core/shell-completion.js +674 -0
- package/dist/core/smart-monitor.d.ts +146 -0
- package/dist/core/smart-monitor.js +1199 -0
- package/dist/core/spec-driven.d.ts +233 -0
- package/dist/core/spec-driven.js +1485 -0
- package/dist/core/spending-warnings.d.ts +123 -0
- package/dist/core/spending-warnings.js +456 -0
- package/dist/core/sub-agent.d.ts +298 -0
- package/dist/core/sub-agent.js +1023 -0
- package/dist/core/telemetry.d.ts +157 -0
- package/dist/core/telemetry.js +412 -0
- package/dist/core/terminal-ux.d.ts +134 -0
- package/dist/core/terminal-ux.js +649 -0
- package/dist/core/testing.d.ts +146 -0
- package/dist/core/testing.js +1200 -0
- package/dist/core/types.d.ts +439 -0
- package/dist/core/types.js +6 -0
- package/dist/core/undo-redo.d.ts +112 -0
- package/dist/core/undo-redo.js +290 -0
- package/dist/core/updater.d.ts +159 -0
- package/dist/core/updater.js +608 -0
- package/dist/core/vim-mode.d.ts +151 -0
- package/dist/core/vim-mode.js +771 -0
- package/dist/core/voice.d.ts +137 -0
- package/dist/core/voice.js +538 -0
- package/dist/core/web-dashboard.d.ts +109 -0
- package/dist/core/web-dashboard.js +484 -0
- package/dist/hooks/hooks.d.ts +74 -0
- package/dist/hooks/hooks.js +160 -0
- package/dist/hooks/llm-evaluator.d.ts +165 -0
- package/dist/hooks/llm-evaluator.js +560 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +1186 -0
- package/dist/lsp/lsp-manager.d.ts +63 -0
- package/dist/lsp/lsp-manager.js +351 -0
- package/dist/mcp/client.d.ts +133 -0
- package/dist/mcp/client.js +684 -0
- package/dist/mcp/mcp-apps.d.ts +70 -0
- package/dist/mcp/mcp-apps.js +1007 -0
- package/dist/tools/bash.d.ts +5 -0
- package/dist/tools/bash.js +195 -0
- package/dist/tools/browser.d.ts +92 -0
- package/dist/tools/browser.js +1570 -0
- package/dist/tools/extended.d.ts +6 -0
- package/dist/tools/extended.js +191 -0
- package/dist/tools/file.d.ts +10 -0
- package/dist/tools/file.js +382 -0
- package/dist/tools/github.d.ts +389 -0
- package/dist/tools/github.js +759 -0
- package/dist/tools/index.d.ts +9 -0
- package/dist/tools/index.js +40 -0
- package/dist/tools/memory.d.ts +6 -0
- package/dist/tools/memory.js +197 -0
- package/dist/tools/registry.d.ts +29 -0
- package/dist/tools/registry.js +64 -0
- package/dist/tools/web.d.ts +6 -0
- package/dist/tools/web.js +150 -0
- package/dist/ui/renderer.d.ts +97 -0
- package/dist/ui/renderer.js +279 -0
- package/dist/ui/theme.d.ts +27 -0
- package/dist/ui/theme.js +106 -0
- package/package.json +83 -0
|
@@ -0,0 +1,436 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// NeuroCLI - GAP-37: Auto-Compact with Model-Aware Context Management
|
|
3
|
+
// Automatic context compaction that adapts to model context windows
|
|
4
|
+
// ============================================================
|
|
5
|
+
import { MODELS } from '../api/models.js';
|
|
6
|
+
import { ContextManager } from './context.js';
|
|
7
|
+
// ---- Token Estimation ----
|
|
8
|
+
const CJK_RANGES = /[\u4e00-\u9fff\u3040-\u309f\u30a0-\u30ff\uac00-\ud7af]/g;
|
|
9
|
+
function estimateTokensForText(text) {
|
|
10
|
+
const cjkMatches = text.match(CJK_RANGES);
|
|
11
|
+
const cjkCount = cjkMatches ? cjkMatches.length : 0;
|
|
12
|
+
const otherCount = text.length - cjkCount;
|
|
13
|
+
return Math.ceil(cjkCount / 2 + otherCount / 4);
|
|
14
|
+
}
|
|
15
|
+
// ---- Default Config ----
|
|
16
|
+
export const DEFAULT_AUTO_COMPACT_CONFIG = {
|
|
17
|
+
enabled: true,
|
|
18
|
+
warningThreshold: 0.7,
|
|
19
|
+
compactThreshold: 0.85,
|
|
20
|
+
emergencyThreshold: 0.95,
|
|
21
|
+
preserveRecentCount: 6,
|
|
22
|
+
preserveSystemPrompt: true,
|
|
23
|
+
compactStrategy: 'hybrid',
|
|
24
|
+
tokenBudget: {
|
|
25
|
+
systemPrompt: 0.15, // 15% of context window
|
|
26
|
+
conversation: 0.45, // 45% of context window
|
|
27
|
+
tools: 0.10, // 10% of context window
|
|
28
|
+
output: 0.30, // 30% of context window (reserved for output)
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
// ---- Fallback Context Window ----
|
|
32
|
+
const DEFAULT_CONTEXT_WINDOW = 128000;
|
|
33
|
+
export class AutoCompactManager {
|
|
34
|
+
config;
|
|
35
|
+
currentModelId;
|
|
36
|
+
contextManager = null;
|
|
37
|
+
compactor = null;
|
|
38
|
+
// Callbacks
|
|
39
|
+
warningCallbacks = [];
|
|
40
|
+
compactCallbacks = [];
|
|
41
|
+
emergencyCallbacks = [];
|
|
42
|
+
// Track last level to avoid duplicate callbacks
|
|
43
|
+
lastLevel = 'normal';
|
|
44
|
+
constructor(config = {}) {
|
|
45
|
+
this.config = { ...DEFAULT_AUTO_COMPACT_CONFIG, ...config };
|
|
46
|
+
// Deep merge tokenBudget
|
|
47
|
+
if (config.tokenBudget) {
|
|
48
|
+
this.config.tokenBudget = {
|
|
49
|
+
...DEFAULT_AUTO_COMPACT_CONFIG.tokenBudget,
|
|
50
|
+
...config.tokenBudget,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
this.currentModelId = '';
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Update the current model and recalculate model-aware thresholds.
|
|
57
|
+
* Rebuilds ContextManager and ContextCompactor for the new model.
|
|
58
|
+
*/
|
|
59
|
+
setModel(modelId) {
|
|
60
|
+
this.currentModelId = modelId;
|
|
61
|
+
const model = MODELS[modelId];
|
|
62
|
+
const contextWindow = model?.contextWindow ?? DEFAULT_CONTEXT_WINDOW;
|
|
63
|
+
this.contextManager = new ContextManager(modelId, contextWindow);
|
|
64
|
+
// ContextCompactor requires a ContextManager and an OpenRouterClient.
|
|
65
|
+
// We keep a null reference because we implement our own compaction logic
|
|
66
|
+
// that delegates to the ContextManager's token counting and summarization,
|
|
67
|
+
// falling back to the compactor only when available.
|
|
68
|
+
this.compactor = null;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Set the ContextCompactor for LLM-backed summarization.
|
|
72
|
+
* This is optional — without it, auto-compact uses local strategies only.
|
|
73
|
+
*/
|
|
74
|
+
setCompactor(compactor) {
|
|
75
|
+
this.compactor = compactor;
|
|
76
|
+
}
|
|
77
|
+
// ---- Monitoring ----
|
|
78
|
+
/**
|
|
79
|
+
* Compute detailed context usage breakdown for the given messages and model.
|
|
80
|
+
*/
|
|
81
|
+
getContextUsage(messages, modelId) {
|
|
82
|
+
const model = MODELS[modelId];
|
|
83
|
+
const maxTokens = model?.contextWindow ?? DEFAULT_CONTEXT_WINDOW;
|
|
84
|
+
// Calculate token counts per category
|
|
85
|
+
let systemPromptTokens = 0;
|
|
86
|
+
let conversationTokens = 0;
|
|
87
|
+
let toolsTokens = 0;
|
|
88
|
+
for (const msg of messages) {
|
|
89
|
+
const msgTokens = this.estimateMessageTokens(msg);
|
|
90
|
+
if (msg.role === 'system') {
|
|
91
|
+
systemPromptTokens += msgTokens;
|
|
92
|
+
}
|
|
93
|
+
else if (msg.role === 'tool') {
|
|
94
|
+
toolsTokens += msgTokens;
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
conversationTokens += msgTokens;
|
|
98
|
+
}
|
|
99
|
+
// Tool calls on assistant messages count toward tools budget
|
|
100
|
+
if (msg.toolCalls) {
|
|
101
|
+
for (const tc of msg.toolCalls) {
|
|
102
|
+
toolsTokens += estimateTokensForText(tc.function.name + tc.function.arguments);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
const totalTokens = systemPromptTokens + conversationTokens + toolsTokens;
|
|
107
|
+
const outputReserve = Math.floor(maxTokens * this.config.tokenBudget.output);
|
|
108
|
+
const availableTokens = Math.max(0, maxTokens - totalTokens - outputReserve);
|
|
109
|
+
const usagePercent = totalTokens / maxTokens;
|
|
110
|
+
let level = 'normal';
|
|
111
|
+
if (usagePercent >= this.config.emergencyThreshold) {
|
|
112
|
+
level = 'emergency';
|
|
113
|
+
}
|
|
114
|
+
else if (usagePercent >= this.config.compactThreshold) {
|
|
115
|
+
level = 'compact';
|
|
116
|
+
}
|
|
117
|
+
else if (usagePercent >= this.config.warningThreshold) {
|
|
118
|
+
level = 'warning';
|
|
119
|
+
}
|
|
120
|
+
return {
|
|
121
|
+
totalTokens,
|
|
122
|
+
maxTokens,
|
|
123
|
+
usagePercent,
|
|
124
|
+
breakdown: {
|
|
125
|
+
systemPrompt: systemPromptTokens,
|
|
126
|
+
conversation: conversationTokens,
|
|
127
|
+
tools: toolsTokens,
|
|
128
|
+
available: availableTokens,
|
|
129
|
+
},
|
|
130
|
+
level,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Determine whether compaction is needed and at what level.
|
|
135
|
+
* Fires callbacks when the level transitions.
|
|
136
|
+
*/
|
|
137
|
+
shouldCompact(messages, modelId) {
|
|
138
|
+
const usage = this.getContextUsage(messages, modelId);
|
|
139
|
+
const needed = usage.level === 'compact' || usage.level === 'emergency';
|
|
140
|
+
// Fire callbacks on level transitions
|
|
141
|
+
if (usage.level !== this.lastLevel) {
|
|
142
|
+
if (usage.level === 'warning') {
|
|
143
|
+
for (const cb of this.warningCallbacks) {
|
|
144
|
+
cb(usage);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
else if (usage.level === 'compact') {
|
|
148
|
+
// Also fire warning callbacks at compact level if transitioning from normal
|
|
149
|
+
if (this.lastLevel === 'normal') {
|
|
150
|
+
for (const cb of this.warningCallbacks) {
|
|
151
|
+
cb(usage);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
else if (usage.level === 'emergency') {
|
|
156
|
+
for (const cb of this.emergencyCallbacks) {
|
|
157
|
+
cb(usage);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
this.lastLevel = usage.level;
|
|
161
|
+
}
|
|
162
|
+
return { needed, level: usage.level };
|
|
163
|
+
}
|
|
164
|
+
// ---- Compaction ----
|
|
165
|
+
/**
|
|
166
|
+
* Compact messages according to the configured or specified strategy.
|
|
167
|
+
* Returns the compacted message list along with usage metrics.
|
|
168
|
+
*/
|
|
169
|
+
async compact(messages, strategy) {
|
|
170
|
+
if (!this.config.enabled) {
|
|
171
|
+
return messages;
|
|
172
|
+
}
|
|
173
|
+
const effectiveModelId = this.currentModelId || 'openrouter/free';
|
|
174
|
+
const usage = this.getContextUsage(messages, effectiveModelId);
|
|
175
|
+
const effectiveStrategy = strategy ?? this.config.compactStrategy;
|
|
176
|
+
let result;
|
|
177
|
+
switch (effectiveStrategy) {
|
|
178
|
+
case 'summarize':
|
|
179
|
+
result = await this.compactBySummarize(messages, usage);
|
|
180
|
+
break;
|
|
181
|
+
case 'drop-oldest':
|
|
182
|
+
result = this.compactByDropOldest(messages, usage);
|
|
183
|
+
break;
|
|
184
|
+
case 'hybrid':
|
|
185
|
+
result = await this.compactByHybrid(messages, usage);
|
|
186
|
+
break;
|
|
187
|
+
default:
|
|
188
|
+
result = this.compactByDropOldest(messages, usage);
|
|
189
|
+
}
|
|
190
|
+
// Fire compact callbacks
|
|
191
|
+
const newUsage = this.getContextUsage(result, effectiveModelId);
|
|
192
|
+
for (const cb of this.compactCallbacks) {
|
|
193
|
+
cb(newUsage, result);
|
|
194
|
+
}
|
|
195
|
+
// Update tracked level
|
|
196
|
+
this.lastLevel = newUsage.level;
|
|
197
|
+
return result;
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Strategy: Summarize older messages into a single context message.
|
|
201
|
+
* Keeps last N messages verbatim, summarizes everything else.
|
|
202
|
+
*/
|
|
203
|
+
async compactBySummarize(messages, usage) {
|
|
204
|
+
const model = MODELS[this.currentModelId];
|
|
205
|
+
const maxTokens = model?.contextWindow ?? DEFAULT_CONTEXT_WINDOW;
|
|
206
|
+
const targetTokens = Math.floor(maxTokens * this.config.compactThreshold * 0.75); // compact to 75% of threshold
|
|
207
|
+
const { systemMessages, nonSystemMessages } = this.partitionMessages(messages);
|
|
208
|
+
if (nonSystemMessages.length <= this.config.preserveRecentCount) {
|
|
209
|
+
// Nothing to summarize
|
|
210
|
+
return messages;
|
|
211
|
+
}
|
|
212
|
+
// Split into "old" and "recent"
|
|
213
|
+
const preserveCount = this.config.preserveRecentCount;
|
|
214
|
+
const recentMessages = nonSystemMessages.slice(-preserveCount);
|
|
215
|
+
const oldMessages = nonSystemMessages.slice(0, -preserveCount);
|
|
216
|
+
// Build summary of old messages
|
|
217
|
+
const summaryContent = this.buildSummary(oldMessages);
|
|
218
|
+
const summaryMessage = {
|
|
219
|
+
role: 'system',
|
|
220
|
+
content: `## Earlier Conversation Summary\n${summaryContent}`,
|
|
221
|
+
timestamp: Date.now(),
|
|
222
|
+
};
|
|
223
|
+
let result = [...systemMessages, summaryMessage, ...recentMessages];
|
|
224
|
+
// If we still exceed the target, try LLM-backed compaction
|
|
225
|
+
const resultUsage = this.getContextUsage(result, this.currentModelId);
|
|
226
|
+
if (resultUsage.usagePercent >= this.config.compactThreshold && this.compactor) {
|
|
227
|
+
try {
|
|
228
|
+
const compactionResult = await this.compactor.compact(result);
|
|
229
|
+
result = compactionResult.messages;
|
|
230
|
+
}
|
|
231
|
+
catch {
|
|
232
|
+
// Fall through — keep local summary result
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
// If still over budget, progressively drop oldest from the old messages section
|
|
236
|
+
if (this.getContextUsage(result, this.currentModelId).usagePercent >= this.config.emergencyThreshold) {
|
|
237
|
+
result = this.emergencyDrop(result);
|
|
238
|
+
}
|
|
239
|
+
return result;
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Strategy: Drop oldest messages (except system prompt and recent).
|
|
243
|
+
*/
|
|
244
|
+
compactByDropOldest(messages, usage) {
|
|
245
|
+
const model = MODELS[this.currentModelId];
|
|
246
|
+
const maxTokens = model?.contextWindow ?? DEFAULT_CONTEXT_WINDOW;
|
|
247
|
+
const targetTokens = Math.floor(maxTokens * this.config.compactThreshold * 0.75);
|
|
248
|
+
const { systemMessages, nonSystemMessages } = this.partitionMessages(messages);
|
|
249
|
+
if (nonSystemMessages.length <= this.config.preserveRecentCount) {
|
|
250
|
+
return messages;
|
|
251
|
+
}
|
|
252
|
+
// Start by keeping system + recent; progressively add older messages from newest to oldest
|
|
253
|
+
const preserveCount = this.config.preserveRecentCount;
|
|
254
|
+
const recentMessages = nonSystemMessages.slice(-preserveCount);
|
|
255
|
+
const olderMessages = nonSystemMessages.slice(0, -preserveCount);
|
|
256
|
+
// Work backwards through older messages, adding as many as we can afford
|
|
257
|
+
const keptOlder = [];
|
|
258
|
+
let currentTokens = this.estimateMessagesTokens([...systemMessages, ...recentMessages]);
|
|
259
|
+
for (let i = olderMessages.length - 1; i >= 0; i--) {
|
|
260
|
+
const msgTokens = this.estimateMessageTokens(olderMessages[i]);
|
|
261
|
+
if (currentTokens + msgTokens <= targetTokens) {
|
|
262
|
+
keptOlder.unshift(olderMessages[i]);
|
|
263
|
+
currentTokens += msgTokens;
|
|
264
|
+
}
|
|
265
|
+
else {
|
|
266
|
+
break;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
// If we dropped some messages, add a brief context note
|
|
270
|
+
const droppedCount = olderMessages.length - keptOlder.length;
|
|
271
|
+
let result;
|
|
272
|
+
if (droppedCount > 0) {
|
|
273
|
+
const contextNote = {
|
|
274
|
+
role: 'system',
|
|
275
|
+
content: `## Context Note\n${droppedCount} earlier message(s) were dropped to fit the context window. Key details may have been lost.`,
|
|
276
|
+
timestamp: Date.now(),
|
|
277
|
+
};
|
|
278
|
+
result = [...systemMessages, contextNote, ...keptOlder, ...recentMessages];
|
|
279
|
+
}
|
|
280
|
+
else {
|
|
281
|
+
result = [...systemMessages, ...keptOlder, ...recentMessages];
|
|
282
|
+
}
|
|
283
|
+
return result;
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Strategy: Hybrid — summarize first, then drop oldest if still over budget.
|
|
287
|
+
*/
|
|
288
|
+
async compactByHybrid(messages, usage) {
|
|
289
|
+
// First pass: summarize
|
|
290
|
+
const summarized = await this.compactBySummarize(messages, usage);
|
|
291
|
+
// Check if still over budget
|
|
292
|
+
const postUsage = this.getContextUsage(summarized, this.currentModelId);
|
|
293
|
+
if (postUsage.usagePercent < this.config.compactThreshold) {
|
|
294
|
+
return summarized;
|
|
295
|
+
}
|
|
296
|
+
// Second pass: drop oldest from the summarized result
|
|
297
|
+
return this.compactByDropOldest(summarized, postUsage);
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Emergency compaction: keep only system prompt + last 3 exchanges (6 messages).
|
|
301
|
+
* This is the last resort when everything else fails.
|
|
302
|
+
*/
|
|
303
|
+
emergencyDrop(messages) {
|
|
304
|
+
const { systemMessages, nonSystemMessages } = this.partitionMessages(messages);
|
|
305
|
+
// Keep exactly the last 3 exchanges (6 messages: user/assistant/tool pairs)
|
|
306
|
+
const emergencyPreserveCount = 6;
|
|
307
|
+
const recentMessages = nonSystemMessages.slice(-emergencyPreserveCount);
|
|
308
|
+
const emergencyNote = {
|
|
309
|
+
role: 'system',
|
|
310
|
+
content: '## Emergency Compaction\nContext was critically over budget. Older messages were dropped. Only the most recent exchanges are preserved.',
|
|
311
|
+
timestamp: Date.now(),
|
|
312
|
+
};
|
|
313
|
+
return [...systemMessages, emergencyNote, ...recentMessages];
|
|
314
|
+
}
|
|
315
|
+
// ---- Token Estimation ----
|
|
316
|
+
/**
|
|
317
|
+
* Estimate the number of tokens in a text string for a given model.
|
|
318
|
+
* Uses ~4 chars/token for English, ~2 chars/token for CJK.
|
|
319
|
+
*/
|
|
320
|
+
estimateTokens(text, _modelId) {
|
|
321
|
+
return estimateTokensForText(text);
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* Estimate total tokens across an array of messages,
|
|
325
|
+
* including tool calls.
|
|
326
|
+
*/
|
|
327
|
+
estimateMessagesTokens(messages) {
|
|
328
|
+
return messages.reduce((sum, msg) => sum + this.estimateMessageTokens(msg), 0);
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* Estimate tokens for a single message including tool calls.
|
|
332
|
+
*/
|
|
333
|
+
estimateMessageTokens(msg) {
|
|
334
|
+
let tokens = estimateTokensForText(msg.content);
|
|
335
|
+
if (msg.toolCalls) {
|
|
336
|
+
for (const tc of msg.toolCalls) {
|
|
337
|
+
tokens += estimateTokensForText(tc.function.name + tc.function.arguments);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
// Overhead per message: role label, formatting, etc. (~4 tokens)
|
|
341
|
+
tokens += 4;
|
|
342
|
+
return tokens;
|
|
343
|
+
}
|
|
344
|
+
// ---- Callbacks ----
|
|
345
|
+
onWarning(callback) {
|
|
346
|
+
this.warningCallbacks.push(callback);
|
|
347
|
+
}
|
|
348
|
+
onCompact(callback) {
|
|
349
|
+
this.compactCallbacks.push(callback);
|
|
350
|
+
}
|
|
351
|
+
onEmergency(callback) {
|
|
352
|
+
this.emergencyCallbacks.push(callback);
|
|
353
|
+
}
|
|
354
|
+
// ---- Helpers ----
|
|
355
|
+
/**
|
|
356
|
+
* Partition messages into system and non-system buckets.
|
|
357
|
+
*/
|
|
358
|
+
partitionMessages(messages) {
|
|
359
|
+
const systemMessages = [];
|
|
360
|
+
const nonSystemMessages = [];
|
|
361
|
+
for (const msg of messages) {
|
|
362
|
+
if (msg.role === 'system') {
|
|
363
|
+
systemMessages.push(msg);
|
|
364
|
+
}
|
|
365
|
+
else {
|
|
366
|
+
nonSystemMessages.push(msg);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
return { systemMessages, nonSystemMessages };
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* Build a concise summary from a list of messages.
|
|
373
|
+
* Prioritizes user intent, tool interactions, and assistant decisions.
|
|
374
|
+
*/
|
|
375
|
+
buildSummary(messages) {
|
|
376
|
+
const parts = [];
|
|
377
|
+
for (const msg of messages) {
|
|
378
|
+
const preview = msg.content.length > 150
|
|
379
|
+
? msg.content.slice(0, 150) + '...'
|
|
380
|
+
: msg.content;
|
|
381
|
+
switch (msg.role) {
|
|
382
|
+
case 'user':
|
|
383
|
+
parts.push(`[User]: ${preview}`);
|
|
384
|
+
break;
|
|
385
|
+
case 'assistant':
|
|
386
|
+
if (msg.toolCalls?.length) {
|
|
387
|
+
const toolNames = msg.toolCalls.map(tc => tc.function.name).join(', ');
|
|
388
|
+
parts.push(`[Assistant called: ${toolNames}] ${preview}`);
|
|
389
|
+
}
|
|
390
|
+
else {
|
|
391
|
+
parts.push(`[Assistant]: ${preview}`);
|
|
392
|
+
}
|
|
393
|
+
break;
|
|
394
|
+
case 'tool':
|
|
395
|
+
parts.push(`[Tool result]: ${preview.slice(0, 80)}`);
|
|
396
|
+
break;
|
|
397
|
+
default:
|
|
398
|
+
break;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
return parts.join('\n');
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
// ---- Convenience: Auto-Compact Middleware ----
|
|
405
|
+
/**
|
|
406
|
+
* Middleware function that can be called before each LLM call.
|
|
407
|
+
* Checks context usage, auto-compacts if needed, and returns
|
|
408
|
+
* the (possibly compacted) message list.
|
|
409
|
+
*/
|
|
410
|
+
export async function autoCompactMiddleware(manager, messages, modelId) {
|
|
411
|
+
if (!manager['config'].enabled) {
|
|
412
|
+
return messages;
|
|
413
|
+
}
|
|
414
|
+
manager.setModel(modelId);
|
|
415
|
+
const { needed, level } = manager.shouldCompact(messages, modelId);
|
|
416
|
+
if (!needed) {
|
|
417
|
+
return messages;
|
|
418
|
+
}
|
|
419
|
+
// Choose strategy based on severity
|
|
420
|
+
let strategy;
|
|
421
|
+
if (level === 'emergency') {
|
|
422
|
+
strategy = 'drop-oldest'; // Fastest, most aggressive
|
|
423
|
+
}
|
|
424
|
+
else if (level === 'compact') {
|
|
425
|
+
strategy = manager['config'].compactStrategy;
|
|
426
|
+
}
|
|
427
|
+
else {
|
|
428
|
+
return messages;
|
|
429
|
+
}
|
|
430
|
+
return manager.compact(messages, strategy);
|
|
431
|
+
}
|
|
432
|
+
// ---- Convenience: Create a pre-configured AutoCompactManager ----
|
|
433
|
+
export function createAutoCompactManager(overrides) {
|
|
434
|
+
return new AutoCompactManager(overrides);
|
|
435
|
+
}
|
|
436
|
+
//# sourceMappingURL=auto-compact.js.map
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
export type SafetyLevel = 'conservative' | 'moderate' | 'aggressive';
|
|
2
|
+
export interface AutoModeConfig {
|
|
3
|
+
/** Whether auto mode is currently enabled */
|
|
4
|
+
enabled: boolean;
|
|
5
|
+
/** Safety level controlling what operations are permitted */
|
|
6
|
+
safetyLevel: SafetyLevel;
|
|
7
|
+
/** Maximum iterations before auto mode stops (default 50) */
|
|
8
|
+
maxIterations: number;
|
|
9
|
+
/** Maximum spend in USD – 0 = unlimited */
|
|
10
|
+
maxCost: number;
|
|
11
|
+
/** Maximum execution time in ms – 0 = unlimited */
|
|
12
|
+
maxTimeMs: number;
|
|
13
|
+
/** Commands that are ALWAYS blocked, even in auto mode */
|
|
14
|
+
blockedCommands: string[];
|
|
15
|
+
/** File glob patterns that can never be modified in auto mode */
|
|
16
|
+
blockedPatterns: string[];
|
|
17
|
+
/** Auto git commit after each change */
|
|
18
|
+
autoCommit: boolean;
|
|
19
|
+
/** Auto run tests after each change */
|
|
20
|
+
autoTest: boolean;
|
|
21
|
+
/** Pause auto mode when an error is encountered */
|
|
22
|
+
pauseOnError: boolean;
|
|
23
|
+
}
|
|
24
|
+
export interface AutoModeStats {
|
|
25
|
+
/** Number of iterations completed */
|
|
26
|
+
iterations: number;
|
|
27
|
+
/** Number of files changed (created, modified, or deleted) */
|
|
28
|
+
filesChanged: number;
|
|
29
|
+
/** Number of commands executed */
|
|
30
|
+
commandsRun: number;
|
|
31
|
+
/** Total elapsed time in ms */
|
|
32
|
+
timeElapsedMs: number;
|
|
33
|
+
/** Total cost accrued in USD */
|
|
34
|
+
totalCost: number;
|
|
35
|
+
/** Number of operations blocked by safety */
|
|
36
|
+
blockedOperations: number;
|
|
37
|
+
/** Number of errors encountered */
|
|
38
|
+
errors: number;
|
|
39
|
+
}
|
|
40
|
+
export interface GoalDefinition {
|
|
41
|
+
/** Unique identifier */
|
|
42
|
+
id: string;
|
|
43
|
+
/** Short human-readable name */
|
|
44
|
+
name: string;
|
|
45
|
+
/** High-level goal description */
|
|
46
|
+
description: string;
|
|
47
|
+
/** Optional success criteria – when all pass, the goal is considered complete */
|
|
48
|
+
successCriteria: string[];
|
|
49
|
+
/** Optional list of sub-goals */
|
|
50
|
+
subGoals: GoalDefinition[];
|
|
51
|
+
/** ISO timestamp when the goal was created */
|
|
52
|
+
createdAt: string;
|
|
53
|
+
/** ISO timestamp when the goal was completed (if applicable) */
|
|
54
|
+
completedAt?: string;
|
|
55
|
+
/** Current status */
|
|
56
|
+
status: 'pending' | 'in_progress' | 'completed' | 'failed' | 'cancelled';
|
|
57
|
+
/** Progress from 0 to 1 */
|
|
58
|
+
progress: number;
|
|
59
|
+
}
|
|
60
|
+
export interface RoutineStep {
|
|
61
|
+
/** Optional label for the step */
|
|
62
|
+
label?: string;
|
|
63
|
+
/** The prompt or command to execute */
|
|
64
|
+
prompt: string;
|
|
65
|
+
/** Optional model override for this step */
|
|
66
|
+
model?: string;
|
|
67
|
+
/** Whether to pause between this step and the next */
|
|
68
|
+
pauseAfter?: boolean;
|
|
69
|
+
/** Maximum iterations allowed for this step */
|
|
70
|
+
maxIterations?: number;
|
|
71
|
+
}
|
|
72
|
+
export interface RoutineDefinition {
|
|
73
|
+
/** Unique identifier */
|
|
74
|
+
id: string;
|
|
75
|
+
/** Short human-readable name */
|
|
76
|
+
name: string;
|
|
77
|
+
/** Description of what the routine accomplishes */
|
|
78
|
+
description: string;
|
|
79
|
+
/** Ordered list of steps */
|
|
80
|
+
steps: RoutineStep[];
|
|
81
|
+
/** ISO timestamp when the routine was created */
|
|
82
|
+
createdAt: string;
|
|
83
|
+
/** ISO timestamp of last run */
|
|
84
|
+
lastRunAt?: string;
|
|
85
|
+
/** Number of times this routine has been run */
|
|
86
|
+
runCount: number;
|
|
87
|
+
/** Tags for categorisation */
|
|
88
|
+
tags: string[];
|
|
89
|
+
}
|
|
90
|
+
export interface AutoModeCheckpoint {
|
|
91
|
+
id: string;
|
|
92
|
+
timestamp: string;
|
|
93
|
+
iteration: number;
|
|
94
|
+
goalId?: string;
|
|
95
|
+
routineId?: string;
|
|
96
|
+
snapshot: string;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Minimal interface that any execution engine must satisfy for AutoMode
|
|
100
|
+
* to orchestrate it. This avoids a hard import of NeuroEngine while
|
|
101
|
+
* keeping the two modules loosely coupled.
|
|
102
|
+
*/
|
|
103
|
+
export interface AutoModeEngine {
|
|
104
|
+
/** Run a single prompt through the engine and return the assistant text */
|
|
105
|
+
runPrompt(prompt: string, model?: string): Promise<{
|
|
106
|
+
text: string;
|
|
107
|
+
inputTokens: number;
|
|
108
|
+
outputTokens: number;
|
|
109
|
+
cost: number;
|
|
110
|
+
filesChanged: number;
|
|
111
|
+
commandsRun: number;
|
|
112
|
+
error?: string;
|
|
113
|
+
}>;
|
|
114
|
+
}
|
|
115
|
+
export declare class AutoMode {
|
|
116
|
+
private config;
|
|
117
|
+
private stats;
|
|
118
|
+
private goals;
|
|
119
|
+
private routines;
|
|
120
|
+
private checkpoints;
|
|
121
|
+
private startTime;
|
|
122
|
+
private running;
|
|
123
|
+
private abortController;
|
|
124
|
+
private onStatusChange?;
|
|
125
|
+
constructor(config?: Partial<AutoModeConfig>, onStatusChange?: (status: AutoModeStatus) => void);
|
|
126
|
+
/** Enable auto mode – skip all approval prompts, run safety checks in background */
|
|
127
|
+
enable(): void;
|
|
128
|
+
/** Disable auto mode, return to interactive */
|
|
129
|
+
disable(): void;
|
|
130
|
+
/** Check whether auto mode is currently enabled */
|
|
131
|
+
isEnabled(): boolean;
|
|
132
|
+
/** Check whether auto mode is currently running a task */
|
|
133
|
+
isRunning(): boolean;
|
|
134
|
+
getConfig(): Readonly<AutoModeConfig>;
|
|
135
|
+
updateConfig(updates: Partial<AutoModeConfig>): void;
|
|
136
|
+
setSafetyLevel(level: SafetyLevel): void;
|
|
137
|
+
/**
|
|
138
|
+
* Run a task fully autonomously.
|
|
139
|
+
*
|
|
140
|
+
* The engine will be called repeatedly until:
|
|
141
|
+
* - maxIterations is reached
|
|
142
|
+
* - maxCost is exceeded
|
|
143
|
+
* - maxTimeMs is exceeded
|
|
144
|
+
* - The task appears complete (engine signals no further action needed)
|
|
145
|
+
* - An error occurs and pauseOnError is true
|
|
146
|
+
* - The user aborts
|
|
147
|
+
*/
|
|
148
|
+
executeAuto(prompt: string, engine: AutoModeEngine, maxIterations?: number): Promise<AutoModeResult>;
|
|
149
|
+
/** Abort the currently running auto execution */
|
|
150
|
+
abort(): void;
|
|
151
|
+
/** Set a high-level goal and let the agent work towards it autonomously */
|
|
152
|
+
setGoal(name: string, description: string, successCriteria?: string[]): GoalDefinition;
|
|
153
|
+
/** Get a goal by ID */
|
|
154
|
+
getGoal(goalId: string): GoalDefinition | undefined;
|
|
155
|
+
/** List all goals */
|
|
156
|
+
listGoals(): GoalDefinition[];
|
|
157
|
+
/** Update goal progress */
|
|
158
|
+
updateGoalProgress(goalId: string, progress: number, status?: GoalDefinition['status']): void;
|
|
159
|
+
/** Add a sub-goal */
|
|
160
|
+
addSubGoal(parentId: string, name: string, description: string): GoalDefinition | null;
|
|
161
|
+
/** Delete a goal */
|
|
162
|
+
deleteGoal(goalId: string): boolean;
|
|
163
|
+
/**
|
|
164
|
+
* Execute a goal autonomously – will iterate until the goal is reached
|
|
165
|
+
* or limits are hit
|
|
166
|
+
*/
|
|
167
|
+
executeGoal(goalId: string, engine: AutoModeEngine): Promise<AutoModeResult>;
|
|
168
|
+
/** Create a new routine */
|
|
169
|
+
createRoutine(name: string, description: string, steps: RoutineStep[], tags?: string[]): RoutineDefinition;
|
|
170
|
+
/** Get a routine by ID */
|
|
171
|
+
getRoutine(routineId: string): RoutineDefinition | undefined;
|
|
172
|
+
/** List all routines, optionally filtered by tag */
|
|
173
|
+
listRoutines(tag?: string): RoutineDefinition[];
|
|
174
|
+
/** Delete a routine */
|
|
175
|
+
deleteRoutine(routineId: string): boolean;
|
|
176
|
+
/** Update a routine's steps */
|
|
177
|
+
updateRoutineSteps(routineId: string, steps: RoutineStep[]): boolean;
|
|
178
|
+
/** Replay a saved routine */
|
|
179
|
+
executeRoutine(routineId: string, engine: AutoModeEngine): Promise<AutoModeResult>;
|
|
180
|
+
/**
|
|
181
|
+
* Pre-flight safety check – examines the prompt for dangerous commands
|
|
182
|
+
* or patterns that should be blocked even in auto mode.
|
|
183
|
+
*/
|
|
184
|
+
preFlightSafetyCheck(prompt: string): {
|
|
185
|
+
allowed: boolean;
|
|
186
|
+
reason?: string;
|
|
187
|
+
};
|
|
188
|
+
/**
|
|
189
|
+
* Runtime safety check for a specific command about to be executed.
|
|
190
|
+
* This is meant to be called by the engine before running any shell command.
|
|
191
|
+
*/
|
|
192
|
+
isCommandAllowed(command: string): {
|
|
193
|
+
allowed: boolean;
|
|
194
|
+
reason?: string;
|
|
195
|
+
};
|
|
196
|
+
/**
|
|
197
|
+
* Runtime safety check for a file path about to be modified.
|
|
198
|
+
* This is meant to be called by the engine before any file write.
|
|
199
|
+
*/
|
|
200
|
+
isFileModificationAllowed(filePath: string): {
|
|
201
|
+
allowed: boolean;
|
|
202
|
+
reason?: string;
|
|
203
|
+
};
|
|
204
|
+
getStats(): Readonly<AutoModeStats>;
|
|
205
|
+
resetStats(): void;
|
|
206
|
+
listCheckpoints(): AutoModeCheckpoint[];
|
|
207
|
+
getLatestCheckpoint(): AutoModeCheckpoint | undefined;
|
|
208
|
+
/** Create a checkpoint of the current state */
|
|
209
|
+
createCheckpoint(iteration: number, goalId?: string, routineId?: string): AutoModeCheckpoint;
|
|
210
|
+
private freshStats;
|
|
211
|
+
private emitStatus;
|
|
212
|
+
private buildBlockedCommands;
|
|
213
|
+
private buildBlockedPatterns;
|
|
214
|
+
private detectCompletion;
|
|
215
|
+
private buildGoalPrompt;
|
|
216
|
+
private autoGitCommit;
|
|
217
|
+
private autoRunTests;
|
|
218
|
+
private matchesGlobPattern;
|
|
219
|
+
private persistState;
|
|
220
|
+
private loadState;
|
|
221
|
+
private persistGoals;
|
|
222
|
+
private persistRoutines;
|
|
223
|
+
}
|
|
224
|
+
export type AutoModeStatus = 'disabled' | 'enabled' | 'running';
|
|
225
|
+
export interface AutoModeResult {
|
|
226
|
+
ok: boolean;
|
|
227
|
+
iterations: number;
|
|
228
|
+
finalText: string;
|
|
229
|
+
completed?: boolean;
|
|
230
|
+
error?: string;
|
|
231
|
+
}
|
|
232
|
+
//# sourceMappingURL=auto-mode.d.ts.map
|