@illuma-ai/agents 1.1.2 → 1.1.3
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/cjs/graphs/Graph.cjs +115 -82
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +115 -82
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/package.json +1 -1
- package/src/graphs/Graph.ts +140 -102
- package/src/graphs/gapFeatures.test.ts +234 -2
|
@@ -1124,68 +1124,116 @@ class StandardGraph extends Graph {
|
|
|
1124
1124
|
}
|
|
1125
1125
|
}
|
|
1126
1126
|
if (agentContext.pruneMessages) {
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
//
|
|
1134
|
-
//
|
|
1135
|
-
//
|
|
1136
|
-
//
|
|
1137
|
-
// 3. If NOTHING exists (first-ever prune) → skip summary, fire async generation
|
|
1138
|
-
// The summary catches up asynchronously and is available for subsequent
|
|
1139
|
-
// iterations (tool calls) and the next conversation turn.
|
|
1127
|
+
// ── Context Compaction (Copilot-style: never delete messages) ─────
|
|
1128
|
+
//
|
|
1129
|
+
// DESIGN: Original messages are NEVER removed from the array.
|
|
1130
|
+
// Instead, we build a "windowed view" for the LLM:
|
|
1131
|
+
// [system prompt] + [summary of older turns] + [recent turns that fit]
|
|
1132
|
+
//
|
|
1133
|
+
// This ensures:
|
|
1134
|
+
// - No context is ever lost (summary covers older turns)
|
|
1135
|
+
// - We can always re-summarize from originals if summary is stale
|
|
1136
|
+
// - Conversation chaining works naturally across turns
|
|
1140
1137
|
//
|
|
1141
|
-
//
|
|
1142
|
-
//
|
|
1143
|
-
//
|
|
1144
|
-
//
|
|
1145
|
-
|
|
1138
|
+
// Flow:
|
|
1139
|
+
// 1. Resolve best available summary (cached > persisted > seed)
|
|
1140
|
+
// 2. Calculate token budget available for recent messages
|
|
1141
|
+
// 3. Walk newest→oldest, build view of messages that fit
|
|
1142
|
+
// 4. Assemble: [system] + [summary] + [recent window]
|
|
1143
|
+
// 5. Fire background summary update for messages outside the window
|
|
1146
1144
|
const sumConfig = agentContext.summarizationConfig;
|
|
1147
|
-
const
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1145
|
+
const tokenCounter = agentContext.tokenCounter;
|
|
1146
|
+
const maxTokens = agentContext.maxContextTokens ?? 0;
|
|
1147
|
+
// Step 1: Resolve best available summary
|
|
1148
|
+
let summary;
|
|
1149
|
+
let summarySource;
|
|
1150
|
+
if (this._cachedRunSummary != null) {
|
|
1151
|
+
summary = this._cachedRunSummary;
|
|
1152
|
+
summarySource = 'cached';
|
|
1153
|
+
}
|
|
1154
|
+
else if (agentContext.persistedSummary != null &&
|
|
1155
|
+
agentContext.persistedSummary !== '') {
|
|
1156
|
+
summary = agentContext.persistedSummary;
|
|
1157
|
+
this._cachedRunSummary = summary;
|
|
1158
|
+
summarySource = 'persisted';
|
|
1159
|
+
}
|
|
1160
|
+
else if (sumConfig?.initialSummary != null &&
|
|
1161
|
+
sumConfig.initialSummary !== '') {
|
|
1162
|
+
summary = sumConfig.initialSummary;
|
|
1163
|
+
this._cachedRunSummary = summary;
|
|
1164
|
+
summarySource = 'initial-seed';
|
|
1165
|
+
}
|
|
1166
|
+
else {
|
|
1167
|
+
summarySource = 'none';
|
|
1168
|
+
}
|
|
1169
|
+
// Step 2: Calculate token budget
|
|
1170
|
+
// Apply EMA calibration for accuracy across iterations
|
|
1171
|
+
const calibratedMax = pruneCalibration.applyCalibration(maxTokens, this._pruneCalibration);
|
|
1172
|
+
const systemMsg = messages$1[0]?.getType() === 'system' ? messages$1[0] : null;
|
|
1173
|
+
const systemTokens = systemMsg != null
|
|
1174
|
+
? (agentContext.indexTokenCountMap[0] ?? 0)
|
|
1175
|
+
: 0;
|
|
1176
|
+
const summaryMsg = summary != null && summary !== ''
|
|
1177
|
+
? new messages.SystemMessage(`[Conversation Summary]\n${summary}`)
|
|
1178
|
+
: null;
|
|
1179
|
+
const summaryTokens = summaryMsg != null && tokenCounter != null
|
|
1180
|
+
? tokenCounter(summaryMsg)
|
|
1181
|
+
: 0;
|
|
1182
|
+
// Budget for recent messages = total - system - summary - 3 (assistant priming)
|
|
1183
|
+
const recentBudget = calibratedMax - systemTokens - summaryTokens - 3;
|
|
1184
|
+
// Step 3: Walk newest→oldest, collect messages that fit in the budget
|
|
1185
|
+
const contentStart = systemMsg != null ? 1 : 0;
|
|
1186
|
+
let usedTokens = 0;
|
|
1187
|
+
let windowStart = messages$1.length; // index where the recent window begins
|
|
1188
|
+
for (let i = messages$1.length - 1; i >= contentStart; i--) {
|
|
1189
|
+
const msgTokens = agentContext.indexTokenCountMap[i] ?? 0;
|
|
1190
|
+
if (usedTokens + msgTokens > recentBudget) {
|
|
1191
|
+
break;
|
|
1192
|
+
}
|
|
1193
|
+
usedTokens += msgTokens;
|
|
1194
|
+
windowStart = i;
|
|
1195
|
+
}
|
|
1196
|
+
// Ensure we don't split tool-call / tool-result pairs.
|
|
1197
|
+
// If windowStart lands on a ToolMessage, walk back to include its AI message.
|
|
1198
|
+
while (windowStart > contentStart &&
|
|
1199
|
+
messages$1[windowStart]?.getType() === 'tool') {
|
|
1200
|
+
windowStart--;
|
|
1201
|
+
usedTokens += agentContext.indexTokenCountMap[windowStart] ?? 0;
|
|
1202
|
+
}
|
|
1203
|
+
const recentMessages = messages$1.slice(windowStart);
|
|
1204
|
+
const compactedMessages = messages$1.slice(contentStart, windowStart);
|
|
1205
|
+
const hasSummary = summaryMsg != null;
|
|
1206
|
+
// Step 4: Assemble the windowed view
|
|
1207
|
+
// [system] + [summary (covers compacted messages)] + [recent window]
|
|
1208
|
+
const viewParts = [];
|
|
1209
|
+
if (systemMsg != null) {
|
|
1210
|
+
viewParts.push(systemMsg);
|
|
1211
|
+
}
|
|
1212
|
+
if (summaryMsg != null) {
|
|
1213
|
+
viewParts.push(summaryMsg);
|
|
1214
|
+
}
|
|
1215
|
+
viewParts.push(...recentMessages);
|
|
1216
|
+
messagesToUse = viewParts;
|
|
1217
|
+
console.debug(`[Graph:Compaction] View: ${messages$1.length}→${viewParts.length} msgs ` +
|
|
1218
|
+
`(${compactedMessages.length} behind summary, ${recentMessages.length} in window) | ` +
|
|
1219
|
+
`summary=${summarySource}${summary ? ` (len=${summary.length})` : ''} | ` +
|
|
1220
|
+
`budget=${recentBudget}/${calibratedMax} used=${usedTokens}`);
|
|
1221
|
+
// Step 5: Fire background summary update (non-blocking)
|
|
1222
|
+
// Summarize messages outside the window so next iteration has a fresh summary.
|
|
1223
|
+
// Only trigger if there are compacted messages worth summarizing.
|
|
1224
|
+
if (compactedMessages.length > 0 &&
|
|
1225
|
+
agentContext.summarizeCallback) {
|
|
1226
|
+
const shouldSummarize = this.shouldTriggerSummarization(compactedMessages.length, maxTokens, agentContext.indexTokenCountMap, agentContext.instructionTokens, sumConfig);
|
|
1227
|
+
if (shouldSummarize) {
|
|
1180
1228
|
if (this._summaryInFlight) {
|
|
1181
|
-
this._pendingMessagesToRefine.push(...
|
|
1182
|
-
console.debug(`[Graph:
|
|
1229
|
+
this._pendingMessagesToRefine.push(...compactedMessages);
|
|
1230
|
+
console.debug(`[Graph:Compaction] Summary in-flight, queued ${compactedMessages.length} msgs (pending=${this._pendingMessagesToRefine.length})`);
|
|
1183
1231
|
}
|
|
1184
1232
|
else {
|
|
1185
1233
|
this._summaryInFlight = true;
|
|
1186
1234
|
const allMessages = this._pendingMessagesToRefine.length > 0
|
|
1187
|
-
? [...this._pendingMessagesToRefine, ...
|
|
1188
|
-
:
|
|
1235
|
+
? [...this._pendingMessagesToRefine, ...compactedMessages]
|
|
1236
|
+
: compactedMessages;
|
|
1189
1237
|
this._pendingMessagesToRefine = [];
|
|
1190
1238
|
agentContext
|
|
1191
1239
|
.summarizeCallback(allMessages)
|
|
@@ -1195,40 +1243,17 @@ class StandardGraph extends Graph {
|
|
|
1195
1243
|
}
|
|
1196
1244
|
})
|
|
1197
1245
|
.catch((err) => {
|
|
1198
|
-
console.error('[Graph] Background summary failed (non-fatal):', err);
|
|
1246
|
+
console.error('[Graph:Compaction] Background summary update failed (non-fatal):', err);
|
|
1199
1247
|
})
|
|
1200
1248
|
.finally(() => {
|
|
1201
1249
|
this._summaryInFlight = false;
|
|
1202
1250
|
});
|
|
1203
1251
|
}
|
|
1204
|
-
if (summary != null && summary !== '') {
|
|
1205
|
-
hasSummary = true;
|
|
1206
|
-
const summaryMsg = new messages.SystemMessage(`[Conversation Summary]\n${summary}`);
|
|
1207
|
-
const systemIdx = messagesToUse[0]?.getType() === 'system' ? 1 : 0;
|
|
1208
|
-
messagesToUse = [
|
|
1209
|
-
...messagesToUse.slice(0, systemIdx),
|
|
1210
|
-
summaryMsg,
|
|
1211
|
-
...messagesToUse.slice(systemIdx),
|
|
1212
|
-
];
|
|
1213
|
-
}
|
|
1214
|
-
}
|
|
1215
|
-
catch (err) {
|
|
1216
|
-
console.error('[Graph] Summarization failed:', err);
|
|
1217
1252
|
}
|
|
1218
1253
|
}
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
}
|
|
1223
|
-
// Deduplicate system messages that accumulate from repeated tool iterations
|
|
1224
|
-
const { messages: dedupedMessages, removedCount } = dedup.deduplicateSystemMessages(messagesToUse);
|
|
1225
|
-
if (removedCount > 0) {
|
|
1226
|
-
messagesToUse = dedupedMessages;
|
|
1227
|
-
console.debug(`[Graph:Dedup] Removed ${removedCount} duplicate system message(s)`);
|
|
1228
|
-
}
|
|
1229
|
-
// Post-prune context note for task-tool-enabled agents
|
|
1230
|
-
if (messagesToRefine.length > 0 && contextPressure.hasTaskTool(agentContext.tools)) {
|
|
1231
|
-
const postPruneNote = contextPressure.buildPostPruneNote(messagesToRefine.length, hasSummary);
|
|
1254
|
+
// Post-compaction context note for task-tool-enabled agents
|
|
1255
|
+
if (compactedMessages.length > 0 && contextPressure.hasTaskTool(agentContext.tools)) {
|
|
1256
|
+
const postPruneNote = contextPressure.buildPostPruneNote(compactedMessages.length, hasSummary);
|
|
1232
1257
|
if (postPruneNote) {
|
|
1233
1258
|
messagesToUse = [
|
|
1234
1259
|
...messagesToUse,
|
|
@@ -1237,6 +1262,14 @@ class StandardGraph extends Graph {
|
|
|
1237
1262
|
}
|
|
1238
1263
|
}
|
|
1239
1264
|
}
|
|
1265
|
+
// Deduplicate system messages — ALWAYS runs, not just during compaction.
|
|
1266
|
+
// Duplicate system messages accumulate from repeated tool iterations,
|
|
1267
|
+
// summary injections, and context notes across turns.
|
|
1268
|
+
const { messages: dedupedMessages, removedCount } = dedup.deduplicateSystemMessages(messagesToUse);
|
|
1269
|
+
if (removedCount > 0) {
|
|
1270
|
+
messagesToUse = dedupedMessages;
|
|
1271
|
+
console.debug(`[Graph:Dedup] Removed ${removedCount} duplicate system message(s)`);
|
|
1272
|
+
}
|
|
1240
1273
|
let finalMessages = messagesToUse;
|
|
1241
1274
|
if (agentContext.useLegacyContent) {
|
|
1242
1275
|
finalMessages = content.formatContentStrings(finalMessages);
|