open-agents-ai 0.31.3 → 0.31.4
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/dist/index.js +393 -9
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -10990,6 +10990,7 @@ Rules:
|
|
|
10990
10990
|
_pauseResolve = null;
|
|
10991
10991
|
_sudoPassword = null;
|
|
10992
10992
|
_sudoResolve = null;
|
|
10993
|
+
_pendingCompaction = null;
|
|
10993
10994
|
constructor(backend, options) {
|
|
10994
10995
|
this.backend = backend;
|
|
10995
10996
|
this.options = {
|
|
@@ -11099,6 +11100,23 @@ Rules:
|
|
|
11099
11100
|
get isPaused() {
|
|
11100
11101
|
return this._paused;
|
|
11101
11102
|
}
|
|
11103
|
+
/**
|
|
11104
|
+
* Request manual context compaction at the next turn boundary.
|
|
11105
|
+
* The strategy controls how aggressively context is compressed:
|
|
11106
|
+
* - "default" — standard progressive summarization
|
|
11107
|
+
* - "aggressive" — maximum compression, keep only decisions + errors
|
|
11108
|
+
* - "decisions" — extract action→outcome pairs, discard exploration
|
|
11109
|
+
* - "errors" — preserve error context, compress successes
|
|
11110
|
+
* - "summary" — high-level paragraph, minimal detail
|
|
11111
|
+
*/
|
|
11112
|
+
requestCompaction(strategy = "default") {
|
|
11113
|
+
this._pendingCompaction = strategy;
|
|
11114
|
+
this.emit({
|
|
11115
|
+
type: "compaction",
|
|
11116
|
+
content: `Manual compaction requested (strategy: ${strategy})`,
|
|
11117
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
11118
|
+
});
|
|
11119
|
+
}
|
|
11102
11120
|
/**
|
|
11103
11121
|
* If paused, block until resume() or abort() is called.
|
|
11104
11122
|
* Returns true if the loop should continue, false if aborted while paused.
|
|
@@ -11246,7 +11264,14 @@ Integrate this guidance into your current approach. Continue working on the task
|
|
|
11246
11264
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
11247
11265
|
});
|
|
11248
11266
|
}
|
|
11249
|
-
|
|
11267
|
+
let compacted;
|
|
11268
|
+
if (this._pendingCompaction) {
|
|
11269
|
+
const strategy = this._pendingCompaction;
|
|
11270
|
+
this._pendingCompaction = null;
|
|
11271
|
+
compacted = this.compactMessages(messages, strategy, true);
|
|
11272
|
+
} else {
|
|
11273
|
+
compacted = this.compactMessages(messages);
|
|
11274
|
+
}
|
|
11250
11275
|
const { maxOutputTokens: effectiveMaxTokens } = this.contextLimits();
|
|
11251
11276
|
const chatRequest = {
|
|
11252
11277
|
messages: compacted,
|
|
@@ -11499,7 +11524,14 @@ Integrate this guidance into your current approach. Continue working on the task
|
|
|
11499
11524
|
}
|
|
11500
11525
|
this.emit({ type: "user_interrupt", content: userMsg.replace(/\[IMAGE_BASE64:[^\]]+\]/, "[image]").slice(0, 200), turn, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
11501
11526
|
}
|
|
11502
|
-
|
|
11527
|
+
let compactedMsgs;
|
|
11528
|
+
if (this._pendingCompaction) {
|
|
11529
|
+
const strategy = this._pendingCompaction;
|
|
11530
|
+
this._pendingCompaction = null;
|
|
11531
|
+
compactedMsgs = this.compactMessages(messages, strategy, true);
|
|
11532
|
+
} else {
|
|
11533
|
+
compactedMsgs = this.compactMessages(messages);
|
|
11534
|
+
}
|
|
11503
11535
|
const chatRequest = { messages: compactedMsgs, tools: toolDefs, temperature: this.options.temperature, maxTokens: this.options.maxTokens, timeoutMs: this.options.requestTimeoutMs };
|
|
11504
11536
|
let response;
|
|
11505
11537
|
try {
|
|
@@ -11683,7 +11715,7 @@ ${tail}`;
|
|
|
11683
11715
|
// -------------------------------------------------------------------------
|
|
11684
11716
|
// Context compaction
|
|
11685
11717
|
// -------------------------------------------------------------------------
|
|
11686
|
-
compactMessages(messages) {
|
|
11718
|
+
compactMessages(messages, strategy = "default", force = false) {
|
|
11687
11719
|
if (messages.length < 3)
|
|
11688
11720
|
return messages;
|
|
11689
11721
|
const totalChars = messages.reduce((sum, m) => {
|
|
@@ -11696,10 +11728,15 @@ ${tail}`;
|
|
|
11696
11728
|
}, 0);
|
|
11697
11729
|
const estimatedTokens = totalChars / 4;
|
|
11698
11730
|
const limits = this.contextLimits();
|
|
11699
|
-
if (estimatedTokens < limits.compactionThreshold) {
|
|
11731
|
+
if (!force && estimatedTokens < limits.compactionThreshold) {
|
|
11700
11732
|
return messages;
|
|
11701
11733
|
}
|
|
11702
|
-
|
|
11734
|
+
if (force && messages.length < 5)
|
|
11735
|
+
return messages;
|
|
11736
|
+
let keepRecent = limits.keepRecent;
|
|
11737
|
+
if (strategy === "aggressive" || strategy === "summary") {
|
|
11738
|
+
keepRecent = Math.max(2, Math.floor(keepRecent / 2));
|
|
11739
|
+
}
|
|
11703
11740
|
const head = messages.slice(0, 2);
|
|
11704
11741
|
if (messages.length <= 2 + keepRecent)
|
|
11705
11742
|
return messages;
|
|
@@ -11728,16 +11765,35 @@ ${tail}`;
|
|
|
11728
11765
|
nonCompactionMiddle.push(msg);
|
|
11729
11766
|
}
|
|
11730
11767
|
}
|
|
11731
|
-
const
|
|
11768
|
+
const maskedMiddle = this.maskOldObservations(nonCompactionMiddle);
|
|
11769
|
+
let newSummary;
|
|
11770
|
+
switch (strategy) {
|
|
11771
|
+
case "aggressive":
|
|
11772
|
+
newSummary = this.summarizeAggressive(maskedMiddle);
|
|
11773
|
+
break;
|
|
11774
|
+
case "decisions":
|
|
11775
|
+
newSummary = this.summarizeDecisions(maskedMiddle);
|
|
11776
|
+
break;
|
|
11777
|
+
case "errors":
|
|
11778
|
+
newSummary = this.summarizeErrors(maskedMiddle);
|
|
11779
|
+
break;
|
|
11780
|
+
case "summary":
|
|
11781
|
+
newSummary = this.summarizeHighLevel(maskedMiddle);
|
|
11782
|
+
break;
|
|
11783
|
+
default:
|
|
11784
|
+
newSummary = this.summarizeCompactedMessages(maskedMiddle);
|
|
11785
|
+
}
|
|
11732
11786
|
const combinedSummary = previousSummary ? this.progressiveSummarize(previousSummary, newSummary) : newSummary;
|
|
11787
|
+
const strategyLabel = strategy !== "default" ? ` (${strategy})` : "";
|
|
11788
|
+
const forceLabel = force ? " [manual]" : "";
|
|
11733
11789
|
this.emit({
|
|
11734
11790
|
type: "compaction",
|
|
11735
|
-
content: `Compacted ${middle.length} messages${previousSummary ? " (progressive)" : ""}`,
|
|
11791
|
+
content: `Compacted ${middle.length} messages${strategyLabel}${forceLabel}${previousSummary ? " (progressive)" : ""}`,
|
|
11736
11792
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
11737
11793
|
});
|
|
11738
11794
|
const compactionMsg = {
|
|
11739
11795
|
role: "system",
|
|
11740
|
-
content: `[Context compacted \u2014 summary of earlier work]
|
|
11796
|
+
content: `[Context compacted${strategyLabel} \u2014 summary of earlier work]
|
|
11741
11797
|
|
|
11742
11798
|
${combinedSummary}
|
|
11743
11799
|
|
|
@@ -11803,6 +11859,56 @@ ${newerSummary}` : newerSummary;
|
|
|
11803
11859
|
}
|
|
11804
11860
|
return condensed.join("\n");
|
|
11805
11861
|
}
|
|
11862
|
+
/**
|
|
11863
|
+
* Observation masking pre-pass: replace verbose tool outputs with compact
|
|
11864
|
+
* placeholders. Errors and short outputs (<500 chars) are kept verbatim.
|
|
11865
|
+
* Inspired by OpenHands' ObservationMaskingCondenser — zero LLM cost.
|
|
11866
|
+
*/
|
|
11867
|
+
maskOldObservations(messages) {
|
|
11868
|
+
const toolCallNames = /* @__PURE__ */ new Map();
|
|
11869
|
+
for (const msg of messages) {
|
|
11870
|
+
if (msg.tool_calls) {
|
|
11871
|
+
for (const tc of msg.tool_calls) {
|
|
11872
|
+
toolCallNames.set(tc.id, tc.function.name);
|
|
11873
|
+
}
|
|
11874
|
+
}
|
|
11875
|
+
}
|
|
11876
|
+
return messages.map((msg) => {
|
|
11877
|
+
if (msg.role !== "tool" || typeof msg.content !== "string")
|
|
11878
|
+
return msg;
|
|
11879
|
+
const content = msg.content;
|
|
11880
|
+
if (content.startsWith("Error:") || /^(FAIL|ERR!|TypeError|SyntaxError)/i.test(content))
|
|
11881
|
+
return msg;
|
|
11882
|
+
if (content.length < 500)
|
|
11883
|
+
return msg;
|
|
11884
|
+
const toolName = msg.tool_call_id ? toolCallNames.get(msg.tool_call_id) : void 0;
|
|
11885
|
+
const lines = content.split("\n").length;
|
|
11886
|
+
switch (toolName) {
|
|
11887
|
+
case "file_read":
|
|
11888
|
+
return { ...msg, content: `[file content: ${lines} lines, ${content.length} chars \u2014 omitted for compaction]` };
|
|
11889
|
+
case "grep_search":
|
|
11890
|
+
return { ...msg, content: `[grep results: ${lines} matches, ${content.length} chars \u2014 omitted for compaction]` };
|
|
11891
|
+
case "find_files":
|
|
11892
|
+
return { ...msg, content: `[find results: ${lines} files, omitted for compaction]` };
|
|
11893
|
+
case "list_directory":
|
|
11894
|
+
return { ...msg, content: `[directory listing: ${lines} entries, omitted for compaction]` };
|
|
11895
|
+
case "web_fetch":
|
|
11896
|
+
return { ...msg, content: `[web page content: ${content.length} chars \u2014 omitted for compaction]` };
|
|
11897
|
+
case "web_search":
|
|
11898
|
+
return { ...msg, content: `[search results: ${lines} results \u2014 omitted for compaction]` };
|
|
11899
|
+
case "shell":
|
|
11900
|
+
case "background_run":
|
|
11901
|
+
if (/PASS|FAIL|error|warning/i.test(content))
|
|
11902
|
+
return msg;
|
|
11903
|
+
return { ...msg, content: `[command output: ${lines} lines, ${content.length} chars \u2014 omitted for compaction]` };
|
|
11904
|
+
default:
|
|
11905
|
+
if (content.length > 2e3) {
|
|
11906
|
+
return { ...msg, content: `[${toolName ?? "tool"} output: ${content.length} chars \u2014 omitted for compaction]` };
|
|
11907
|
+
}
|
|
11908
|
+
return msg;
|
|
11909
|
+
}
|
|
11910
|
+
});
|
|
11911
|
+
}
|
|
11806
11912
|
/**
|
|
11807
11913
|
* Extract a rich structured summary from compacted messages, preserving:
|
|
11808
11914
|
* - Assistant analysis/reasoning text
|
|
@@ -11970,6 +12076,253 @@ ${newerSummary}` : newerSummary;
|
|
|
11970
12076
|
return parts.join("\n");
|
|
11971
12077
|
}
|
|
11972
12078
|
// -------------------------------------------------------------------------
|
|
12079
|
+
// Strategy-specific summarizers for manual /compact command
|
|
12080
|
+
// -------------------------------------------------------------------------
|
|
12081
|
+
/**
|
|
12082
|
+
* Aggressive compaction: extract only key decisions and errors.
|
|
12083
|
+
* Discards exploration, file reads, and search results entirely.
|
|
12084
|
+
* Keeps: what files were changed, what errors occurred, what approach was chosen.
|
|
12085
|
+
*/
|
|
12086
|
+
summarizeAggressive(messages) {
|
|
12087
|
+
const decisions = [];
|
|
12088
|
+
const errors = [];
|
|
12089
|
+
const filesChanged = /* @__PURE__ */ new Map();
|
|
12090
|
+
let toolCallCount = 0;
|
|
12091
|
+
const toolCallMap = /* @__PURE__ */ new Map();
|
|
12092
|
+
for (const msg of messages) {
|
|
12093
|
+
if (msg.tool_calls) {
|
|
12094
|
+
for (const tc of msg.tool_calls) {
|
|
12095
|
+
toolCallCount++;
|
|
12096
|
+
const args = (() => {
|
|
12097
|
+
try {
|
|
12098
|
+
return JSON.parse(tc.function.arguments);
|
|
12099
|
+
} catch {
|
|
12100
|
+
return {};
|
|
12101
|
+
}
|
|
12102
|
+
})();
|
|
12103
|
+
toolCallMap.set(tc.id, { name: tc.function.name, args });
|
|
12104
|
+
const name = tc.function.name;
|
|
12105
|
+
if (name === "file_edit" || name === "file_write" || name === "batch_edit") {
|
|
12106
|
+
const path = String(args.path || "");
|
|
12107
|
+
if (path)
|
|
12108
|
+
filesChanged.set(path, name === "file_write" ? "created/rewritten" : "edited");
|
|
12109
|
+
}
|
|
12110
|
+
}
|
|
12111
|
+
}
|
|
12112
|
+
if (msg.role === "assistant" && typeof msg.content === "string" && msg.content.trim().length > 30) {
|
|
12113
|
+
const text = msg.content.trim();
|
|
12114
|
+
const decisionPatterns = /(?:I (?:will|need to|should|decided|chose|'ll)|Let me|The (?:fix|solution|approach|issue))[^.!?\n]{10,120}[.!?]/gi;
|
|
12115
|
+
const matches = text.match(decisionPatterns);
|
|
12116
|
+
if (matches) {
|
|
12117
|
+
for (const m of matches.slice(0, 2))
|
|
12118
|
+
decisions.push(m.trim());
|
|
12119
|
+
}
|
|
12120
|
+
}
|
|
12121
|
+
if (msg.role === "tool" && typeof msg.content === "string") {
|
|
12122
|
+
const tc = msg.tool_call_id ? toolCallMap.get(msg.tool_call_id) : void 0;
|
|
12123
|
+
if (msg.content.startsWith("Error:") || /FAIL|ERR!/i.test(msg.content)) {
|
|
12124
|
+
const ctx = tc ? `${tc.name}` : "tool";
|
|
12125
|
+
errors.push(`${ctx}: ${msg.content.split("\n")[0]?.slice(0, 150)}`);
|
|
12126
|
+
}
|
|
12127
|
+
}
|
|
12128
|
+
}
|
|
12129
|
+
const parts = [];
|
|
12130
|
+
parts.push(`## Aggressive Compact (${toolCallCount} tool calls compressed)
|
|
12131
|
+
`);
|
|
12132
|
+
if (filesChanged.size > 0) {
|
|
12133
|
+
parts.push("### Files Modified");
|
|
12134
|
+
for (const [path, action] of filesChanged)
|
|
12135
|
+
parts.push(`- \`${path}\` (${action})`);
|
|
12136
|
+
parts.push("");
|
|
12137
|
+
}
|
|
12138
|
+
if (decisions.length > 0) {
|
|
12139
|
+
parts.push("### Key Decisions");
|
|
12140
|
+
for (const d of decisions.slice(0, 5))
|
|
12141
|
+
parts.push(`- ${d}`);
|
|
12142
|
+
parts.push("");
|
|
12143
|
+
}
|
|
12144
|
+
if (errors.length > 0) {
|
|
12145
|
+
parts.push("### Errors (avoid repeating)");
|
|
12146
|
+
for (const e of errors.slice(0, 5))
|
|
12147
|
+
parts.push(`- ${e}`);
|
|
12148
|
+
}
|
|
12149
|
+
return parts.join("\n");
|
|
12150
|
+
}
|
|
12151
|
+
/**
|
|
12152
|
+
* Decisions compaction: extract action→outcome pairs only.
|
|
12153
|
+
* Each tool call becomes a single "did X → got Y" line.
|
|
12154
|
+
*/
|
|
12155
|
+
summarizeDecisions(messages) {
|
|
12156
|
+
const actionOutcomes = [];
|
|
12157
|
+
const toolCallMap = /* @__PURE__ */ new Map();
|
|
12158
|
+
for (const msg of messages) {
|
|
12159
|
+
if (msg.tool_calls) {
|
|
12160
|
+
for (const tc of msg.tool_calls) {
|
|
12161
|
+
const args = (() => {
|
|
12162
|
+
try {
|
|
12163
|
+
return JSON.parse(tc.function.arguments);
|
|
12164
|
+
} catch {
|
|
12165
|
+
return {};
|
|
12166
|
+
}
|
|
12167
|
+
})();
|
|
12168
|
+
toolCallMap.set(tc.id, { name: tc.function.name, args });
|
|
12169
|
+
}
|
|
12170
|
+
}
|
|
12171
|
+
if (msg.role === "tool" && typeof msg.content === "string" && msg.tool_call_id) {
|
|
12172
|
+
const tc = toolCallMap.get(msg.tool_call_id);
|
|
12173
|
+
if (!tc)
|
|
12174
|
+
continue;
|
|
12175
|
+
const content = msg.content;
|
|
12176
|
+
let action;
|
|
12177
|
+
let outcome;
|
|
12178
|
+
switch (tc.name) {
|
|
12179
|
+
case "file_read":
|
|
12180
|
+
action = `Read \`${tc.args.path}\``;
|
|
12181
|
+
outcome = `${content.split("\n").length} lines`;
|
|
12182
|
+
break;
|
|
12183
|
+
case "file_write":
|
|
12184
|
+
action = `Wrote \`${tc.args.path}\``;
|
|
12185
|
+
outcome = content.startsWith("Error:") ? content.slice(0, 100) : "success";
|
|
12186
|
+
break;
|
|
12187
|
+
case "file_edit":
|
|
12188
|
+
action = `Edited \`${tc.args.path}\``;
|
|
12189
|
+
outcome = content.startsWith("Error:") ? content.slice(0, 100) : "applied";
|
|
12190
|
+
break;
|
|
12191
|
+
case "shell":
|
|
12192
|
+
case "background_run":
|
|
12193
|
+
action = `Ran \`${String(tc.args.command || "").slice(0, 60)}\``;
|
|
12194
|
+
outcome = content.startsWith("Error:") ? "failed" : /PASS|success/i.test(content) ? "passed" : "completed";
|
|
12195
|
+
break;
|
|
12196
|
+
case "grep_search":
|
|
12197
|
+
action = `Searched "${tc.args.pattern}"`;
|
|
12198
|
+
outcome = `${(content.match(/\n/g) || []).length} matches`;
|
|
12199
|
+
break;
|
|
12200
|
+
default:
|
|
12201
|
+
action = tc.name;
|
|
12202
|
+
outcome = content.startsWith("Error:") ? "failed" : "ok";
|
|
12203
|
+
}
|
|
12204
|
+
actionOutcomes.push(`${action} \u2192 ${outcome}`);
|
|
12205
|
+
}
|
|
12206
|
+
}
|
|
12207
|
+
const parts = [];
|
|
12208
|
+
parts.push(`## Decision Log (${actionOutcomes.length} actions)
|
|
12209
|
+
`);
|
|
12210
|
+
for (const ao of actionOutcomes)
|
|
12211
|
+
parts.push(`- ${ao}`);
|
|
12212
|
+
return parts.join("\n");
|
|
12213
|
+
}
|
|
12214
|
+
/**
|
|
12215
|
+
* Error-preserving compaction: keep full error context for failure avoidance,
|
|
12216
|
+
* aggressively compress successful operations to single lines.
|
|
12217
|
+
*/
|
|
12218
|
+
summarizeErrors(messages) {
|
|
12219
|
+
const errors = [];
|
|
12220
|
+
const successes = [];
|
|
12221
|
+
const toolCallMap = /* @__PURE__ */ new Map();
|
|
12222
|
+
for (const msg of messages) {
|
|
12223
|
+
if (msg.tool_calls) {
|
|
12224
|
+
for (const tc of msg.tool_calls) {
|
|
12225
|
+
const args = (() => {
|
|
12226
|
+
try {
|
|
12227
|
+
return JSON.parse(tc.function.arguments);
|
|
12228
|
+
} catch {
|
|
12229
|
+
return {};
|
|
12230
|
+
}
|
|
12231
|
+
})();
|
|
12232
|
+
toolCallMap.set(tc.id, { name: tc.function.name, args });
|
|
12233
|
+
}
|
|
12234
|
+
}
|
|
12235
|
+
if (msg.role === "tool" && typeof msg.content === "string" && msg.tool_call_id) {
|
|
12236
|
+
const tc = toolCallMap.get(msg.tool_call_id);
|
|
12237
|
+
if (!tc)
|
|
12238
|
+
continue;
|
|
12239
|
+
const content = msg.content;
|
|
12240
|
+
const isError = content.startsWith("Error:") || /FAIL|ERR!|error:|TypeError|SyntaxError/i.test(content);
|
|
12241
|
+
const action = tc.name === "shell" ? `\`${String(tc.args.command || "").slice(0, 80)}\`` : `${tc.name}(${String(tc.args.path || tc.args.pattern || "").slice(0, 60)})`;
|
|
12242
|
+
if (isError) {
|
|
12243
|
+
errors.push({
|
|
12244
|
+
context: action,
|
|
12245
|
+
error: content.slice(0, 300)
|
|
12246
|
+
});
|
|
12247
|
+
} else {
|
|
12248
|
+
successes.push(`${action} \u2192 ok`);
|
|
12249
|
+
}
|
|
12250
|
+
}
|
|
12251
|
+
}
|
|
12252
|
+
const parts = [];
|
|
12253
|
+
parts.push(`## Error-Focused Compact
|
|
12254
|
+
`);
|
|
12255
|
+
if (errors.length > 0) {
|
|
12256
|
+
parts.push("### Errors & Failures (preserve to avoid repeating)");
|
|
12257
|
+
for (const { context, error } of errors.slice(0, 8)) {
|
|
12258
|
+
parts.push(`- **${context}**:`);
|
|
12259
|
+
parts.push(` \`\`\`
|
|
12260
|
+
${error}
|
|
12261
|
+
\`\`\``);
|
|
12262
|
+
}
|
|
12263
|
+
parts.push("");
|
|
12264
|
+
}
|
|
12265
|
+
if (successes.length > 0) {
|
|
12266
|
+
parts.push(`### Successful Operations (${successes.length} total)`);
|
|
12267
|
+
for (const s of successes.slice(0, 10))
|
|
12268
|
+
parts.push(`- ${s}`);
|
|
12269
|
+
if (successes.length > 10)
|
|
12270
|
+
parts.push(`- ... and ${successes.length - 10} more`);
|
|
12271
|
+
}
|
|
12272
|
+
return parts.join("\n");
|
|
12273
|
+
}
|
|
12274
|
+
/**
|
|
12275
|
+
* High-level summary: paragraph-form description of what was accomplished.
|
|
12276
|
+
* Minimal detail, maximum compression — good for very long sessions.
|
|
12277
|
+
*/
|
|
12278
|
+
summarizeHighLevel(messages) {
|
|
12279
|
+
const filesModified = /* @__PURE__ */ new Set();
|
|
12280
|
+
const filesRead = /* @__PURE__ */ new Set();
|
|
12281
|
+
let shellCount = 0;
|
|
12282
|
+
let toolCallCount = 0;
|
|
12283
|
+
let lastAssistantText = "";
|
|
12284
|
+
for (const msg of messages) {
|
|
12285
|
+
if (msg.tool_calls) {
|
|
12286
|
+
for (const tc of msg.tool_calls) {
|
|
12287
|
+
toolCallCount++;
|
|
12288
|
+
const args = (() => {
|
|
12289
|
+
try {
|
|
12290
|
+
return JSON.parse(tc.function.arguments);
|
|
12291
|
+
} catch {
|
|
12292
|
+
return {};
|
|
12293
|
+
}
|
|
12294
|
+
})();
|
|
12295
|
+
const name = tc.function.name;
|
|
12296
|
+
if (name === "file_edit" || name === "file_write" || name === "batch_edit") {
|
|
12297
|
+
filesModified.add(String(args.path || ""));
|
|
12298
|
+
} else if (name === "file_read") {
|
|
12299
|
+
filesRead.add(String(args.path || ""));
|
|
12300
|
+
} else if (name === "shell" || name === "background_run") {
|
|
12301
|
+
shellCount++;
|
|
12302
|
+
}
|
|
12303
|
+
}
|
|
12304
|
+
}
|
|
12305
|
+
if (msg.role === "assistant" && typeof msg.content === "string" && msg.content.trim().length > 20) {
|
|
12306
|
+
lastAssistantText = msg.content.trim();
|
|
12307
|
+
}
|
|
12308
|
+
}
|
|
12309
|
+
const modList = Array.from(filesModified).filter(Boolean);
|
|
12310
|
+
const readList = Array.from(filesRead).filter(Boolean);
|
|
12311
|
+
const parts = [];
|
|
12312
|
+
parts.push(`## Session Summary (${toolCallCount} tool calls)
|
|
12313
|
+
`);
|
|
12314
|
+
parts.push(`Modified ${modList.length} file(s), read ${readList.length} file(s), ran ${shellCount} command(s).
|
|
12315
|
+
`);
|
|
12316
|
+
if (modList.length > 0) {
|
|
12317
|
+
parts.push(`Files changed: ${modList.slice(0, 8).map((f) => `\`${f}\``).join(", ")}${modList.length > 8 ? ` +${modList.length - 8} more` : ""}`);
|
|
12318
|
+
}
|
|
12319
|
+
if (lastAssistantText) {
|
|
12320
|
+
parts.push(`
|
|
12321
|
+
Last analysis: ${lastAssistantText.slice(0, 300)}${lastAssistantText.length > 300 ? "..." : ""}`);
|
|
12322
|
+
}
|
|
12323
|
+
return parts.join("\n");
|
|
12324
|
+
}
|
|
12325
|
+
// -------------------------------------------------------------------------
|
|
11973
12326
|
// Tool definition builder
|
|
11974
12327
|
// -------------------------------------------------------------------------
|
|
11975
12328
|
buildToolDefinitions() {
|
|
@@ -14088,6 +14441,8 @@ function renderSlashHelp() {
|
|
|
14088
14441
|
["/stats", "Show session dashboard (metrics, tool usage, task history)"],
|
|
14089
14442
|
["/stop", "Stop current task and save progress (alias: /pause)"],
|
|
14090
14443
|
["/resume", "Resume a previously stopped task"],
|
|
14444
|
+
["/compact", "Force context compaction now (default strategy)"],
|
|
14445
|
+
["/compact <strategy>", "Compact with strategy: aggressive, decisions, errors, summary"],
|
|
14091
14446
|
["/bruteforce", "Toggle brute-force mode (auto re-engage on turn limit)"],
|
|
14092
14447
|
["/tools", "List agent-created custom tools"],
|
|
14093
14448
|
["/skills", "List available AIWG skills"],
|
|
@@ -14457,6 +14812,7 @@ var init_render = __esm({
|
|
|
14457
14812
|
"/verbose",
|
|
14458
14813
|
"/dream",
|
|
14459
14814
|
"/bruteforce",
|
|
14815
|
+
"/compact",
|
|
14460
14816
|
"/tools",
|
|
14461
14817
|
"/skills",
|
|
14462
14818
|
"/clear",
|
|
@@ -16578,6 +16934,26 @@ async function handleSlashCommand(input, ctx) {
|
|
|
16578
16934
|
}
|
|
16579
16935
|
return "handled";
|
|
16580
16936
|
}
|
|
16937
|
+
case "compact":
|
|
16938
|
+
case "gc": {
|
|
16939
|
+
if (!ctx.hasActiveTask?.()) {
|
|
16940
|
+
renderWarning("No active task to compact. Context compaction requires a running task.");
|
|
16941
|
+
return "handled";
|
|
16942
|
+
}
|
|
16943
|
+
const validStrategies = ["default", "aggressive", "decisions", "errors", "summary"];
|
|
16944
|
+
const strategy = arg && validStrategies.includes(arg) ? arg : "default";
|
|
16945
|
+
if (arg && !validStrategies.includes(arg)) {
|
|
16946
|
+
renderWarning(`Unknown strategy "${arg}". Valid: ${validStrategies.join(", ")}`);
|
|
16947
|
+
return "handled";
|
|
16948
|
+
}
|
|
16949
|
+
const ok = ctx.requestCompaction?.(strategy) ?? false;
|
|
16950
|
+
if (ok) {
|
|
16951
|
+
renderInfo(`Compaction requested (strategy: ${c2.bold(strategy)}). Will apply at next turn boundary.`);
|
|
16952
|
+
} else {
|
|
16953
|
+
renderWarning("Could not request compaction.");
|
|
16954
|
+
}
|
|
16955
|
+
return "handled";
|
|
16956
|
+
}
|
|
16581
16957
|
default: {
|
|
16582
16958
|
const skills = discoverSkills(ctx.repoRoot);
|
|
16583
16959
|
const skill = skills.find((s) => s.name === cmd || s.name === cmd.replace(/_/g, "-"));
|
|
@@ -21610,7 +21986,9 @@ async function startInteractive(config, repoPath) {
|
|
|
21610
21986
|
"/skills",
|
|
21611
21987
|
"/pause",
|
|
21612
21988
|
"/stop",
|
|
21613
|
-
"/resume"
|
|
21989
|
+
"/resume",
|
|
21990
|
+
"/compact",
|
|
21991
|
+
"/gc"
|
|
21614
21992
|
];
|
|
21615
21993
|
const discoveredSkillNames = discoverSkills(repoRoot).map((s) => `/${s.name}`);
|
|
21616
21994
|
const allCompletions = [.../* @__PURE__ */ new Set([...BUILTIN_COMMANDS, ...discoveredSkillNames])].sort();
|
|
@@ -21901,6 +22279,12 @@ async function startInteractive(config, repoPath) {
|
|
|
21901
22279
|
statusBar.setCapabilities(caps);
|
|
21902
22280
|
},
|
|
21903
22281
|
hasActiveTask: () => activeTask !== null,
|
|
22282
|
+
requestCompaction(strategy) {
|
|
22283
|
+
if (!activeTask)
|
|
22284
|
+
return false;
|
|
22285
|
+
activeTask.runner.requestCompaction(strategy);
|
|
22286
|
+
return true;
|
|
22287
|
+
},
|
|
21904
22288
|
abortTask() {
|
|
21905
22289
|
if (!activeTask)
|
|
21906
22290
|
return false;
|
package/package.json
CHANGED