billion-context-pi 0.1.36 → 0.1.38
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/README.md +2 -41
- package/README.zh-CN.md +2 -41
- package/dist/config.d.ts +10 -1
- package/dist/delegate-tool.d.ts +5 -0
- package/dist/index.js +1215 -371
- package/dist/index.js.map +1 -1
- package/dist/runtime.d.ts +3 -1
- package/dist/sequence-match.d.ts +6 -0
- package/dist/system-prompt.d.ts +2 -1
- package/dist/user-config.d.ts +3 -0
- package/package.json +5 -3
package/dist/index.js
CHANGED
|
@@ -132,9 +132,11 @@ function prune(messages, state, options = {}) {
|
|
|
132
132
|
const indexById = /* @__PURE__ */ new Map();
|
|
133
133
|
messages.forEach((message, index) => indexById.set(message.id, index));
|
|
134
134
|
const anchors = inject ? collectSummaryAnchors(state, indexById) : [];
|
|
135
|
-
return
|
|
136
|
-
|
|
137
|
-
|
|
135
|
+
return stripOrphanedReasoning(
|
|
136
|
+
stripOrphanedToolResults(
|
|
137
|
+
stripOrphanedToolCalls(
|
|
138
|
+
rebuildMessages(messages, covered, firstUserIndex, anchors)
|
|
139
|
+
)
|
|
138
140
|
)
|
|
139
141
|
);
|
|
140
142
|
}
|
|
@@ -211,6 +213,24 @@ function stripOrphanedToolCalls(messages) {
|
|
|
211
213
|
(m) => m.contentType !== "tool-call" || !m.toolCallId || m.toolName === "compress" || knownResultIds.has(m.toolCallId)
|
|
212
214
|
);
|
|
213
215
|
}
|
|
216
|
+
function stripOrphanedReasoning(messages) {
|
|
217
|
+
const drop = /* @__PURE__ */ new Set();
|
|
218
|
+
for (let i = 0; i < messages.length; i++) {
|
|
219
|
+
if (drop.has(i)) continue;
|
|
220
|
+
if (messages[i].contentType !== "reasoning") continue;
|
|
221
|
+
let j = i;
|
|
222
|
+
while (j + 1 < messages.length && messages[j + 1].contentType === "reasoning") {
|
|
223
|
+
j++;
|
|
224
|
+
}
|
|
225
|
+
const companion = messages[j + 1];
|
|
226
|
+
const hasCompanion = companion !== void 0 && companion.role === "assistant" && (companion.contentType === "text" || companion.contentType === "tool-call");
|
|
227
|
+
if (!hasCompanion) {
|
|
228
|
+
for (let k = i; k <= j; k++) drop.add(k);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
if (drop.size === 0) return messages;
|
|
232
|
+
return messages.filter((_, i) => !drop.has(i));
|
|
233
|
+
}
|
|
214
234
|
function syncBlocks(messages, state) {
|
|
215
235
|
const presentIds = new Set(messages.map((message) => message.id));
|
|
216
236
|
const deactivated = [];
|
|
@@ -273,7 +293,8 @@ function defaultConfig(modelContextLimit, overrides = {}) {
|
|
|
273
293
|
growthCap: 5e4,
|
|
274
294
|
minGrowthFloor: 2e4,
|
|
275
295
|
minGrowthRatio: 0.45,
|
|
276
|
-
emergencyThresholdPct: 0.95
|
|
296
|
+
emergencyThresholdPct: 0.95,
|
|
297
|
+
tier2GrowthMultiplier: 1.5
|
|
277
298
|
},
|
|
278
299
|
promotionThreshold: 5,
|
|
279
300
|
truncate: { threshold: 0.95 },
|
|
@@ -343,6 +364,18 @@ function parseBoundary(ref) {
|
|
|
343
364
|
}
|
|
344
365
|
return null;
|
|
345
366
|
}
|
|
367
|
+
var BoundaryNotFoundError = class extends Error {
|
|
368
|
+
code = "BOUNDARY_NOT_FOUND";
|
|
369
|
+
kind;
|
|
370
|
+
endpoint;
|
|
371
|
+
constructor(kind, endpoint, message) {
|
|
372
|
+
super(message);
|
|
373
|
+
this.name = "BoundaryNotFoundError";
|
|
374
|
+
this.code = "BOUNDARY_NOT_FOUND";
|
|
375
|
+
this.kind = kind;
|
|
376
|
+
this.endpoint = endpoint;
|
|
377
|
+
}
|
|
378
|
+
};
|
|
346
379
|
function resolveBoundaries(input) {
|
|
347
380
|
const start = parseBoundary(input.startRef);
|
|
348
381
|
const end = parseBoundary(input.endRef);
|
|
@@ -355,13 +388,8 @@ function resolveBoundaries(input) {
|
|
|
355
388
|
input.messages.forEach(
|
|
356
389
|
(message, index) => indexByRawId.set(message.id, index)
|
|
357
390
|
);
|
|
358
|
-
let startIndex = resolveAnchorIndex(start, input.state, indexByRawId);
|
|
359
|
-
let endIndex = resolveAnchorIndex(end, input.state, indexByRawId);
|
|
360
|
-
if (startIndex === null || endIndex === null) {
|
|
361
|
-
throw new Error(
|
|
362
|
-
`Boundary not found in visible context (likely consumed by an existing block). startId="${input.startRef}", endId="${input.endRef}".`
|
|
363
|
-
);
|
|
364
|
-
}
|
|
391
|
+
let startIndex = resolveAnchorIndex(start, input.state, indexByRawId, "start");
|
|
392
|
+
let endIndex = resolveAnchorIndex(end, input.state, indexByRawId, "end");
|
|
365
393
|
if (startIndex > endIndex) {
|
|
366
394
|
[startIndex, endIndex] = [endIndex, startIndex];
|
|
367
395
|
}
|
|
@@ -392,16 +420,51 @@ function resolveBoundaries(input) {
|
|
|
392
420
|
protectedGaps
|
|
393
421
|
};
|
|
394
422
|
}
|
|
395
|
-
function resolveAnchorIndex(boundary, state, indexByRawId) {
|
|
423
|
+
function resolveAnchorIndex(boundary, state, indexByRawId, endpoint) {
|
|
424
|
+
const label = endpoint === "start" ? "startId" : "endId";
|
|
396
425
|
if (boundary.kind === "message") {
|
|
397
426
|
const rawId = state.messageRefs.byRef[boundary.raw] ?? state.messageRefs.byRef[formatPaddedRef(boundary.numericId)];
|
|
398
|
-
if (!rawId)
|
|
427
|
+
if (!rawId) {
|
|
428
|
+
throw new BoundaryNotFoundError(
|
|
429
|
+
"unknown",
|
|
430
|
+
endpoint,
|
|
431
|
+
`${label}="${boundary.raw}" does not exist in this session (typo or wrong session) \u2014 run acp_status for current refs.`
|
|
432
|
+
);
|
|
433
|
+
}
|
|
399
434
|
const index = indexByRawId.get(rawId);
|
|
400
|
-
|
|
435
|
+
if (index === void 0) {
|
|
436
|
+
throw new BoundaryNotFoundError(
|
|
437
|
+
"consumed",
|
|
438
|
+
endpoint,
|
|
439
|
+
`${label}="${boundary.raw}" not found in visible context (likely consumed by an existing block).`
|
|
440
|
+
);
|
|
441
|
+
}
|
|
442
|
+
return index;
|
|
401
443
|
}
|
|
402
444
|
const block = blockById(state, `b${boundary.numericId}`);
|
|
403
|
-
if (!block
|
|
404
|
-
|
|
445
|
+
if (!block) {
|
|
446
|
+
throw new BoundaryNotFoundError(
|
|
447
|
+
"unknown",
|
|
448
|
+
endpoint,
|
|
449
|
+
`${label}="b${boundary.numericId}" does not exist in this session (typo or wrong session) \u2014 run acp_status for current refs.`
|
|
450
|
+
);
|
|
451
|
+
}
|
|
452
|
+
if (!block.active) {
|
|
453
|
+
throw new BoundaryNotFoundError(
|
|
454
|
+
"consumed",
|
|
455
|
+
endpoint,
|
|
456
|
+
`${label}="b${boundary.numericId}" not found in visible context (block distilled/consumed by a higher-tier block).`
|
|
457
|
+
);
|
|
458
|
+
}
|
|
459
|
+
const anchor = earliestIndexOfIds(block.effectiveMessageIds, indexByRawId);
|
|
460
|
+
if (anchor === null) {
|
|
461
|
+
throw new BoundaryNotFoundError(
|
|
462
|
+
"consumed",
|
|
463
|
+
endpoint,
|
|
464
|
+
`${label}="b${boundary.numericId}" not found in visible context (block messages consumed by a higher-tier block).`
|
|
465
|
+
);
|
|
466
|
+
}
|
|
467
|
+
return anchor;
|
|
405
468
|
}
|
|
406
469
|
function formatPaddedRef(index) {
|
|
407
470
|
return `m${String(index).padStart(5, "0")}`;
|
|
@@ -778,6 +841,38 @@ function adjustBoundariesForToolPairs(startIndex, endIndex, messages, maxScan =
|
|
|
778
841
|
}
|
|
779
842
|
return { startIndex: newStartIndex, endIndex: newEndIndex };
|
|
780
843
|
}
|
|
844
|
+
function adjustBoundariesForReasoningPairs(startIndex, endIndex, messages) {
|
|
845
|
+
if (startIndex > endIndex) {
|
|
846
|
+
return { startIndex, endIndex };
|
|
847
|
+
}
|
|
848
|
+
let newStartIndex = startIndex;
|
|
849
|
+
let newEndIndex = endIndex;
|
|
850
|
+
for (let i = startIndex; i <= endIndex && i < messages.length; i++) {
|
|
851
|
+
const msg = messages[i];
|
|
852
|
+
if (!msg) continue;
|
|
853
|
+
if (msg.contentType === "reasoning") {
|
|
854
|
+
let j = i;
|
|
855
|
+
while (j + 1 < messages.length && messages[j + 1].contentType === "reasoning") {
|
|
856
|
+
j++;
|
|
857
|
+
}
|
|
858
|
+
const companion = messages[j + 1];
|
|
859
|
+
if (companion !== void 0 && companion.role === "assistant" && (companion.contentType === "text" || companion.contentType === "tool-call") && j + 1 > newEndIndex) {
|
|
860
|
+
newEndIndex = j + 1;
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
if (msg.role === "assistant" && (msg.contentType === "text" || msg.contentType === "tool-call")) {
|
|
864
|
+
let k = i - 1;
|
|
865
|
+
while (k >= 0 && messages[k].contentType === "reasoning") {
|
|
866
|
+
k--;
|
|
867
|
+
}
|
|
868
|
+
const runStart = k + 1;
|
|
869
|
+
if (runStart < i && runStart >= 0 && messages[runStart].contentType === "reasoning" && runStart < newStartIndex) {
|
|
870
|
+
newStartIndex = runStart;
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
return { startIndex: newStartIndex, endIndex: newEndIndex };
|
|
875
|
+
}
|
|
781
876
|
function refNum(ref) {
|
|
782
877
|
const n = parseInt(ref.slice(1), 10);
|
|
783
878
|
return Number.isNaN(n) ? -1 : n;
|
|
@@ -924,6 +1019,44 @@ function buildCompressibleRanges(messages, state, config, protectedZoneRefs, cou
|
|
|
924
1019
|
protected: protectedRanges
|
|
925
1020
|
};
|
|
926
1021
|
}
|
|
1022
|
+
function mergeBatch(batch) {
|
|
1023
|
+
const first = batch[0];
|
|
1024
|
+
const last = batch[batch.length - 1];
|
|
1025
|
+
const count = batch.reduce((s, r) => s + r.count, 0);
|
|
1026
|
+
const tokens = batch.reduce((s, r) => s + r.tokens, 0);
|
|
1027
|
+
const toolPct = Math.round(
|
|
1028
|
+
batch.reduce((s, r) => s + r.toolPct * r.count, 0) / count
|
|
1029
|
+
);
|
|
1030
|
+
const merged = {
|
|
1031
|
+
startRef: first.startRef,
|
|
1032
|
+
endRef: last.endRef,
|
|
1033
|
+
count,
|
|
1034
|
+
tokens,
|
|
1035
|
+
toolPct,
|
|
1036
|
+
textPct: 100 - toolPct
|
|
1037
|
+
};
|
|
1038
|
+
if (batch.some((r) => r.dangerous === true)) {
|
|
1039
|
+
merged.dangerous = true;
|
|
1040
|
+
}
|
|
1041
|
+
return merged;
|
|
1042
|
+
}
|
|
1043
|
+
function mergeRangesToThreshold(ranges, minChars) {
|
|
1044
|
+
if (minChars <= 0 || ranges.length === 0) return ranges;
|
|
1045
|
+
const result = [];
|
|
1046
|
+
let batch = [];
|
|
1047
|
+
for (const r of ranges) {
|
|
1048
|
+
batch.push(r);
|
|
1049
|
+
const batchTokens = batch.reduce((s, x) => s + x.tokens, 0);
|
|
1050
|
+
if (batchTokens * 4 >= minChars) {
|
|
1051
|
+
result.push(mergeBatch(batch));
|
|
1052
|
+
batch = [];
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
if (batch.length > 0) {
|
|
1056
|
+
result.push(mergeBatch(batch));
|
|
1057
|
+
}
|
|
1058
|
+
return result;
|
|
1059
|
+
}
|
|
927
1060
|
function runPipeline(nodes, initial, ctx) {
|
|
928
1061
|
let io = initial;
|
|
929
1062
|
for (const node of nodes) {
|
|
@@ -932,6 +1065,9 @@ function runPipeline(nodes, initial, ctx) {
|
|
|
932
1065
|
}
|
|
933
1066
|
return io;
|
|
934
1067
|
}
|
|
1068
|
+
function rangeError(spec, message) {
|
|
1069
|
+
return `range ${spec.startRef}..${spec.endRef}: ${message}`;
|
|
1070
|
+
}
|
|
935
1071
|
function createCore(ports = {}) {
|
|
936
1072
|
const countTokens = ports.countTokens ?? defaultCountTokens;
|
|
937
1073
|
function applyCompression(input) {
|
|
@@ -943,20 +1079,44 @@ function createCore(ports = {}) {
|
|
|
943
1079
|
const warnings = [];
|
|
944
1080
|
const protectedMessageIds = input.protectedMessageIds ?? computeProtectedRefs(input.messages, input.state, input.config, countTokens);
|
|
945
1081
|
const preExistingCoverage = collectCoverage(state);
|
|
946
|
-
const
|
|
1082
|
+
const classifications = /* @__PURE__ */ new Map();
|
|
1083
|
+
const classificationErrors = [];
|
|
1084
|
+
const consumedRanges = [];
|
|
947
1085
|
for (const spec of input.ranges) {
|
|
948
|
-
let resolved;
|
|
949
1086
|
try {
|
|
950
|
-
resolved = resolveBoundaries({
|
|
1087
|
+
const resolved = resolveBoundaries({
|
|
951
1088
|
startRef: spec.startRef,
|
|
952
1089
|
endRef: spec.endRef,
|
|
953
1090
|
messages: input.messages,
|
|
954
1091
|
state
|
|
955
1092
|
});
|
|
956
|
-
|
|
957
|
-
|
|
1093
|
+
classifications.set(spec, { status: "ok", resolved });
|
|
1094
|
+
} catch (error) {
|
|
1095
|
+
if (error instanceof BoundaryNotFoundError) {
|
|
1096
|
+
classifications.set(
|
|
1097
|
+
spec,
|
|
1098
|
+
error.kind === "unknown" ? { status: "unknown", error } : { status: "consumed", error }
|
|
1099
|
+
);
|
|
1100
|
+
if (error.kind === "consumed") {
|
|
1101
|
+
consumedRanges.push(spec);
|
|
1102
|
+
} else {
|
|
1103
|
+
classificationErrors.push(rangeError(spec, error.message));
|
|
1104
|
+
}
|
|
1105
|
+
} else {
|
|
1106
|
+
classifications.set(spec, {
|
|
1107
|
+
status: "invalid",
|
|
1108
|
+
error: error instanceof Error ? error : new Error(String(error))
|
|
1109
|
+
});
|
|
1110
|
+
classificationErrors.push(
|
|
1111
|
+
rangeError(spec, error instanceof Error ? error.message : String(error))
|
|
1112
|
+
);
|
|
1113
|
+
}
|
|
958
1114
|
}
|
|
959
|
-
|
|
1115
|
+
}
|
|
1116
|
+
const rangeIndexSets = [];
|
|
1117
|
+
for (const [spec, resolution] of classifications) {
|
|
1118
|
+
if (resolution.status !== "ok") continue;
|
|
1119
|
+
const indices = resolution.resolved.messageIds.map(
|
|
960
1120
|
(id) => input.messages.findIndex((m) => m.id === id)
|
|
961
1121
|
).filter((i) => i >= 0);
|
|
962
1122
|
rangeIndexSets.push({ spec, indices });
|
|
@@ -983,37 +1143,27 @@ function createCore(ports = {}) {
|
|
|
983
1143
|
if (input.config.compress.minCompressRange > 0 && input.ranges.length > 0) {
|
|
984
1144
|
let totalRangeChars = 0;
|
|
985
1145
|
let hasBlockBoundaryRange = false;
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
resolved = resolveBoundaries({
|
|
991
|
-
startRef: spec.startRef,
|
|
992
|
-
endRef: spec.endRef,
|
|
993
|
-
messages: input.messages,
|
|
994
|
-
state
|
|
995
|
-
});
|
|
996
|
-
} catch {
|
|
997
|
-
continue;
|
|
998
|
-
}
|
|
999
|
-
if (resolved.boundaryKind === "block") {
|
|
1146
|
+
let countedRanges = 0;
|
|
1147
|
+
for (const [spec, resolution] of classifications) {
|
|
1148
|
+
if (resolution.status !== "ok" || skipSpecs.has(spec)) continue;
|
|
1149
|
+
if (resolution.resolved.boundaryKind === "block") {
|
|
1000
1150
|
hasBlockBoundaryRange = true;
|
|
1001
1151
|
continue;
|
|
1002
1152
|
}
|
|
1003
|
-
|
|
1153
|
+
countedRanges++;
|
|
1154
|
+
for (const id of resolution.resolved.messageIds) {
|
|
1004
1155
|
const msg = input.messages.find((m) => m.id === id);
|
|
1005
1156
|
totalRangeChars += msg?.text?.length ?? 0;
|
|
1006
1157
|
}
|
|
1007
1158
|
}
|
|
1008
1159
|
if (!hasBlockBoundaryRange && totalRangeChars < input.config.compress.minCompressRange) {
|
|
1160
|
+
const gateMessage = consumedRanges.length > 0 ? `Requested range(s) already compressed (e.g. ${consumedRanges[0].startRef}..${consumedRanges[0].endRef}); remaining compressible content ${totalRangeChars} chars < min ${input.config.compress.minCompressRange}. Nothing to do \u2014 run acp_status to see current compressible ranges.` : `Total compressible content too small (${totalRangeChars} chars across ${countedRanges} range(s), min ${input.config.compress.minCompressRange}). Combine more messages into your range(s) to meet the threshold.`;
|
|
1009
1161
|
return {
|
|
1010
1162
|
state: input.state,
|
|
1011
1163
|
result: {
|
|
1012
1164
|
blocksCreated: 0,
|
|
1013
1165
|
tokensCompressed: 0,
|
|
1014
|
-
errors: [
|
|
1015
|
-
`Total compressible content too small (${totalRangeChars} chars across ${input.ranges.length} range(s), min ${input.config.compress.minCompressRange}). Combine more messages into your range(s) to meet the threshold.`
|
|
1016
|
-
],
|
|
1166
|
+
errors: [gateMessage, ...classificationErrors],
|
|
1017
1167
|
warnings: []
|
|
1018
1168
|
}
|
|
1019
1169
|
};
|
|
@@ -1021,6 +1171,18 @@ function createCore(ports = {}) {
|
|
|
1021
1171
|
}
|
|
1022
1172
|
for (const spec of input.ranges) {
|
|
1023
1173
|
if (skipSpecs.has(spec)) continue;
|
|
1174
|
+
const resolution = classifications.get(spec);
|
|
1175
|
+
if (resolution === void 0) continue;
|
|
1176
|
+
if (resolution.status === "consumed") {
|
|
1177
|
+
warnings.push(
|
|
1178
|
+
`Skipped range (${spec.startRef}..${spec.endRef}) \u2014 already compressed (messages consumed by existing block(s)); nothing to compress.`
|
|
1179
|
+
);
|
|
1180
|
+
continue;
|
|
1181
|
+
}
|
|
1182
|
+
if (resolution.status === "unknown" || resolution.status === "invalid") {
|
|
1183
|
+
errors.push(rangeError(spec, resolution.error.message));
|
|
1184
|
+
continue;
|
|
1185
|
+
}
|
|
1024
1186
|
try {
|
|
1025
1187
|
const outcome = applySingleRange({
|
|
1026
1188
|
spec,
|
|
@@ -1036,7 +1198,7 @@ function createCore(ports = {}) {
|
|
|
1036
1198
|
tokensCompressed += outcome.tokens;
|
|
1037
1199
|
warnings.push(...outcome.warnings);
|
|
1038
1200
|
} catch (error) {
|
|
1039
|
-
errors.push(error instanceof Error ? error.message : String(error));
|
|
1201
|
+
errors.push(rangeError(spec, error instanceof Error ? error.message : String(error)));
|
|
1040
1202
|
}
|
|
1041
1203
|
}
|
|
1042
1204
|
state.stats.compressionCount += blocksCreated;
|
|
@@ -1174,7 +1336,10 @@ var recommendNode = {
|
|
|
1174
1336
|
const nothingToCompress = contextRanges.compressible.length === 0;
|
|
1175
1337
|
const recommendation = {
|
|
1176
1338
|
contextRanges,
|
|
1177
|
-
recommendedRanges:
|
|
1339
|
+
recommendedRanges: mergeRangesToThreshold(
|
|
1340
|
+
contextRanges.compressible,
|
|
1341
|
+
ctx.config.compress.minCompressRange
|
|
1342
|
+
),
|
|
1178
1343
|
nothingToCompress
|
|
1179
1344
|
};
|
|
1180
1345
|
return { ...io, effects: { ...io.effects, recommendation } };
|
|
@@ -1200,6 +1365,7 @@ var nudgeNode = {
|
|
|
1200
1365
|
if (baseline > 0 && ctx.tokenCount < baseline - nudgeGrowthTokens) {
|
|
1201
1366
|
stamped.lastPerMessageNudgeTokens = ctx.tokenCount;
|
|
1202
1367
|
stamped.lastNudgeShownTokens = 0;
|
|
1368
|
+
stamped.lastShownByTier = {};
|
|
1203
1369
|
}
|
|
1204
1370
|
if (stamped.lastPerMessageNudgeTokens === 0) {
|
|
1205
1371
|
stamped.lastPerMessageNudgeTokens = ctx.tokenCount;
|
|
@@ -1244,7 +1410,7 @@ function applySingleRange(input) {
|
|
|
1244
1410
|
messages: input.messages,
|
|
1245
1411
|
state: input.state
|
|
1246
1412
|
});
|
|
1247
|
-
const rangeMessageIds =
|
|
1413
|
+
const rangeMessageIds = applyPairBoundaryAdjustments(
|
|
1248
1414
|
resolved,
|
|
1249
1415
|
input.messages
|
|
1250
1416
|
);
|
|
@@ -1358,20 +1524,33 @@ function applySingleRange(input) {
|
|
|
1358
1524
|
}
|
|
1359
1525
|
return { tokens: compressedTokens, warnings };
|
|
1360
1526
|
}
|
|
1361
|
-
function
|
|
1527
|
+
function applyPairBoundaryAdjustments(resolved, messages) {
|
|
1362
1528
|
if (resolved.boundaryKind === "block") {
|
|
1363
1529
|
return resolved.messageIds;
|
|
1364
1530
|
}
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1531
|
+
let startIndex = resolved.startIndex;
|
|
1532
|
+
let endIndex = resolved.endIndex;
|
|
1533
|
+
for (let pass = 0; pass < 2; pass++) {
|
|
1534
|
+
const reasoningAdjusted = adjustBoundariesForReasoningPairs(
|
|
1535
|
+
startIndex,
|
|
1536
|
+
endIndex,
|
|
1537
|
+
messages
|
|
1538
|
+
);
|
|
1539
|
+
const toolAdjusted = adjustBoundariesForToolPairs(
|
|
1540
|
+
reasoningAdjusted.startIndex,
|
|
1541
|
+
reasoningAdjusted.endIndex,
|
|
1542
|
+
messages
|
|
1543
|
+
);
|
|
1544
|
+
const changed = toolAdjusted.startIndex !== startIndex || toolAdjusted.endIndex !== endIndex;
|
|
1545
|
+
startIndex = toolAdjusted.startIndex;
|
|
1546
|
+
endIndex = toolAdjusted.endIndex;
|
|
1547
|
+
if (!changed) break;
|
|
1548
|
+
}
|
|
1549
|
+
if (startIndex === resolved.startIndex && endIndex === resolved.endIndex) {
|
|
1371
1550
|
return resolved.messageIds;
|
|
1372
1551
|
}
|
|
1373
1552
|
const ids = [];
|
|
1374
|
-
for (let i =
|
|
1553
|
+
for (let i = startIndex; i <= endIndex; i++) {
|
|
1375
1554
|
const msg = messages[i];
|
|
1376
1555
|
if (msg) ids.push(msg.id);
|
|
1377
1556
|
}
|
|
@@ -1455,10 +1634,11 @@ function resolveAdaptiveGrowth(modelContextLimit, nudge) {
|
|
|
1455
1634
|
)
|
|
1456
1635
|
);
|
|
1457
1636
|
}
|
|
1458
|
-
function pendingByTier(state, recommendation, countTokens) {
|
|
1637
|
+
function pendingByTier(state, recommendation, countTokens, minCompressRange) {
|
|
1459
1638
|
const out = {};
|
|
1460
|
-
const
|
|
1461
|
-
|
|
1639
|
+
const merged = recommendation?.recommendedRanges ?? [];
|
|
1640
|
+
const effective = minCompressRange > 0 ? merged.filter((r) => r.tokens * 4 >= minCompressRange) : merged;
|
|
1641
|
+
out[1] = { pending: effective.reduce((s, r) => s + r.tokens, 0), targetBlocks: [] };
|
|
1462
1642
|
const active = activeBlocks(state);
|
|
1463
1643
|
const t1 = active.filter((b) => b.tier === 1);
|
|
1464
1644
|
const t2 = active.filter((b) => b.tier === 2);
|
|
@@ -1473,6 +1653,7 @@ function decideNudge(input) {
|
|
|
1473
1653
|
const nudgeGrowthTokens = resolveAdaptiveGrowth(limit, config.nudge);
|
|
1474
1654
|
const overLimit = usage >= config.nudge.maxContextLimitPct;
|
|
1475
1655
|
const emergencyOverride = usage >= config.nudge.emergencyThresholdPct;
|
|
1656
|
+
const pressure = overLimit || emergencyOverride;
|
|
1476
1657
|
const baseline = state.nudge.lastPerMessageNudgeTokens;
|
|
1477
1658
|
const hadPendingNudge = state.nudge.lastNudgeShownTokens > 0;
|
|
1478
1659
|
const hasPendingNudge = hadPendingNudge;
|
|
@@ -1484,44 +1665,67 @@ function decideNudge(input) {
|
|
|
1484
1665
|
);
|
|
1485
1666
|
const growthSinceReference = tokenCount - growthReference;
|
|
1486
1667
|
const rec = recommendation;
|
|
1487
|
-
const tiers = pendingByTier(
|
|
1668
|
+
const tiers = pendingByTier(
|
|
1669
|
+
state,
|
|
1670
|
+
rec,
|
|
1671
|
+
countTokens,
|
|
1672
|
+
config.compress.minCompressRange
|
|
1673
|
+
);
|
|
1674
|
+
const tier2Threshold = Math.round(
|
|
1675
|
+
nudgeGrowthTokens * (config.nudge.tier2GrowthMultiplier ?? 1.5)
|
|
1676
|
+
);
|
|
1488
1677
|
let injectedTier = null;
|
|
1489
1678
|
let injectedReason = "";
|
|
1490
1679
|
const growthReady = growthSinceReference >= growthFloor;
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1680
|
+
const t1Eff = tiers[1]?.pending ?? 0;
|
|
1681
|
+
const t2Pen = tiers[2]?.pending ?? 0;
|
|
1682
|
+
const t3Pen = tiers[3]?.pending ?? 0;
|
|
1683
|
+
if (pressure) {
|
|
1684
|
+
const candidates = [1];
|
|
1685
|
+
if (config.tiers.enabled) {
|
|
1686
|
+
candidates.push(2, 3);
|
|
1687
|
+
}
|
|
1688
|
+
let best = null;
|
|
1689
|
+
let bestPending = 0;
|
|
1690
|
+
for (const t of candidates) {
|
|
1691
|
+
const p = tiers[t]?.pending ?? 0;
|
|
1692
|
+
if (p > bestPending) {
|
|
1693
|
+
bestPending = p;
|
|
1694
|
+
best = t;
|
|
1695
|
+
}
|
|
1502
1696
|
}
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1697
|
+
if (best !== null && bestPending > 0) {
|
|
1698
|
+
injectedTier = best;
|
|
1699
|
+
const label = emergencyOverride ? "EMERGENCY" : "OVER-LIMIT";
|
|
1700
|
+
injectedReason = best === 1 ? `${label} T1: max effective pending ${bestPending}, usage ${Math.round(usage * 100)}%` : `${label} T${best} distill: max pending ${bestPending} (T1 effective ${t1Eff}, T2 ${t2Pen}, T3 ${t3Pen}), usage ${Math.round(usage * 100)}%`;
|
|
1701
|
+
}
|
|
1702
|
+
} else if (growthReady) {
|
|
1703
|
+
if (t1Eff >= nudgeGrowthTokens) {
|
|
1704
|
+
injectedTier = 1;
|
|
1705
|
+
injectedReason = `T1 effective ${t1Eff} >= ${nudgeGrowthTokens}, growth ${growthSinceReference}, usage ${Math.round(usage * 100)}%`;
|
|
1706
|
+
} else if (config.tiers.enabled && t2Pen >= tier2Threshold && t2Pen > t1Eff) {
|
|
1707
|
+
const lastShown = state.nudge.lastShownByTier[2] ?? 0;
|
|
1708
|
+
const cadenceMet = lastShown === 0 || tokenCount - lastShown >= growthFloor;
|
|
1709
|
+
if (cadenceMet) {
|
|
1710
|
+
injectedTier = 2;
|
|
1711
|
+
injectedReason = `T2 distill ready: ${tiers[2].targetBlocks.length} tier-1 blocks (${t2Pen} tokens) >= ${tier2Threshold} (1.5x) and > T1 effective ${t1Eff}, usage ${Math.round(usage * 100)}%`;
|
|
1712
|
+
}
|
|
1713
|
+
} else if (config.tiers.enabled && t3Pen >= tier2Threshold && t3Pen > t2Pen && t3Pen > t1Eff) {
|
|
1714
|
+
const lastShown = state.nudge.lastShownByTier[3] ?? 0;
|
|
1715
|
+
const cadenceMet = lastShown === 0 || tokenCount - lastShown >= growthFloor;
|
|
1716
|
+
if (cadenceMet) {
|
|
1717
|
+
injectedTier = 3;
|
|
1718
|
+
injectedReason = `T3 condense ready: ${tiers[3].targetBlocks.length} tier-2 blocks (${t3Pen} tokens) >= ${tier2Threshold} (1.5x) and > T2 ${t2Pen} and > T1 effective ${t1Eff}, usage ${Math.round(usage * 100)}%`;
|
|
1719
|
+
}
|
|
1511
1720
|
}
|
|
1512
1721
|
}
|
|
1513
|
-
const shouldInject = injectedTier !== null
|
|
1722
|
+
const shouldInject = injectedTier !== null;
|
|
1514
1723
|
let reason;
|
|
1515
|
-
if (
|
|
1516
|
-
reason = injectedReason;
|
|
1517
|
-
} else if (emergencyOverride) {
|
|
1518
|
-
reason = `EMERGENCY: usage ${Math.round(usage * 100)}% >= ${Math.round(config.nudge.emergencyThresholdPct * 100)}% (no compressible content)`;
|
|
1519
|
-
} else if (overLimit && injectedTier !== null) {
|
|
1520
|
-
reason = injectedReason;
|
|
1521
|
-
} else if (overLimit) {
|
|
1522
|
-
reason = `OVER-LIMIT: usage ${Math.round(usage * 100)}% >= ${Math.round(config.nudge.maxContextLimitPct * 100)}% (no compressible content)`;
|
|
1523
|
-
} else if (injectedTier !== null) {
|
|
1724
|
+
if (injectedTier !== null) {
|
|
1524
1725
|
reason = injectedReason;
|
|
1726
|
+
} else if (pressure) {
|
|
1727
|
+
const label = emergencyOverride ? "EMERGENCY" : "OVER-LIMIT";
|
|
1728
|
+
reason = `${label}: usage ${Math.round(usage * 100)}% but no tier has effective compressible content (T1 effective ${t1Eff}, T2 ${t2Pen}, T3 ${t3Pen}) \u2014 nudge suppressed to avoid offering ranges below minCompressRange`;
|
|
1525
1729
|
} else {
|
|
1526
1730
|
const tiersList = [1, 2, 3];
|
|
1527
1731
|
const eligible = tiersList.filter((t) => config.tiers.enabled || t === 1);
|
|
@@ -1727,12 +1931,39 @@ DROP:
|
|
|
1727
1931
|
- Anything marked [OBSOLETE] or [SUPERSEDED] \u2014 drop entirely, note "[N blocks obsolete]" in the summary.
|
|
1728
1932
|
|
|
1729
1933
|
SIZE TARGET: 30-60 tokens per source block (including header). For a batch of N source blocks, total output \u2248 N \xD7 40 tokens. If a source block has only one trivial fact, output just the header + one line.`;
|
|
1730
|
-
var
|
|
1934
|
+
var defaultPrompts = Object.freeze({
|
|
1935
|
+
compressPhilosophy: COMPRESS_PHILOSOPHY,
|
|
1936
|
+
howToCompressRules: HOW_TO_COMPRESS_RULES,
|
|
1937
|
+
tier2DistillRules: TIER2_DISTILL_RULES,
|
|
1938
|
+
tier3CondenseRules: TIER3_CONDENSE_RULES
|
|
1939
|
+
});
|
|
1940
|
+
function resolvePrompts(overrides, options = {}) {
|
|
1941
|
+
const clean = {};
|
|
1942
|
+
if (overrides) {
|
|
1943
|
+
for (const [key, value] of Object.entries(overrides)) {
|
|
1944
|
+
if (typeof value === "string") {
|
|
1945
|
+
clean[key] = value;
|
|
1946
|
+
}
|
|
1947
|
+
}
|
|
1948
|
+
}
|
|
1949
|
+
const keys = Object.keys(clean);
|
|
1950
|
+
if (keys.length > 0 && !options.acknowledgeRisk) {
|
|
1951
|
+
throw new Error(
|
|
1952
|
+
`resolvePrompts: overriding compression rules requires { acknowledgeRisk: true }. Overridden keys: ${keys.join(", ")}. These rules are quality-critical (tuned over months of production use); changing them can degrade summary quality and break retrieval (summaries may lose paths, signatures, decisions).`
|
|
1953
|
+
);
|
|
1954
|
+
}
|
|
1955
|
+
return { ...defaultPrompts, ...clean };
|
|
1956
|
+
}
|
|
1957
|
+
function efficiencyNote(prompts) {
|
|
1958
|
+
return `This is an efficiency nudge to compress early and keep context lean \u2014 not an overflow warning. A separate, stronger alert will appear if the context is actually full.
|
|
1731
1959
|
|
|
1732
|
-
${
|
|
1733
|
-
|
|
1960
|
+
${prompts.compressPhilosophy}`;
|
|
1961
|
+
}
|
|
1962
|
+
function emergencyHeader(prompts) {
|
|
1963
|
+
return `\u26A0\uFE0F Context limit reached \u2014 compress now. Prioritize consumed tool outputs.
|
|
1734
1964
|
|
|
1735
|
-
${
|
|
1965
|
+
${prompts.compressPhilosophy}`;
|
|
1966
|
+
}
|
|
1736
1967
|
function formatK(n) {
|
|
1737
1968
|
if (n >= 1e3) return `${(n / 1e3).toFixed(1)}K`;
|
|
1738
1969
|
return `${n}`;
|
|
@@ -1840,43 +2071,45 @@ function formatRanges(compressible, protectedRanges) {
|
|
|
1840
2071
|
return `Compressible ranges (${merged.length}, oldest first):
|
|
1841
2072
|
${lines.join("\n")}`;
|
|
1842
2073
|
}
|
|
1843
|
-
function renderNudgeText(decision) {
|
|
2074
|
+
function renderNudgeText(decision, prompts = defaultPrompts) {
|
|
1844
2075
|
const breakdownStr = formatBreakdown(decision.contextBreakdown);
|
|
1845
2076
|
const rangesStr = formatRanges(decision.compressibleRanges, decision.protectedRanges ?? []);
|
|
2077
|
+
const isEmergency = !!decision.breakdown?.emergencyOverride || !!decision.breakdown?.overLimit;
|
|
1846
2078
|
if (decision.tier !== null && decision.tier >= 2) {
|
|
1847
2079
|
const isT2 = decision.tier === 2;
|
|
1848
2080
|
const targets = decision.tierTargetBlocks ?? [];
|
|
1849
2081
|
const blockList = formatTierTargetBlocks(targets);
|
|
1850
2082
|
const startId = targets[0]?.blockId ?? "b1";
|
|
1851
2083
|
const endId = targets[targets.length - 1]?.blockId ?? "b5";
|
|
2084
|
+
const voice = isEmergency ? "emergency" : "gentle";
|
|
2085
|
+
const triggerLine = isEmergency ? `[EMERGENCY \u2014 TIER ${decision.tier} ${isT2 ? "DISTILLATION" : "CONDENSATION"}] Context limit reached \u2014 distill NOW into a denser summary to reclaim tokens.` : `[TIER ${decision.tier} ${isT2 ? "DISTILLATION" : "CONDENSATION"} TRIGGER]`;
|
|
1852
2086
|
return {
|
|
1853
|
-
voice
|
|
2087
|
+
voice,
|
|
1854
2088
|
text: [
|
|
1855
|
-
|
|
2089
|
+
efficiencyNote(prompts),
|
|
1856
2090
|
"",
|
|
1857
2091
|
breakdownStr,
|
|
1858
2092
|
"",
|
|
1859
|
-
|
|
2093
|
+
triggerLine,
|
|
1860
2094
|
isT2 ? `Your tier-1 compression summaries have accumulated. Distill them into a single denser tier-2 summary. Use block IDs as boundaries (startId and endId as bN). Any raw (uncompressed) messages sitting between the boundary blocks are absorbed into the tier-2 block as well \u2014 apply HOW TO COMPRESS to those raw messages and the TIER 2 distillation rules to the existing summaries, so the whole span is covered and nothing is lost.` : `Your tier-2 compression summaries have accumulated. Condense them further into a tier-3 ultra-condensed summary. Use block IDs as boundaries (startId and endId as bN). Any raw (uncompressed) messages sitting between the boundary blocks are absorbed into the tier-3 block as well \u2014 apply HOW TO COMPRESS to those raw messages and the TIER 3 condensation rules to the existing summaries, so the whole span is covered and nothing is lost.`,
|
|
1861
2095
|
blockList,
|
|
1862
2096
|
`Example: compress({ content: [{ startId: "${startId}", endId: "${endId}", summary: "..." }] })`,
|
|
1863
2097
|
"",
|
|
1864
|
-
|
|
2098
|
+
prompts.howToCompressRules,
|
|
1865
2099
|
"",
|
|
1866
|
-
isT2 ?
|
|
2100
|
+
isT2 ? prompts.tier2DistillRules : prompts.tier3CondenseRules
|
|
1867
2101
|
].join("\n")
|
|
1868
2102
|
};
|
|
1869
2103
|
}
|
|
1870
|
-
const isEmergency = !!decision.breakdown?.emergencyOverride || !!decision.breakdown?.overLimit;
|
|
1871
2104
|
if (isEmergency) {
|
|
1872
2105
|
return {
|
|
1873
2106
|
voice: "emergency",
|
|
1874
2107
|
text: [
|
|
1875
|
-
|
|
2108
|
+
emergencyHeader(prompts),
|
|
1876
2109
|
"",
|
|
1877
2110
|
breakdownStr,
|
|
1878
2111
|
"",
|
|
1879
|
-
|
|
2112
|
+
prompts.howToCompressRules,
|
|
1880
2113
|
"",
|
|
1881
2114
|
`{ "topic": "...", "content": [{ "startId": "<ID>", "endId": "<ID>", "summary": "..." }] }`,
|
|
1882
2115
|
"Only use IDs from visible messages above. Compress older work first.",
|
|
@@ -1888,11 +2121,11 @@ function renderNudgeText(decision) {
|
|
|
1888
2121
|
return {
|
|
1889
2122
|
voice: "gentle",
|
|
1890
2123
|
text: [
|
|
1891
|
-
|
|
2124
|
+
efficiencyNote(prompts),
|
|
1892
2125
|
"",
|
|
1893
2126
|
breakdownStr,
|
|
1894
2127
|
"",
|
|
1895
|
-
|
|
2128
|
+
prompts.howToCompressRules,
|
|
1896
2129
|
"",
|
|
1897
2130
|
rangesStr,
|
|
1898
2131
|
"",
|
|
@@ -2231,9 +2464,9 @@ function charBigrams(text) {
|
|
|
2231
2464
|
}
|
|
2232
2465
|
return grams;
|
|
2233
2466
|
}
|
|
2234
|
-
function tfMap(text,
|
|
2467
|
+
function tfMap(text, stem22) {
|
|
2235
2468
|
const m = /* @__PURE__ */ new Map();
|
|
2236
|
-
for (const t of tokenize(text, { stem:
|
|
2469
|
+
for (const t of tokenize(text, { stem: stem22 })) m.set(t, (m.get(t) ?? 0) + 1);
|
|
2237
2470
|
return m;
|
|
2238
2471
|
}
|
|
2239
2472
|
var bm25Algorithm = {
|
|
@@ -2980,6 +3213,99 @@ function mergeInitialState(parsed) {
|
|
|
2980
3213
|
};
|
|
2981
3214
|
}
|
|
2982
3215
|
|
|
3216
|
+
// src/sequence-match.ts
|
|
3217
|
+
function findUniqueLongestRun(candidates, live) {
|
|
3218
|
+
if (candidates.length === 0 || live.length === 0) return void 0;
|
|
3219
|
+
const ids = /* @__PURE__ */ new Map();
|
|
3220
|
+
const intern = (key) => {
|
|
3221
|
+
const existing = ids.get(key);
|
|
3222
|
+
if (existing !== void 0) return existing;
|
|
3223
|
+
const id = ids.size + 1;
|
|
3224
|
+
ids.set(key, id);
|
|
3225
|
+
return id;
|
|
3226
|
+
};
|
|
3227
|
+
const candidateIds = candidates.map(intern);
|
|
3228
|
+
const liveIds = live.map(intern);
|
|
3229
|
+
const separator = ids.size + 1;
|
|
3230
|
+
const sequence = [...candidateIds, separator, ...liveIds];
|
|
3231
|
+
const suffixArray = buildSuffixArray(sequence);
|
|
3232
|
+
const lcp = buildLcp(sequence, suffixArray);
|
|
3233
|
+
const candidateLength = candidates.length;
|
|
3234
|
+
const liveOffset = candidateLength + 1;
|
|
3235
|
+
const sourceOf = (suffix) => {
|
|
3236
|
+
if (suffix < candidateLength) return 0;
|
|
3237
|
+
if (suffix >= liveOffset) return 1;
|
|
3238
|
+
return void 0;
|
|
3239
|
+
};
|
|
3240
|
+
let bestLength = 0;
|
|
3241
|
+
for (let index = 1; index < suffixArray.length; index++) {
|
|
3242
|
+
const leftSource = sourceOf(suffixArray[index - 1]);
|
|
3243
|
+
const rightSource = sourceOf(suffixArray[index]);
|
|
3244
|
+
if (leftSource === void 0 || rightSource === void 0 || leftSource === rightSource) continue;
|
|
3245
|
+
bestLength = Math.max(bestLength, lcp[index]);
|
|
3246
|
+
}
|
|
3247
|
+
if (bestLength === 0) return void 0;
|
|
3248
|
+
let pairCount = 0;
|
|
3249
|
+
let uniqueCandidateStart = -1;
|
|
3250
|
+
let uniqueLiveStart = -1;
|
|
3251
|
+
for (let start = 0; start < suffixArray.length; ) {
|
|
3252
|
+
let end = start;
|
|
3253
|
+
while (end + 1 < suffixArray.length && lcp[end + 1] >= bestLength) end++;
|
|
3254
|
+
if (end > start) {
|
|
3255
|
+
const candidateStarts = [];
|
|
3256
|
+
const liveStarts = [];
|
|
3257
|
+
for (let index = start; index <= end; index++) {
|
|
3258
|
+
const suffix = suffixArray[index];
|
|
3259
|
+
const source = sourceOf(suffix);
|
|
3260
|
+
if (source === 0) candidateStarts.push(suffix);
|
|
3261
|
+
else if (source === 1) liveStarts.push(suffix - liveOffset);
|
|
3262
|
+
}
|
|
3263
|
+
const groupPairs = candidateStarts.length * liveStarts.length;
|
|
3264
|
+
pairCount += groupPairs;
|
|
3265
|
+
if (groupPairs === 1) {
|
|
3266
|
+
uniqueCandidateStart = candidateStarts[0];
|
|
3267
|
+
uniqueLiveStart = liveStarts[0];
|
|
3268
|
+
}
|
|
3269
|
+
if (pairCount > 1) return void 0;
|
|
3270
|
+
}
|
|
3271
|
+
start = end + 1;
|
|
3272
|
+
}
|
|
3273
|
+
return pairCount === 1 ? { candidateStart: uniqueCandidateStart, liveStart: uniqueLiveStart, length: bestLength } : void 0;
|
|
3274
|
+
}
|
|
3275
|
+
function buildSuffixArray(sequence) {
|
|
3276
|
+
const suffixArray = sequence.map((_, index) => index);
|
|
3277
|
+
let ranks = [...sequence];
|
|
3278
|
+
for (let width = 1; width < sequence.length; width *= 2) {
|
|
3279
|
+
suffixArray.sort((left, right) => ranks[left] - ranks[right] || (ranks[left + width] ?? -1) - (ranks[right + width] ?? -1));
|
|
3280
|
+
const nextRanks = Array(sequence.length);
|
|
3281
|
+
nextRanks[suffixArray[0]] = 0;
|
|
3282
|
+
for (let index = 1; index < suffixArray.length; index++) {
|
|
3283
|
+
const previous = suffixArray[index - 1];
|
|
3284
|
+
const current = suffixArray[index];
|
|
3285
|
+
const differs = ranks[previous] !== ranks[current] || (ranks[previous + width] ?? -1) !== (ranks[current + width] ?? -1);
|
|
3286
|
+
nextRanks[current] = nextRanks[previous] + (differs ? 1 : 0);
|
|
3287
|
+
}
|
|
3288
|
+
ranks = nextRanks;
|
|
3289
|
+
if (ranks[suffixArray.at(-1)] === sequence.length - 1) break;
|
|
3290
|
+
}
|
|
3291
|
+
return suffixArray;
|
|
3292
|
+
}
|
|
3293
|
+
function buildLcp(sequence, suffixArray) {
|
|
3294
|
+
const positions = Array(sequence.length);
|
|
3295
|
+
for (let index = 0; index < suffixArray.length; index++) positions[suffixArray[index]] = index;
|
|
3296
|
+
const lcp = Array(sequence.length).fill(0);
|
|
3297
|
+
let length = 0;
|
|
3298
|
+
for (let start = 0; start < sequence.length; start++) {
|
|
3299
|
+
const position = positions[start];
|
|
3300
|
+
if (position === 0) continue;
|
|
3301
|
+
const previous = suffixArray[position - 1];
|
|
3302
|
+
while (start + length < sequence.length && previous + length < sequence.length && sequence[start + length] === sequence[previous + length]) length++;
|
|
3303
|
+
lcp[position] = length;
|
|
3304
|
+
if (length > 0) length--;
|
|
3305
|
+
}
|
|
3306
|
+
return lcp;
|
|
3307
|
+
}
|
|
3308
|
+
|
|
2983
3309
|
// src/runtime.ts
|
|
2984
3310
|
function readContextEntries(sm) {
|
|
2985
3311
|
const source = sm;
|
|
@@ -2993,15 +3319,17 @@ function isPiHost(sm) {
|
|
|
2993
3319
|
}
|
|
2994
3320
|
function mergeLiveEntries(entries, live, state, origins) {
|
|
2995
3321
|
const persisted = entries.filter((e) => e.type === "message");
|
|
2996
|
-
const
|
|
2997
|
-
const
|
|
3322
|
+
const liveIdentities = live.map(messageIdentity);
|
|
3323
|
+
const persistedIdentities = persisted.map((entry) => messageIdentity(entry.message));
|
|
3324
|
+
const persistedRange = findUniqueLongestRun(persistedIdentities, normalizePersistedMatchKeys(persisted, persistedIdentities, live, liveIdentities));
|
|
3325
|
+
const originRange = findUniqueLongestRun(origins.map((origin) => origin.identity), liveIdentities);
|
|
2998
3326
|
const out = [];
|
|
2999
3327
|
const nextOrigins = [];
|
|
3000
3328
|
const usedIds = /* @__PURE__ */ new Set();
|
|
3001
3329
|
for (let i = 0; i < live.length; i++) {
|
|
3002
3330
|
const msg = live[i];
|
|
3003
|
-
const entry =
|
|
3004
|
-
const origin =
|
|
3331
|
+
const entry = valueInRange(persisted, persistedRange, i);
|
|
3332
|
+
const origin = valueInRange(origins, originRange, i);
|
|
3005
3333
|
if (entry) {
|
|
3006
3334
|
if (origin) migrateLiveRefs(state, origin.rawId, entry.id);
|
|
3007
3335
|
else migrateTaggedRef(state, msg, entry.id);
|
|
@@ -3011,23 +3339,13 @@ function mergeLiveEntries(entries, live, state, origins) {
|
|
|
3011
3339
|
const id = origin?.rawId ?? nextLiveId(state, usedIds, i);
|
|
3012
3340
|
usedIds.add(id);
|
|
3013
3341
|
out.push({ type: "message", id, parentId: null, timestamp: String(msg.timestamp ?? Date.now()), message: msg });
|
|
3014
|
-
nextOrigins.push({ rawId: id, identity:
|
|
3342
|
+
nextOrigins.push({ rawId: id, identity: liveIdentities[i] });
|
|
3015
3343
|
}
|
|
3016
3344
|
origins.splice(0, origins.length, ...nextOrigins);
|
|
3017
|
-
const unmatched = live.length -
|
|
3345
|
+
const unmatched = live.length - (persistedRange?.length ?? 0);
|
|
3018
3346
|
if (unmatched > 0) logInfo("runtime", { event: "merge-live-entries", live: live.length, unmatched });
|
|
3019
3347
|
return out;
|
|
3020
3348
|
}
|
|
3021
|
-
function matchOrigins(origins, live) {
|
|
3022
|
-
for (let start = 0; start < origins.length; start++) {
|
|
3023
|
-
const count = origins.length - start;
|
|
3024
|
-
if (count > live.length) continue;
|
|
3025
|
-
if (origins.slice(start).every((origin, index) => origin.identity === messageIdentity(live[index]))) {
|
|
3026
|
-
return [...origins.slice(start), ...Array(live.length - count)];
|
|
3027
|
-
}
|
|
3028
|
-
}
|
|
3029
|
-
return Array(live.length);
|
|
3030
|
-
}
|
|
3031
3349
|
function nextLiveId(state, used, index) {
|
|
3032
3350
|
let id = `live-${index}`;
|
|
3033
3351
|
let suffix = index;
|
|
@@ -3054,31 +3372,39 @@ function migrateLiveRefs(state, liveId, stableId) {
|
|
|
3054
3372
|
delete state.messageRefs.byRaw[rawId];
|
|
3055
3373
|
}
|
|
3056
3374
|
}
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
const
|
|
3062
|
-
if (
|
|
3063
|
-
|
|
3064
|
-
|
|
3375
|
+
var NO_PERSISTED_MATCH = /* @__PURE__ */ Symbol("no-persisted-match");
|
|
3376
|
+
function normalizePersistedMatchKeys(persisted, persistedIdentities, live, liveIdentities) {
|
|
3377
|
+
const persistedByStructure = /* @__PURE__ */ new Map();
|
|
3378
|
+
for (let index = 0; index < persisted.length; index++) {
|
|
3379
|
+
const key = toolResultStructureKey(persisted[index].message);
|
|
3380
|
+
if (key === void 0) continue;
|
|
3381
|
+
persistedByStructure.set(key, persistedByStructure.has(key) ? -1 : index);
|
|
3382
|
+
}
|
|
3383
|
+
return live.map((message, liveIndex) => {
|
|
3384
|
+
const key = toolResultStructureKey(message);
|
|
3385
|
+
const candidateIndex = key === void 0 ? void 0 : persistedByStructure.get(key);
|
|
3386
|
+
if (candidateIndex === void 0) return liveIdentities[liveIndex];
|
|
3387
|
+
if (candidateIndex < 0) return NO_PERSISTED_MATCH;
|
|
3388
|
+
return sameToolResult(persisted[candidateIndex].message, message) ? persistedIdentities[candidateIndex] : liveIdentities[liveIndex];
|
|
3389
|
+
});
|
|
3065
3390
|
}
|
|
3066
|
-
function
|
|
3067
|
-
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
return a === b;
|
|
3078
|
-
}
|
|
3391
|
+
function toolResultStructureKey(message) {
|
|
3392
|
+
if (message.role !== "toolResult") return void 0;
|
|
3393
|
+
return `${message.toolName}\0${message.toolCallId}`;
|
|
3394
|
+
}
|
|
3395
|
+
function valueInRange(values, range, liveIndex) {
|
|
3396
|
+
if (!range || liveIndex < range.liveStart || liveIndex >= range.liveStart + range.length) return void 0;
|
|
3397
|
+
return values[range.candidateStart + liveIndex - range.liveStart];
|
|
3398
|
+
}
|
|
3399
|
+
function sameToolResult(stored, visible) {
|
|
3400
|
+
if (stored.role !== "toolResult" || visible.role !== "toolResult") return false;
|
|
3401
|
+
return sameNonTextBlocks(stored.content, visible.content) && matchesStoredText(extractText(stored.content), extractText(visible.content));
|
|
3079
3402
|
}
|
|
3080
3403
|
function sameNonTextBlocks(a, b) {
|
|
3081
|
-
const nonText = (blocks) => blocks.filter((block) =>
|
|
3404
|
+
const nonText = (blocks) => blocks.filter((block) => {
|
|
3405
|
+
if (!block || typeof block !== "object" || !("type" in block)) return true;
|
|
3406
|
+
return block.type !== "text";
|
|
3407
|
+
});
|
|
3082
3408
|
try {
|
|
3083
3409
|
const na = Array.isArray(a) ? nonText(a) : [];
|
|
3084
3410
|
const nb = Array.isArray(b) ? nonText(b) : [];
|
|
@@ -3106,6 +3432,7 @@ function createRuntime(adapter) {
|
|
|
3106
3432
|
const store = new SessionStateStore();
|
|
3107
3433
|
const locks = /* @__PURE__ */ new Map();
|
|
3108
3434
|
let adapterRef = adapter;
|
|
3435
|
+
let promptsRef = defaultPrompts;
|
|
3109
3436
|
const nudgeShownTurns = /* @__PURE__ */ new Set();
|
|
3110
3437
|
async function acquireLock(sid) {
|
|
3111
3438
|
const prev = locks.get(sid) ?? Promise.resolve();
|
|
@@ -3154,6 +3481,10 @@ function createRuntime(adapter) {
|
|
|
3154
3481
|
return adapterRef;
|
|
3155
3482
|
}, setAdapter: (a) => {
|
|
3156
3483
|
adapterRef = a;
|
|
3484
|
+
}, get prompts() {
|
|
3485
|
+
return promptsRef;
|
|
3486
|
+
}, setPrompts: (p) => {
|
|
3487
|
+
promptsRef = p;
|
|
3157
3488
|
}, markNudgeShown: (k) => {
|
|
3158
3489
|
nudgeShownTurns.add(k);
|
|
3159
3490
|
}, nudgeShownFor: (k) => nudgeShownTurns.has(k), clearNudgeTracking: () => {
|
|
@@ -7581,6 +7912,23 @@ function lastUserMessageId(entries) {
|
|
|
7581
7912
|
return void 0;
|
|
7582
7913
|
}
|
|
7583
7914
|
|
|
7915
|
+
// src/compat.ts
|
|
7916
|
+
function normalizeSystemPrompt(input) {
|
|
7917
|
+
if (input === void 0) return "";
|
|
7918
|
+
if (Array.isArray(input)) return input.join("\n");
|
|
7919
|
+
return input;
|
|
7920
|
+
}
|
|
7921
|
+
function formatSystemPromptForEvent(base, append) {
|
|
7922
|
+
const normalized = normalizeSystemPrompt(base);
|
|
7923
|
+
return `${normalized}
|
|
7924
|
+
|
|
7925
|
+
${append}`;
|
|
7926
|
+
}
|
|
7927
|
+
function getSystemPromptText(ctx) {
|
|
7928
|
+
const result = ctx.getSystemPrompt?.();
|
|
7929
|
+
return normalizeSystemPrompt(result);
|
|
7930
|
+
}
|
|
7931
|
+
|
|
7584
7932
|
// src/compress-tool.ts
|
|
7585
7933
|
function formatK2(n) {
|
|
7586
7934
|
return n >= 1e3 ? `${(n / 1e3).toFixed(1)}K` : String(n);
|
|
@@ -7626,13 +7974,14 @@ async function handleCompress(args, runtime, ctx, toolCallId) {
|
|
|
7626
7974
|
if (ranges.length === 0) return "No ranges provided.";
|
|
7627
7975
|
const { state: initialState, coreMessages } = await runtime.stateFor(ctx);
|
|
7628
7976
|
const config = runtime.configFor(ctx);
|
|
7629
|
-
const
|
|
7630
|
-
const
|
|
7977
|
+
const systemPromptText = getSystemPromptText(ctx);
|
|
7978
|
+
const systemPromptTokens = systemPromptText ? defaultCountTokens(systemPromptText) : 0;
|
|
7979
|
+
const sentTokens = estimateTokens(coreMessages, collectCoveredMessageIds(initialState)) + systemPromptTokens;
|
|
7631
7980
|
const turn = runtime.core.processTurn({
|
|
7632
7981
|
messages: coreMessages,
|
|
7633
7982
|
state: initialState,
|
|
7634
7983
|
config,
|
|
7635
|
-
tokenCount:
|
|
7984
|
+
tokenCount: sentTokens
|
|
7636
7985
|
});
|
|
7637
7986
|
const state = turn.state;
|
|
7638
7987
|
const messages = turn.messages;
|
|
@@ -7768,6 +8117,18 @@ function findMessageContent(ref, ctx) {
|
|
|
7768
8117
|
}
|
|
7769
8118
|
return null;
|
|
7770
8119
|
}
|
|
8120
|
+
function resolveBlockMessages(block, coreMessages, ctx) {
|
|
8121
|
+
const neededBaseIds = new Set(block.effectiveMessageIds.map((id) => id.split("#")[0]));
|
|
8122
|
+
const presentBaseIds = new Set(coreMessages.map((m) => m.id.split("#")[0]));
|
|
8123
|
+
const missingBaseIds = [...neededBaseIds].filter((id) => !presentBaseIds.has(id));
|
|
8124
|
+
if (missingBaseIds.length === 0) return coreMessages;
|
|
8125
|
+
const extra = [];
|
|
8126
|
+
for (const baseId of missingBaseIds) {
|
|
8127
|
+
const entry = ctx.sessionManager.getEntry(baseId);
|
|
8128
|
+
if (entry) extra.push(...entriesToCoreMessages([entry]));
|
|
8129
|
+
}
|
|
8130
|
+
return [...coreMessages, ...extra];
|
|
8131
|
+
}
|
|
7771
8132
|
async function handleMessageRef(ref, ownerBlockId, args, ctx) {
|
|
7772
8133
|
const found = findMessageContent(ref, ctx);
|
|
7773
8134
|
if (!found || !found.text) {
|
|
@@ -7815,7 +8176,8 @@ async function handleDecompress(args, runtime, ctx) {
|
|
|
7815
8176
|
return `Block ${blockId} not found. Active blocks: ${active || "(none)"}.`;
|
|
7816
8177
|
}
|
|
7817
8178
|
const full = args.full ?? false;
|
|
7818
|
-
const
|
|
8179
|
+
const resolved = resolveBlockMessages(block, coreMessages, ctx);
|
|
8180
|
+
const { text, count } = collectBlockContent(state, block, resolved, { full });
|
|
7819
8181
|
if (count === 0) return `Block ${blockId} has no restorable message content.`;
|
|
7820
8182
|
if (args.inline === true && !args.toFile) {
|
|
7821
8183
|
debug.event("decompress", { blockId, full, count, mode: "inline" });
|
|
@@ -7973,105 +8335,624 @@ function formatSize(tokens) {
|
|
|
7973
8335
|
return `${(tokens / 1e6).toFixed(1)}M`;
|
|
7974
8336
|
}
|
|
7975
8337
|
|
|
7976
|
-
//
|
|
7977
|
-
import {
|
|
7978
|
-
|
|
7979
|
-
|
|
7980
|
-
|
|
7981
|
-
import { mkdir as mkdir2, mkdtemp, writeFile as writeFile2, rm, appendFile } from "fs/promises";
|
|
7982
|
-
import { tmpdir as tmpdir2 } from "os";
|
|
7983
|
-
import { join as join4 } from "path";
|
|
7984
|
-
|
|
7985
|
-
// src/footer-status.ts
|
|
7986
|
-
var FOOTER_STATUS_KEY = "billion-context-pi";
|
|
7987
|
-
var ui;
|
|
7988
|
-
var lastFooterText = "";
|
|
7989
|
-
function formatCompactTokens(count) {
|
|
7990
|
-
if (count < 1e3) return count.toString();
|
|
7991
|
-
if (count < 1e4) return `${(count / 1e3).toFixed(1)}k`;
|
|
7992
|
-
if (count < 1e6) return `${Math.round(count / 1e3)}k`;
|
|
7993
|
-
if (count < 1e7) return `${(count / 1e6).toFixed(1)}M`;
|
|
7994
|
-
return `${Math.round(count / 1e6)}M`;
|
|
8338
|
+
// node_modules/billion-context-kit/node_modules/acp-kernel/dist/index.js
|
|
8339
|
+
import { createRequire as createRequire2 } from "module";
|
|
8340
|
+
var BLOCKED_REF2 = "BLOCKED";
|
|
8341
|
+
function refForRaw2(map, rawId) {
|
|
8342
|
+
return map.byRaw[rawId] ?? null;
|
|
7995
8343
|
}
|
|
7996
|
-
|
|
7997
|
-
|
|
7998
|
-
|
|
8344
|
+
var require22 = createRequire2(import.meta.url);
|
|
8345
|
+
function defaultCountTokens2(text) {
|
|
8346
|
+
if (!text) return 0;
|
|
8347
|
+
const cjk = text.match(/[\u4e00-\u9fff\u3040-\u30ff\uac00-\ud7af]/g);
|
|
8348
|
+
const cjkCount = cjk?.length ?? 0;
|
|
8349
|
+
return cjkCount + Math.ceil((text.length - cjkCount) / 4);
|
|
7999
8350
|
}
|
|
8000
|
-
function
|
|
8001
|
-
if (
|
|
8002
|
-
|
|
8003
|
-
|
|
8004
|
-
if (usage && usage.totalTokens > 0) {
|
|
8005
|
-
const costStr = usage.cost.total > 0 ? ` ($${usage.cost.total.toFixed(4)})` : "";
|
|
8006
|
-
text = `sub-agents \u2191${formatCompactTokens(usage.input)} \u2193${formatCompactTokens(usage.output)}${costStr}`;
|
|
8007
|
-
}
|
|
8008
|
-
if ((text ?? "") === lastFooterText) return;
|
|
8009
|
-
lastFooterText = text ?? "";
|
|
8010
|
-
try {
|
|
8011
|
-
ui.setStatus(FOOTER_STATUS_KEY, text);
|
|
8012
|
-
} catch {
|
|
8013
|
-
}
|
|
8351
|
+
function formatTokens3(tokens) {
|
|
8352
|
+
if (tokens < 1e3) return String(tokens);
|
|
8353
|
+
if (tokens < 1e4) return (tokens / 1e3).toFixed(1) + "K";
|
|
8354
|
+
return Math.round(tokens / 1e3) + "K";
|
|
8014
8355
|
}
|
|
8015
|
-
function
|
|
8016
|
-
if (
|
|
8017
|
-
|
|
8018
|
-
ui.setStatus(FOOTER_STATUS_KEY, void 0);
|
|
8019
|
-
} catch {
|
|
8020
|
-
}
|
|
8356
|
+
function classifyType2(message) {
|
|
8357
|
+
if (message.contentType === "tool-call" || message.contentType === "tool-result") {
|
|
8358
|
+
return message.toolName || "tool";
|
|
8021
8359
|
}
|
|
8022
|
-
|
|
8023
|
-
lastFooterText = "";
|
|
8024
|
-
}
|
|
8025
|
-
|
|
8026
|
-
// src/fleet-widget.ts
|
|
8027
|
-
var DELEGATE_WIDGET_KEY = "billion-context-pi-delegates";
|
|
8028
|
-
var REFRESH_MS = 500;
|
|
8029
|
-
var MAX_TASK_LEN = 48;
|
|
8030
|
-
var ui2;
|
|
8031
|
-
var timer;
|
|
8032
|
-
var lastRenderKey = "";
|
|
8033
|
-
var runsSnapshot;
|
|
8034
|
-
function truncateTask(task) {
|
|
8035
|
-
const oneLine = task.replace(/\n/g, " ").trim();
|
|
8036
|
-
if (oneLine.length <= MAX_TASK_LEN) return oneLine;
|
|
8037
|
-
return `${oneLine.slice(0, MAX_TASK_LEN - 1)}\u2026`;
|
|
8360
|
+
return message.contentType;
|
|
8038
8361
|
}
|
|
8039
|
-
function
|
|
8040
|
-
|
|
8041
|
-
const now = Date.now();
|
|
8042
|
-
const header = runs2.length === 1 ? `acp_delegate \xB7 1 running` : `acp_delegate \xB7 ${runs2.length} running`;
|
|
8043
|
-
const rows = runs2.map((r) => {
|
|
8044
|
-
const elapsed = Math.max(0, Math.round((now - r.startedAt) / 1e3));
|
|
8045
|
-
return ` \u25CF ${r.agent} (${elapsed}s) \u2014 ${truncateTask(r.task)}`;
|
|
8046
|
-
});
|
|
8047
|
-
return [header, ...rows];
|
|
8362
|
+
function escapeRegex2(s) {
|
|
8363
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
8048
8364
|
}
|
|
8049
|
-
|
|
8050
|
-
|
|
8365
|
+
var LT2 = "<";
|
|
8366
|
+
var GT2 = ">";
|
|
8367
|
+
var TAG_OPEN2 = LT2 + "acp ";
|
|
8368
|
+
var TAG_CLOSE2 = LT2 + "/acp" + GT2;
|
|
8369
|
+
function acpTag2(ref, tokens, type) {
|
|
8370
|
+
return TAG_OPEN2 + 'tokens="' + formatTokens3(tokens) + '" type="' + type + '"' + GT2 + ref + TAG_CLOSE2;
|
|
8051
8371
|
}
|
|
8052
|
-
function
|
|
8053
|
-
|
|
8054
|
-
|
|
8055
|
-
|
|
8372
|
+
function renderMessage2(message, map, countTokens, strategy) {
|
|
8373
|
+
const ref = refForRaw2(map, message.id);
|
|
8374
|
+
if (!ref || ref === BLOCKED_REF2) return message;
|
|
8375
|
+
if (strategy === "none") return message;
|
|
8376
|
+
if (strategy === "text-only" && message.contentType !== "text") {
|
|
8377
|
+
return message;
|
|
8056
8378
|
}
|
|
8379
|
+
const ownTagRe = new RegExp(
|
|
8380
|
+
"^" + escapeRegex2(TAG_OPEN2) + "[^>]*" + GT2 + escapeRegex2(ref) + escapeRegex2(TAG_CLOSE2) + "\\n?"
|
|
8381
|
+
);
|
|
8382
|
+
const cleanText = (message.text || "").replace(ownTagRe, "");
|
|
8383
|
+
const tokens = countTokens(cleanText);
|
|
8384
|
+
const type = classifyType2(message);
|
|
8385
|
+
const prefix = acpTag2(ref, tokens, type) + "\n";
|
|
8386
|
+
if (!cleanText) return { ...message, text: prefix };
|
|
8387
|
+
return { ...message, text: prefix + cleanText };
|
|
8057
8388
|
}
|
|
8058
|
-
function
|
|
8059
|
-
|
|
8060
|
-
|
|
8061
|
-
|
|
8062
|
-
|
|
8063
|
-
}
|
|
8389
|
+
function renderVisibleRefs2(messages, state, countTokens = (text) => Math.ceil(text.length / 4), strategy = "all") {
|
|
8390
|
+
const map = state.messageRefs;
|
|
8391
|
+
return messages.map(
|
|
8392
|
+
(message) => renderMessage2(message, map, countTokens, strategy)
|
|
8393
|
+
);
|
|
8064
8394
|
}
|
|
8065
|
-
function
|
|
8066
|
-
|
|
8067
|
-
|
|
8068
|
-
|
|
8069
|
-
|
|
8070
|
-
|
|
8071
|
-
|
|
8395
|
+
function createRenderRefsNode2(strategy) {
|
|
8396
|
+
return {
|
|
8397
|
+
name: "render-refs",
|
|
8398
|
+
run(io, ctx) {
|
|
8399
|
+
return {
|
|
8400
|
+
...io,
|
|
8401
|
+
messages: renderVisibleRefs2(io.messages, io.state, ctx.countTokens, strategy)
|
|
8402
|
+
};
|
|
8072
8403
|
}
|
|
8073
|
-
|
|
8074
|
-
|
|
8404
|
+
};
|
|
8405
|
+
}
|
|
8406
|
+
var renderRefsNode2 = createRenderRefsNode2("all");
|
|
8407
|
+
var COMPRESS_PHILOSOPHY2 = `Compression Philosophy:
|
|
8408
|
+
- All compression serves the primary task, but be frugal.
|
|
8409
|
+
- Context capacity is precious. Save context by compressing consumed outputs, not by avoiding tools.
|
|
8410
|
+
- Compress by need, not by percentage.
|
|
8411
|
+
- Work from summaries, not raw tool outputs. All listed ranges (user prompts, tool outputs, code, logs, exploration, intermediate steps) should be compressed to summary format \u2014 the ONLY exceptions are protected content, content the current step is actively using, or critical content you cannot reconstruct.`;
|
|
8412
|
+
var HOW_TO_COMPRESS_RULES2 = `HOW TO COMPRESS
|
|
8413
|
+
|
|
8414
|
+
When you call \`compress\`, the summary you write becomes the only record of the replaced conversation. Make it self-contained and complete: every user request, experiment purpose, and work task in the range must be accurately captured. A later reader (or you, after decompressing) should be able to continue the task WITHOUT needing the original.
|
|
8415
|
+
|
|
8416
|
+
KEEP VERBATIM \u2014 never paraphrase or abbreviate these:
|
|
8417
|
+
- Full file paths with line numbers, directory prefix on every mention (\`lib/hooks.ts:347\`, \`src/index.ts:12-18\`, \`gatenet_v3/model.py:45\`). Never abbreviate to a bare filename (\`hooks.ts\`, \`model.py\`) \u2014 they are ambiguous and cannot be grepped or decompressed-to later.
|
|
8418
|
+
- Function, class, and type signatures (exact names, params, return types) AND critical code lines that encode logic \u2014 the line that IS the finding, not just the function name (e.g. \`kv_keys += define_gate * a_key[i](emb)\` is more useful than "see model_kvnet.py").
|
|
8419
|
+
- Error messages and stack traces (exact text \u2014 you need the literal string to grep for it later).
|
|
8420
|
+
- Key details from reports and analyses \u2014 not just the conclusion. Keep the comparison numbers and the mechanism, not "X is worse" alone (write "1.76\xD7 PPL gap because KV store is static", not "KVNet underperforms").
|
|
8421
|
+
- Decisions and their rationale ("chose X over Y because Z" \u2014 the "because" is load-bearing; without it the decision looks arbitrary).
|
|
8422
|
+
- Constraints discovered ("must support Node 22", "no new dependencies", "AGENTS.md forbids \`as any\`").
|
|
8423
|
+
- Exact values: versions, config keys, thresholds, magic numbers.
|
|
8424
|
+
- User intent \u2014 quote short user messages verbatim. When the message is too long to quote, preserve intent with extra care: do not change scope, constraints, priorities, acceptance criteria, or requested outcomes. Mark them clearly as past quotes (e.g., "User said: ..."), not as current directives. Losing these changes the task itself.
|
|
8425
|
+
- The user's overall goal and any changes to it \u2014 the big-picture objective plus how it evolved during the compressed range. Each summary must reflect the goal as it stood at the end of the range, including pivots (e.g., "initially: fix bug X \u2192 pivoted to: refactor module Y after discovering root cause"). Losing the goal or its evolution makes all subsequent work appear unmotivated.
|
|
8426
|
+
- Purpose behind each significant action \u2014 preserve not just what was done but why: the hypothesis behind each experiment, the question behind each exploration, the task goal behind each work action. Without purpose, the summary reads as disconnected technical steps with no through-line.
|
|
8427
|
+
- Open questions and unresolved TODOs \u2014 losing these changes what work appears to remain.
|
|
8428
|
+
- Message refs of key anchors (\`m00420\`, \`m00510\u2013m00520\`) \u2014 they let you or a later reader jump back via decompress to the exact original.
|
|
8429
|
+
|
|
8430
|
+
DROP \u2014 extract the signal, discard the vessel:
|
|
8431
|
+
- Verbose logs (build/test/\`npm\` output) once you have captured the error line or the result.
|
|
8432
|
+
- Duplicate file reads once the needed content is recorded.
|
|
8433
|
+
- Consumed exploration \u2014 search hits, agent return values, successful tool outputs \u2014 once you have extracted the facts you need (same rule as dead-ends, but nothing went wrong; the content is simply spent).
|
|
8434
|
+
- Dead-end exploration \u2014 but PRESERVE the lesson in one line: "tried X, failed because Y".
|
|
8435
|
+
- Back-and-forth discussion and self-corrections once the final position is captured (keep the outcome, drop the journey to it).
|
|
8436
|
+
- Repeated status checks (\`git status\`, \`ls\`) once state is known.
|
|
8437
|
+
|
|
8438
|
+
For each significant item you DROP (scripts, reports, large analyses, long tool outputs), add a one-line CONTENT description of what it covers \u2014 not where it lives. Bad: "probe script at /path/probe_kvnet.py". Good: "probe_kvnet.py: tests n-gram baseline, generation quality, long-range dependency, position sensitivity, op pipeline, QUERY attention." This lets a later decompress target the right block by relevance, not by guessing locations.
|
|
8439
|
+
|
|
8440
|
+
PRIORITY \u2014 when the summary must be compact, preserve in this order:
|
|
8441
|
+
1. User's overall goal, goal evolution, intent, and hard constraints (losing these changes the task).
|
|
8442
|
+
2. Decisions and rationale.
|
|
8443
|
+
3. Exact technical artifacts: paths, signatures, errors, values.
|
|
8444
|
+
4. Conclusions and key findings.
|
|
8445
|
+
5. Lessons learned: what failed and why.
|
|
8446
|
+
|
|
8447
|
+
Write dense, scannable bullets \u2014 not narrative prose. If the range spans distinct concerns (request \u2192 findings \u2192 decision), group bullets under short thematic headers so a reader can scan to the part they need. Every line must earn its place. Do not mimic the style of existing summaries in context; follow these rules.`;
|
|
8448
|
+
var TIER2_DISTILL_RULES2 = `TIER 2 COMPRESSION \u2014 DISTILLATION
|
|
8449
|
+
|
|
8450
|
+
You are compressing historical summaries (not raw conversation). These summaries have already captured the details. Your job is to DISTILL them: extract only what matters for future work, discard the process.
|
|
8451
|
+
|
|
8452
|
+
KEEP \u2014 these are the only things that survive distillation:
|
|
8453
|
+
- Decisions and their rationale ("chose X over Y because Z" \u2014 the "because" is load-bearing).
|
|
8454
|
+
- Final outcomes: version numbers shipped, PR numbers merged/closed, bugs fixed or deferred.
|
|
8455
|
+
- Key lessons: what failed and why ("tried X, failed because Y"). These prevent repeating mistakes.
|
|
8456
|
+
- Critical constraints discovered ("must support Node 22", "AGENTS.md forbids as any").
|
|
8457
|
+
- Design decisions with architectural impact ("chose compress-as-anchor over synthetic messages because prefix cache").
|
|
8458
|
+
- Whether content is OBSOLETE or SUPERSEDED \u2014 mark with one line: "[SUPERSEDED by PR #NNN]" or "[OBSOLETE: deleted in vX.Y.Z]". Do NOT keep the obsolete content's details \u2014 just the marker and reason.
|
|
8459
|
+
- Function/class/type names and module paths that are the SUBJECT of the work \u2014 e.g., "fixed filterCompressedRanges in prune.ts", "added SessionStateRegistry in state.ts". Not exact line numbers or full signatures \u2014 just enough to LOCATE the code without searching.
|
|
8460
|
+
- Exploration findings: if a block was exploratory with no decision, keep the CONCLUSION in one line ("explored X, not viable because Y"). Do not keep the exploration process.
|
|
8461
|
+
|
|
8462
|
+
DROP \u2014 these were useful during the work but are no longer needed:
|
|
8463
|
+
- Exact line numbers, diffs, verbose function signatures, full code listings.
|
|
8464
|
+
- Build/deploy process details, test execution steps.
|
|
8465
|
+
- Review process details (who reviewed, what rounds, test counts).
|
|
8466
|
+
- Verbose logs, command output, intermediate debugging steps.
|
|
8467
|
+
|
|
8468
|
+
FORMAT:
|
|
8469
|
+
- Start each distilled block with a source header line:
|
|
8470
|
+
\`Source: bN+bM+... (XK\u2192YK tok, Zx). [original topic]\`
|
|
8471
|
+
Example: \`Source: b5+b7 (56K+44K\u2192268 tok, 375x). [Tool-result recap + publish]\`
|
|
8472
|
+
- 3-5 bullet points per source block, each a self-contained fact.
|
|
8473
|
+
- Dense, scannable \u2014 no narrative prose.
|
|
8474
|
+
- Start with the outcome, not the process: "v1.13.0 shipped (7 PRs bundled)" not "implemented 7 PRs then reviewed then merged".
|
|
8475
|
+
- Cross-block synthesis: if multiple source blocks cover the same topic (same PR, same feature, same bug), MERGE them into a single group of bullets. Do not repeat the same fact from different blocks \u2014 keep it once under the most relevant source header.
|
|
8476
|
+
|
|
8477
|
+
SIZE TARGET: 50-150 tokens per source block (excluding the header). If you can't fit it in 150 tokens, you're keeping too much process. If a block has nothing worth keeping (pure noise), output just the header followed by "[no actionable content]."`;
|
|
8478
|
+
var TIER3_CONDENSE_RULES2 = `TIER 3 COMPRESSION \u2014 ULTRA-CONDENSATION
|
|
8479
|
+
|
|
8480
|
+
You are compressing distilled summaries (Tier 2) into ultra-condensed facts (Tier 3). The distilled summaries already contain only decisions and outcomes. Your job is to reduce them to bare factual references.
|
|
8481
|
+
|
|
8482
|
+
PRIORITY \u2014 when a source block has more facts than the size target allows, keep in this order:
|
|
8483
|
+
1. Shipped outcomes (versions released, PRs merged) \u2014 these are permanent record.
|
|
8484
|
+
2. Open work (PRs/issues still pending) \u2014 these may need follow-up.
|
|
8485
|
+
3. Key decisions with architectural impact ("chose X over Y because Z").
|
|
8486
|
+
4. Critical constraints ("must support Node 22").
|
|
8487
|
+
Drop everything else. Tier 3 is a lookup index, not a knowledge base.
|
|
8488
|
+
|
|
8489
|
+
FORMAT:
|
|
8490
|
+
- Start with a source header line:
|
|
8491
|
+
\`Source: bN+bM+... (XK\u2192YK tok, Zx). [original topic]\`
|
|
8492
|
+
- Output 1-3 facts per source block. Each fact is a single line: subject + outcome.
|
|
8493
|
+
- No explanations, no rationale, no process \u2014 just the fact.
|
|
8494
|
+
- Format: "[PR/Issue/Version] \u2014 [outcome in \u22648 words]"
|
|
8495
|
+
- Merge related facts from different source blocks if they concern the same topic.
|
|
8496
|
+
|
|
8497
|
+
EXAMPLES:
|
|
8498
|
+
- "v1.13.0 shipped \u2014 quality gate + GC fix (7 PRs)"
|
|
8499
|
+
- "PR #196 merged \u2014 preserve-first-user (supersedes #169)"
|
|
8500
|
+
- "Bug 1214 fixed \u2014 compress consumed all user messages"
|
|
8501
|
+
- "Chose compress-as-anchor \u2014 prefix cache benefit over synthetic injection"
|
|
8502
|
+
- "Constraint: AGENTS.md forbids as any \u2014 never suppress types"
|
|
8503
|
+
|
|
8504
|
+
DROP:
|
|
8505
|
+
- Multi-sentence context. If a fact needs >1 sentence, it's too detailed for Tier 3.
|
|
8506
|
+
- Lessons learned ("tried X, failed because Y") \u2014 drop UNLESS the failure is likely to recur and the block is <30 days old.
|
|
8507
|
+
- Design rationale details \u2014 keep the decision, drop the "because" unless it's a critical constraint.
|
|
8508
|
+
- Anything marked [OBSOLETE] or [SUPERSEDED] \u2014 drop entirely, note "[N blocks obsolete]" in the summary.
|
|
8509
|
+
|
|
8510
|
+
SIZE TARGET: 30-60 tokens per source block (including header). For a batch of N source blocks, total output \u2248 N \xD7 40 tokens. If a source block has only one trivial fact, output just the header + one line.`;
|
|
8511
|
+
var defaultPrompts2 = Object.freeze({
|
|
8512
|
+
compressPhilosophy: COMPRESS_PHILOSOPHY2,
|
|
8513
|
+
howToCompressRules: HOW_TO_COMPRESS_RULES2,
|
|
8514
|
+
tier2DistillRules: TIER2_DISTILL_RULES2,
|
|
8515
|
+
tier3CondenseRules: TIER3_CONDENSE_RULES2
|
|
8516
|
+
});
|
|
8517
|
+
function formatK3(n) {
|
|
8518
|
+
if (n >= 1e3) return `${(n / 1e3).toFixed(1)}K`;
|
|
8519
|
+
return `${n}`;
|
|
8520
|
+
}
|
|
8521
|
+
function formatRanges2(compressible, protectedRanges) {
|
|
8522
|
+
if (compressible.length === 0 && protectedRanges.length === 0) {
|
|
8523
|
+
return "[No specific ranges detected \u2014 compress any consumed content.]";
|
|
8524
|
+
}
|
|
8525
|
+
const refNum2 = (ref) => {
|
|
8526
|
+
const m = ref.match(/\d+/);
|
|
8527
|
+
return m ? parseInt(m[0], 10) : 0;
|
|
8528
|
+
};
|
|
8529
|
+
const entries = [];
|
|
8530
|
+
for (const r of compressible) {
|
|
8531
|
+
entries.push({
|
|
8532
|
+
startRef: r.startRef,
|
|
8533
|
+
endRef: r.endRef,
|
|
8534
|
+
startNum: refNum2(r.startRef),
|
|
8535
|
+
endNum: refNum2(r.endRef),
|
|
8536
|
+
count: r.count,
|
|
8537
|
+
tokens: r.tokens,
|
|
8538
|
+
toolPct: r.toolPct,
|
|
8539
|
+
textPct: r.textPct,
|
|
8540
|
+
compressibleTokens: r.tokens,
|
|
8541
|
+
compressibleCount: r.count,
|
|
8542
|
+
protectedTokens: 0,
|
|
8543
|
+
protectedCount: 0,
|
|
8544
|
+
protectedTools: [],
|
|
8545
|
+
dangerous: r.dangerous ?? false
|
|
8546
|
+
});
|
|
8547
|
+
}
|
|
8548
|
+
for (const r of protectedRanges) {
|
|
8549
|
+
entries.push({
|
|
8550
|
+
startRef: r.startRef,
|
|
8551
|
+
endRef: r.endRef,
|
|
8552
|
+
startNum: refNum2(r.startRef),
|
|
8553
|
+
endNum: refNum2(r.endRef),
|
|
8554
|
+
count: r.count,
|
|
8555
|
+
tokens: r.tokens,
|
|
8556
|
+
toolPct: 0,
|
|
8557
|
+
textPct: 0,
|
|
8558
|
+
compressibleTokens: 0,
|
|
8559
|
+
compressibleCount: 0,
|
|
8560
|
+
protectedTokens: r.tokens,
|
|
8561
|
+
protectedCount: r.count,
|
|
8562
|
+
protectedTools: [...r.tools],
|
|
8563
|
+
dangerous: false
|
|
8564
|
+
});
|
|
8565
|
+
}
|
|
8566
|
+
entries.sort((a, b) => a.startNum - b.startNum);
|
|
8567
|
+
const merged = [];
|
|
8568
|
+
for (const e of entries) {
|
|
8569
|
+
const last = merged[merged.length - 1];
|
|
8570
|
+
if (last && e.startNum <= last.endNum + 1) {
|
|
8571
|
+
last.endRef = e.endRef;
|
|
8572
|
+
last.endNum = Math.max(last.endNum, e.endNum);
|
|
8573
|
+
last.count += e.count;
|
|
8574
|
+
last.tokens += e.tokens;
|
|
8575
|
+
last.compressibleTokens += e.compressibleTokens;
|
|
8576
|
+
last.compressibleCount += e.compressibleCount;
|
|
8577
|
+
last.protectedTokens += e.protectedTokens;
|
|
8578
|
+
last.protectedCount += e.protectedCount;
|
|
8579
|
+
if (e.dangerous) last.dangerous = true;
|
|
8580
|
+
for (const t of e.protectedTools) {
|
|
8581
|
+
if (!last.protectedTools.includes(t)) last.protectedTools.push(t);
|
|
8582
|
+
}
|
|
8583
|
+
} else {
|
|
8584
|
+
merged.push({ ...e });
|
|
8585
|
+
}
|
|
8586
|
+
}
|
|
8587
|
+
const lines = merged.map((e) => {
|
|
8588
|
+
const suffix = e.dangerous && e.compressibleTokens > 0 ? " \u26A0\uFE0F NOT recommended unless you are certain." : "";
|
|
8589
|
+
if (e.protectedTokens > 0 && e.compressibleTokens === 0) {
|
|
8590
|
+
return ` ${e.startRef}\u2013${e.endRef} ${e.count} msgs ${formatK3(e.tokens)} [PROTECTED: ${e.protectedTools.join(", ")} \u2014 not compressible]${suffix}`;
|
|
8591
|
+
}
|
|
8592
|
+
if (e.protectedTokens > 0 && e.compressibleTokens > 0) {
|
|
8593
|
+
return ` ${e.startRef}\u2013${e.endRef} ${e.count} msgs ${formatK3(e.tokens)} [${formatK3(e.compressibleTokens)} compressible | ${formatK3(e.protectedTokens)} protected: ${e.protectedTools.join(", ")}]${suffix}`;
|
|
8594
|
+
}
|
|
8595
|
+
return ` ${e.startRef}\u2013${e.endRef} ${e.count} msgs ${formatK3(e.tokens)} [tool ${e.toolPct}% | text ${e.textPct}%]${suffix}`;
|
|
8596
|
+
});
|
|
8597
|
+
return `Compressible ranges (${merged.length}, oldest first):
|
|
8598
|
+
${lines.join("\n")}`;
|
|
8599
|
+
}
|
|
8600
|
+
var substringAlgorithm2 = {
|
|
8601
|
+
name: "substring",
|
|
8602
|
+
description: "Exact substring counting (original baseline). Predictable, no normalization.",
|
|
8603
|
+
score(docs, query) {
|
|
8604
|
+
const terms = query.toLowerCase().trim().split(/\s+/).filter((t) => t.length > 0);
|
|
8605
|
+
if (terms.length === 0) return docs.map((d) => ({ ref: d.ref, score: 0 }));
|
|
8606
|
+
return docs.map((d) => {
|
|
8607
|
+
const haystack = d.text.toLowerCase();
|
|
8608
|
+
let score = 0;
|
|
8609
|
+
for (const term of terms) score += countOccurrences22(haystack, term);
|
|
8610
|
+
return { ref: d.ref, score };
|
|
8611
|
+
});
|
|
8612
|
+
}
|
|
8613
|
+
};
|
|
8614
|
+
function countOccurrences22(haystack, needle) {
|
|
8615
|
+
if (!needle) return 0;
|
|
8616
|
+
return haystack.split(needle).length - 1;
|
|
8617
|
+
}
|
|
8618
|
+
function stem2(word) {
|
|
8619
|
+
let w = word;
|
|
8620
|
+
if (w.length <= 3) return w;
|
|
8621
|
+
if (w.endsWith("ies")) w = w.slice(0, -3) + "y";
|
|
8622
|
+
else if (w.endsWith("ses") || w.endsWith("xes") || w.endsWith("zes")) w = w.slice(0, -2);
|
|
8623
|
+
else if (w.endsWith("ches") || w.endsWith("shes")) w = w.slice(0, -2);
|
|
8624
|
+
else if (w.endsWith("s") && !w.endsWith("ss")) w = w.slice(0, -1);
|
|
8625
|
+
if (w.endsWith("ing") && w.length > 5) w = w.slice(0, -3);
|
|
8626
|
+
if (w.endsWith("ed") && w.length > 4) w = w.slice(0, -2);
|
|
8627
|
+
if (w.endsWith("ation") && w.length > 6) w = w.slice(0, -3);
|
|
8628
|
+
else if (w.endsWith("tion") && w.length > 5) w = w.slice(0, -4) + "t";
|
|
8629
|
+
else if (w.endsWith("ion") && w.length > 4) w = w.slice(0, -3);
|
|
8630
|
+
if (w.endsWith("ment") && w.length > 6) w = w.slice(0, -4);
|
|
8631
|
+
if (w.endsWith("ness") && w.length > 6) w = w.slice(0, -4);
|
|
8632
|
+
if (w.endsWith("ly") && w.length > 4) w = w.slice(0, -2);
|
|
8633
|
+
return w;
|
|
8634
|
+
}
|
|
8635
|
+
var CJK2 = /[\u3400-\u9fff\uf900-\ufaff\u3040-\u30ff\uac00-\ud7af]/;
|
|
8636
|
+
var CJK_RUN2 = new RegExp(`${CJK2.source}+`, "g");
|
|
8637
|
+
var LATIN_WORD2 = /[a-z][a-z0-9_]*[a-z0-9]|[a-z0-9]/g;
|
|
8638
|
+
function tokenize2(text, opts = {}) {
|
|
8639
|
+
const lower = text.toLowerCase();
|
|
8640
|
+
const tokens = [];
|
|
8641
|
+
const latin = lower.match(LATIN_WORD2) ?? [];
|
|
8642
|
+
for (let w of latin) {
|
|
8643
|
+
if (w.length >= 2) {
|
|
8644
|
+
if (opts.stem) w = stem2(w);
|
|
8645
|
+
tokens.push(w);
|
|
8646
|
+
}
|
|
8647
|
+
}
|
|
8648
|
+
const cjkRuns = lower.match(CJK_RUN2) ?? [];
|
|
8649
|
+
for (const run of cjkRuns) {
|
|
8650
|
+
if (run.length === 1) {
|
|
8651
|
+
tokens.push(run);
|
|
8652
|
+
} else {
|
|
8653
|
+
for (let i = 0; i < run.length - 1; i++) tokens.push(run.slice(i, i + 2));
|
|
8654
|
+
for (const ch of run) tokens.push(ch);
|
|
8655
|
+
}
|
|
8656
|
+
}
|
|
8657
|
+
return tokens;
|
|
8658
|
+
}
|
|
8659
|
+
function charBigrams2(text) {
|
|
8660
|
+
const grams = [];
|
|
8661
|
+
for (let i = 0; i < text.length - 1; i++) {
|
|
8662
|
+
const pair = text.slice(i, i + 2);
|
|
8663
|
+
if (pair.trim().length === pair.length) grams.push(pair);
|
|
8664
|
+
}
|
|
8665
|
+
return grams;
|
|
8666
|
+
}
|
|
8667
|
+
function tfMap2(text, stem22) {
|
|
8668
|
+
const m = /* @__PURE__ */ new Map();
|
|
8669
|
+
for (const t of tokenize2(text, { stem: stem22 })) m.set(t, (m.get(t) ?? 0) + 1);
|
|
8670
|
+
return m;
|
|
8671
|
+
}
|
|
8672
|
+
var bm25Algorithm2 = {
|
|
8673
|
+
name: "bm25",
|
|
8674
|
+
description: "BM25 with stemming + CJK bigram tokenization. IR-standard relevance ranking.",
|
|
8675
|
+
score(docs, query) {
|
|
8676
|
+
const N = docs.length;
|
|
8677
|
+
const k1 = 1.2;
|
|
8678
|
+
const b = 0.75;
|
|
8679
|
+
const parsed = docs.map((d) => {
|
|
8680
|
+
const text = d.text;
|
|
8681
|
+
const tf = tfMap2(text, true);
|
|
8682
|
+
let len = 0;
|
|
8683
|
+
for (const v of tf.values()) len += v;
|
|
8684
|
+
return { id: d.ref, tf, len };
|
|
8685
|
+
});
|
|
8686
|
+
const avgdl = parsed.reduce((s, d) => s + d.len, 0) / (N || 1);
|
|
8687
|
+
const qTerms = tokenize2(query, { stem: true });
|
|
8688
|
+
if (qTerms.length === 0) return docs.map((d) => ({ ref: d.ref, score: 0 }));
|
|
8689
|
+
const idf = /* @__PURE__ */ new Map();
|
|
8690
|
+
for (const t of new Set(qTerms)) {
|
|
8691
|
+
let df = 0;
|
|
8692
|
+
for (const d of parsed) if (d.tf.has(t)) df++;
|
|
8693
|
+
idf.set(t, Math.log(1 + (N - df + 0.5) / (df + 0.5)));
|
|
8694
|
+
}
|
|
8695
|
+
return parsed.map((d) => {
|
|
8696
|
+
let score = 0;
|
|
8697
|
+
for (const t of qTerms) {
|
|
8698
|
+
const f = d.tf.get(t) ?? 0;
|
|
8699
|
+
if (f === 0) continue;
|
|
8700
|
+
const idfT = idf.get(t) ?? 0;
|
|
8701
|
+
score += idfT * (f * (k1 + 1)) / (f + k1 * (1 - b + b * d.len / (avgdl || 1)));
|
|
8702
|
+
}
|
|
8703
|
+
return { ref: d.id, score };
|
|
8704
|
+
});
|
|
8705
|
+
}
|
|
8706
|
+
};
|
|
8707
|
+
var fuzzyAlgorithm2 = {
|
|
8708
|
+
name: "fuzzy",
|
|
8709
|
+
description: "Character bigram overlap. Typo-tolerant, script-agnostic, high recall.",
|
|
8710
|
+
score(docs, query) {
|
|
8711
|
+
const qTokens = query.toLowerCase().split(/[\s,]+/).filter((t) => t.length >= 4);
|
|
8712
|
+
if (qTokens.length === 0) return docs.map((d) => ({ ref: d.ref, score: 0 }));
|
|
8713
|
+
const qGrams = /* @__PURE__ */ new Set();
|
|
8714
|
+
for (const t of qTokens) for (const g of charBigrams2(t)) qGrams.add(g);
|
|
8715
|
+
if (qGrams.size === 0) return docs.map((d) => ({ ref: d.ref, score: 0 }));
|
|
8716
|
+
return docs.map((d) => {
|
|
8717
|
+
const haystack = d.text.toLowerCase();
|
|
8718
|
+
const docGrams = new Set(charBigrams2(haystack));
|
|
8719
|
+
let hits = 0;
|
|
8720
|
+
for (const g of qGrams) if (docGrams.has(g)) hits++;
|
|
8721
|
+
return { ref: d.ref, score: hits / qGrams.size };
|
|
8722
|
+
});
|
|
8723
|
+
}
|
|
8724
|
+
};
|
|
8725
|
+
var W_BM252 = 0.7;
|
|
8726
|
+
var W_FUZZY2 = 0.3;
|
|
8727
|
+
var hybridAlgorithm2 = {
|
|
8728
|
+
name: "hybrid",
|
|
8729
|
+
description: "Weighted BM25(stem) + fuzzy n-gram. Default \u2014 best precision + recall.",
|
|
8730
|
+
score(docs, query) {
|
|
8731
|
+
const bm = bm25Algorithm2.score(docs, query);
|
|
8732
|
+
const fz = fuzzyAlgorithm2.score(docs, query);
|
|
8733
|
+
const maxBm = Math.max(...bm.map((r) => r.score), 1e-9);
|
|
8734
|
+
const maxFz = Math.max(...fz.map((r) => r.score), 1e-9);
|
|
8735
|
+
const bmMap = new Map(bm.map((r) => [r.ref, r.score / maxBm]));
|
|
8736
|
+
const fzMap = new Map(fz.map((r) => [r.ref, r.score / maxFz]));
|
|
8737
|
+
return docs.map((d) => ({
|
|
8738
|
+
ref: d.ref,
|
|
8739
|
+
score: W_BM252 * (bmMap.get(d.ref) ?? 0) + W_FUZZY2 * (fzMap.get(d.ref) ?? 0)
|
|
8740
|
+
}));
|
|
8741
|
+
}
|
|
8742
|
+
};
|
|
8743
|
+
var registry22 = /* @__PURE__ */ new Map();
|
|
8744
|
+
function registerSearchAlgorithm2(algo) {
|
|
8745
|
+
registry22.set(algo.name, algo);
|
|
8746
|
+
}
|
|
8747
|
+
registerSearchAlgorithm2(substringAlgorithm2);
|
|
8748
|
+
registerSearchAlgorithm2(bm25Algorithm2);
|
|
8749
|
+
registerSearchAlgorithm2(fuzzyAlgorithm2);
|
|
8750
|
+
registerSearchAlgorithm2(hybridAlgorithm2);
|
|
8751
|
+
|
|
8752
|
+
// node_modules/billion-context-kit/dist/index.js
|
|
8753
|
+
var VIABLE_RANGE_MIN_TOKENS = 200;
|
|
8754
|
+
function viableRanges(ranges) {
|
|
8755
|
+
return ranges.filter((r) => r.tokens >= VIABLE_RANGE_MIN_TOKENS);
|
|
8756
|
+
}
|
|
8757
|
+
function topicFallback(summary) {
|
|
8758
|
+
const first = summary.split(/[.\n]/)[0] ?? "";
|
|
8759
|
+
const t = first.trim().replace(/^["'`]+/, "").trim();
|
|
8760
|
+
return t.length <= 30 ? t : `${t.slice(0, 30).trimEnd()}\u2026`;
|
|
8761
|
+
}
|
|
8762
|
+
function formatCompactTokens(count) {
|
|
8763
|
+
if (count < 1e3) return count.toString();
|
|
8764
|
+
if (count < 1e4) return `${(count / 1e3).toFixed(1)}k`;
|
|
8765
|
+
if (count < 1e6) return `${Math.round(count / 1e3)}k`;
|
|
8766
|
+
if (count < 1e7) return `${(count / 1e6).toFixed(1)}M`;
|
|
8767
|
+
return `${Math.round(count / 1e6)}M`;
|
|
8768
|
+
}
|
|
8769
|
+
function bar(value, total, width = 20) {
|
|
8770
|
+
if (total === 0) return "";
|
|
8771
|
+
const filled = Math.max(0, Math.min(width, Math.round(value / total * width)));
|
|
8772
|
+
return "\u2588".repeat(filled) + "\u2591".repeat(width - filled);
|
|
8773
|
+
}
|
|
8774
|
+
function buildStatusPanel(input) {
|
|
8775
|
+
const { tokenCount, state, nudge, modelContextLimit } = input;
|
|
8776
|
+
const fmt2 = input.fmtTokens ?? formatCompactTokens;
|
|
8777
|
+
const bd = nudge?.contextBreakdown;
|
|
8778
|
+
const limit = modelContextLimit;
|
|
8779
|
+
const classified = bd ? bd.system + bd.tool + bd.summaries + bd.code + bd.text : 0;
|
|
8780
|
+
const systemPromptTokens = input.systemPromptTokens;
|
|
8781
|
+
const sentTotal = classified + systemPromptTokens;
|
|
8782
|
+
const sessionOnly = input.unprunedTokens !== void 0 ? Math.max(0, input.unprunedTokens - sentTotal) : 0;
|
|
8783
|
+
const displayTotal = tokenCount;
|
|
8784
|
+
const displayPct = limit > 0 ? Math.round(displayTotal / limit * 100) : 0;
|
|
8785
|
+
const sentPct = limit > 0 ? Math.round(sentTotal / limit * 100) : 0;
|
|
8786
|
+
const activeBlocksList = state.blocks.filter((b) => b.active);
|
|
8787
|
+
const totalBlocksList = state.blocks;
|
|
8788
|
+
const lines = [];
|
|
8789
|
+
lines.push("\u256D\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E");
|
|
8790
|
+
lines.push("\u2502 ACP Context Analysis \u2502");
|
|
8791
|
+
lines.push("\u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F");
|
|
8792
|
+
if (input.version) lines.push(input.version);
|
|
8793
|
+
lines.push("");
|
|
8794
|
+
lines.push(`Context (session accounting, host footer scale): ${displayPct}% (${fmt2(displayTotal)} / ${fmt2(limit)}) \u2014 never shrinks; includes compressed originals`);
|
|
8795
|
+
if (nudge && bd) {
|
|
8796
|
+
const growth = bd.growth;
|
|
8797
|
+
if (growth > 0 && displayTotal > 0) {
|
|
8798
|
+
lines.push(`Growth: +${fmt2(growth)} since last nudge`);
|
|
8799
|
+
}
|
|
8800
|
+
lines.push("");
|
|
8801
|
+
lines.push(`Sent to LLM (after compression, est.): ${fmt2(sentTotal)}${limit > 0 ? ` (${sentPct}% of limit)` : ""}`);
|
|
8802
|
+
if (input.unprunedTokens !== void 0 && sessionOnly > 0) {
|
|
8803
|
+
lines.push(`Session-only (compressed originals, est.): ${fmt2(sessionOnly)} \u2014 pruned from every request; the footer/nudge still count them`);
|
|
8804
|
+
}
|
|
8805
|
+
lines.push("");
|
|
8806
|
+
lines.push("Token Breakdown (sent view):");
|
|
8807
|
+
const categories = [
|
|
8808
|
+
{ label: "Tool", value: bd.tool },
|
|
8809
|
+
{ label: "SysPrompt", value: systemPromptTokens },
|
|
8810
|
+
{ label: "Text", value: bd.text },
|
|
8811
|
+
{ label: "Code", value: bd.code },
|
|
8812
|
+
{ label: "Summaries", value: bd.summaries }
|
|
8813
|
+
];
|
|
8814
|
+
for (const cat of categories) {
|
|
8815
|
+
if (cat.value <= 0) continue;
|
|
8816
|
+
const pct2 = sentTotal > 0 ? Math.round(cat.value / sentTotal * 100) : 0;
|
|
8817
|
+
const b = bar(cat.value, sentTotal);
|
|
8818
|
+
lines.push(` ${cat.label.padEnd(10)} ${b} ${String(pct2).padStart(3)}% ${fmt2(cat.value)}`);
|
|
8819
|
+
}
|
|
8820
|
+
}
|
|
8821
|
+
lines.push("");
|
|
8822
|
+
if (nudge) {
|
|
8823
|
+
if (nudge.shouldInject) {
|
|
8824
|
+
const tierInfo = nudge.tier ? ` [T${nudge.tier} distillation]` : "";
|
|
8825
|
+
lines.push(`Nudge: ACTIVE${tierInfo} \u2014 ${nudge.reason}`);
|
|
8826
|
+
} else {
|
|
8827
|
+
lines.push(`Nudge: idle \u2014 ${nudge.reason}`);
|
|
8828
|
+
}
|
|
8829
|
+
}
|
|
8830
|
+
const ranges = viableRanges(nudge?.compressibleRanges ?? []);
|
|
8831
|
+
const protectedRanges = nudge?.protectedRanges ?? [];
|
|
8832
|
+
if (ranges.length > 0 || protectedRanges.length > 0) {
|
|
8833
|
+
lines.push("");
|
|
8834
|
+
lines.push(formatRanges2(ranges, protectedRanges));
|
|
8835
|
+
}
|
|
8836
|
+
if (activeBlocksList.length > 0) {
|
|
8837
|
+
lines.push("");
|
|
8838
|
+
lines.push(`Blocks: ${activeBlocksList.length} active / ${totalBlocksList.length} total (${fmt2(state.stats.tokensCompressed)} tokens compressed)`);
|
|
8839
|
+
for (const b of activeBlocksList) {
|
|
8840
|
+
const topic = b.topic ? `: ${b.topic}` : `: ${topicFallback(b.summary || "")}`;
|
|
8841
|
+
const summaryTok = defaultCountTokens2(b.summary || "");
|
|
8842
|
+
const origTok = b.compressedTokens > 0 ? b.compressedTokens : summaryTok;
|
|
8843
|
+
lines.push(` [${b.blockId}] T${b.tier} ${fmt2(origTok)}\u2192${fmt2(summaryTok)}${topic}`);
|
|
8844
|
+
}
|
|
8845
|
+
} else if (totalBlocksList.length > 0) {
|
|
8846
|
+
lines.push("");
|
|
8847
|
+
lines.push(`Blocks: 0 active / ${totalBlocksList.length} total (${fmt2(state.stats.tokensCompressed)} tokens compressed)`);
|
|
8848
|
+
} else {
|
|
8849
|
+
lines.push("");
|
|
8850
|
+
lines.push("Blocks: none (nothing compressed yet)");
|
|
8851
|
+
}
|
|
8852
|
+
lines.push("");
|
|
8853
|
+
lines.push("Tag visibility: tags injected to LLM only (deep copy), not persisted in session, not shown in terminal.");
|
|
8854
|
+
return lines.join("\n");
|
|
8855
|
+
}
|
|
8856
|
+
|
|
8857
|
+
// src/delegate-tool.ts
|
|
8858
|
+
import {
|
|
8859
|
+
spawn
|
|
8860
|
+
} from "child_process";
|
|
8861
|
+
import { createWriteStream, existsSync as existsSync2 } from "fs";
|
|
8862
|
+
import { mkdir as mkdir2, mkdtemp, writeFile as writeFile2, rm, appendFile } from "fs/promises";
|
|
8863
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
8864
|
+
import { dirname as dirname3, join as join4, resolve as resolvePath } from "path";
|
|
8865
|
+
|
|
8866
|
+
// src/footer-status.ts
|
|
8867
|
+
var FOOTER_STATUS_KEY = "billion-context-pi";
|
|
8868
|
+
var ui;
|
|
8869
|
+
var lastFooterText = "";
|
|
8870
|
+
function formatCompactTokens2(count) {
|
|
8871
|
+
if (count < 1e3) return count.toString();
|
|
8872
|
+
if (count < 1e4) return `${(count / 1e3).toFixed(1)}k`;
|
|
8873
|
+
if (count < 1e6) return `${Math.round(count / 1e3)}k`;
|
|
8874
|
+
if (count < 1e7) return `${(count / 1e6).toFixed(1)}M`;
|
|
8875
|
+
return `${Math.round(count / 1e6)}M`;
|
|
8876
|
+
}
|
|
8877
|
+
function initFooterStatus(ctx) {
|
|
8878
|
+
ui = ctx.ui;
|
|
8879
|
+
lastFooterText = void 0;
|
|
8880
|
+
}
|
|
8881
|
+
function updateFooterStatus() {
|
|
8882
|
+
if (!ui) return;
|
|
8883
|
+
const usage = getDelegateUsage();
|
|
8884
|
+
let text;
|
|
8885
|
+
if (usage && usage.totalTokens > 0) {
|
|
8886
|
+
const costStr = usage.cost.total > 0 ? ` ($${usage.cost.total.toFixed(4)})` : "";
|
|
8887
|
+
text = `sub-agents \u2191${formatCompactTokens2(usage.input)} \u2193${formatCompactTokens2(usage.output)}${costStr}`;
|
|
8888
|
+
}
|
|
8889
|
+
if ((text ?? "") === lastFooterText) return;
|
|
8890
|
+
lastFooterText = text ?? "";
|
|
8891
|
+
try {
|
|
8892
|
+
ui.setStatus(FOOTER_STATUS_KEY, text);
|
|
8893
|
+
} catch {
|
|
8894
|
+
}
|
|
8895
|
+
}
|
|
8896
|
+
function disposeFooterStatus() {
|
|
8897
|
+
if (ui) {
|
|
8898
|
+
try {
|
|
8899
|
+
ui.setStatus(FOOTER_STATUS_KEY, void 0);
|
|
8900
|
+
} catch {
|
|
8901
|
+
}
|
|
8902
|
+
}
|
|
8903
|
+
ui = void 0;
|
|
8904
|
+
lastFooterText = "";
|
|
8905
|
+
}
|
|
8906
|
+
|
|
8907
|
+
// src/fleet-widget.ts
|
|
8908
|
+
var DELEGATE_WIDGET_KEY = "billion-context-pi-delegates";
|
|
8909
|
+
var REFRESH_MS = 500;
|
|
8910
|
+
var MAX_TASK_LEN = 48;
|
|
8911
|
+
var ui2;
|
|
8912
|
+
var timer;
|
|
8913
|
+
var lastRenderKey = "";
|
|
8914
|
+
var runsSnapshot;
|
|
8915
|
+
function truncateTask(task) {
|
|
8916
|
+
const oneLine = task.replace(/\n/g, " ").trim();
|
|
8917
|
+
if (oneLine.length <= MAX_TASK_LEN) return oneLine;
|
|
8918
|
+
return `${oneLine.slice(0, MAX_TASK_LEN - 1)}\u2026`;
|
|
8919
|
+
}
|
|
8920
|
+
function renderLines(runs2) {
|
|
8921
|
+
if (runs2.length === 0) return void 0;
|
|
8922
|
+
const now = Date.now();
|
|
8923
|
+
const header = runs2.length === 1 ? `acp_delegate \xB7 1 running` : `acp_delegate \xB7 ${runs2.length} running`;
|
|
8924
|
+
const rows = runs2.map((r) => {
|
|
8925
|
+
const elapsed = Math.max(0, Math.round((now - r.startedAt) / 1e3));
|
|
8926
|
+
return ` \u25CF ${r.agent} (${elapsed}s) \u2014 ${truncateTask(r.task)}`;
|
|
8927
|
+
});
|
|
8928
|
+
return [header, ...rows];
|
|
8929
|
+
}
|
|
8930
|
+
function renderKeyFor(runs2) {
|
|
8931
|
+
return runs2.map((r) => `${r.agent}:${Math.round((Date.now() - r.startedAt) / 1e3)}:${truncateTask(r.task)}`).join("|");
|
|
8932
|
+
}
|
|
8933
|
+
function stopTimer() {
|
|
8934
|
+
if (timer) {
|
|
8935
|
+
clearInterval(timer);
|
|
8936
|
+
timer = void 0;
|
|
8937
|
+
}
|
|
8938
|
+
}
|
|
8939
|
+
function clearWidget() {
|
|
8940
|
+
if (!ui2) return;
|
|
8941
|
+
try {
|
|
8942
|
+
ui2.setWidget(DELEGATE_WIDGET_KEY, void 0);
|
|
8943
|
+
} catch {
|
|
8944
|
+
}
|
|
8945
|
+
}
|
|
8946
|
+
function refresh() {
|
|
8947
|
+
if (!ui2) return;
|
|
8948
|
+
const runs2 = runsSnapshot ? runsSnapshot() : [];
|
|
8949
|
+
if (runs2.length === 0) {
|
|
8950
|
+
if (lastRenderKey !== "") {
|
|
8951
|
+
lastRenderKey = "";
|
|
8952
|
+
clearWidget();
|
|
8953
|
+
}
|
|
8954
|
+
updateFooterStatus();
|
|
8955
|
+
stopTimer();
|
|
8075
8956
|
return;
|
|
8076
8957
|
}
|
|
8077
8958
|
const sorted = [...runs2].sort((a, b) => a.startedAt - b.startedAt);
|
|
@@ -8393,6 +9274,44 @@ function delegateSpawnOptions(cwd, env) {
|
|
|
8393
9274
|
shell: false
|
|
8394
9275
|
};
|
|
8395
9276
|
}
|
|
9277
|
+
var PI_CLI_ENTRY_RE = /[\\/]pi-coding-agent[\\/]dist[\\/]cli\.js$/;
|
|
9278
|
+
var PI_PACKAGE_REL = join4("@earendil-works", "pi-coding-agent", "dist", "cli.js");
|
|
9279
|
+
function probeUpFromArgv(argv1) {
|
|
9280
|
+
let dir = resolvePath(dirname3(argv1) || process.cwd());
|
|
9281
|
+
for (; ; ) {
|
|
9282
|
+
const candidate = join4(dir, "node_modules", PI_PACKAGE_REL);
|
|
9283
|
+
if (existsSync2(candidate)) return candidate;
|
|
9284
|
+
const parent = dirname3(dir);
|
|
9285
|
+
if (parent === dir) return null;
|
|
9286
|
+
dir = parent;
|
|
9287
|
+
}
|
|
9288
|
+
}
|
|
9289
|
+
function piCliGlobalCandidates(env) {
|
|
9290
|
+
const candidates = [];
|
|
9291
|
+
if (process.platform === "win32") {
|
|
9292
|
+
if (env.APPDATA) candidates.push(join4(env.APPDATA, "npm", "node_modules", PI_PACKAGE_REL));
|
|
9293
|
+
} else {
|
|
9294
|
+
const home = env.HOME ?? env.USERPROFILE;
|
|
9295
|
+
if (home) candidates.push(join4(home, ".local", "lib", "node_modules", PI_PACKAGE_REL));
|
|
9296
|
+
candidates.push(join4("/usr/local", "lib", "node_modules", PI_PACKAGE_REL));
|
|
9297
|
+
candidates.push(join4("/usr", "lib", "node_modules", PI_PACKAGE_REL));
|
|
9298
|
+
}
|
|
9299
|
+
return candidates;
|
|
9300
|
+
}
|
|
9301
|
+
function resolvePiCliEntry(argv1, env = process.env, piHost = true) {
|
|
9302
|
+
const explicit = env.PI_CLI_PATH;
|
|
9303
|
+
if (explicit) return explicit;
|
|
9304
|
+
if (argv1 && PI_CLI_ENTRY_RE.test(argv1)) return argv1;
|
|
9305
|
+
if (piHost) {
|
|
9306
|
+
const probed = probeUpFromArgv(argv1);
|
|
9307
|
+
if (probed) return probed;
|
|
9308
|
+
for (const candidate of piCliGlobalCandidates(env)) {
|
|
9309
|
+
if (existsSync2(candidate)) return candidate;
|
|
9310
|
+
}
|
|
9311
|
+
logWarn("delegate", { event: "cli-entry-unresolved", argv1, fallback: "argv[1]" });
|
|
9312
|
+
}
|
|
9313
|
+
return argv1;
|
|
9314
|
+
}
|
|
8396
9315
|
var ACP_TOOLS = ["compress", "decompress", "search_context", "acp_status"];
|
|
8397
9316
|
var RESTRICTED_TOOLS = "read,bash,grep,find,ls";
|
|
8398
9317
|
var AGENTS = {
|
|
@@ -8814,7 +9733,7 @@ async function runDelegate(pi, args, ctx, signal) {
|
|
|
8814
9733
|
logInfo("delegate", { event: "spawn", agent: args.agent, runId, cwd, async: isAsync, useJsonStream, mode: ctx.mode, parentDepth });
|
|
8815
9734
|
const child = spawn(
|
|
8816
9735
|
process.execPath,
|
|
8817
|
-
[process.argv[1], ...cliArgs],
|
|
9736
|
+
[resolvePiCliEntry(process.argv[1] ?? "", process.env, isPiHost(ctx.sessionManager)), ...cliArgs],
|
|
8818
9737
|
delegateSpawnOptions(cwd, childEnv)
|
|
8819
9738
|
);
|
|
8820
9739
|
child.stdin?.once("error", (e) => {
|
|
@@ -9187,13 +10106,15 @@ function makeStatusTool(runtime) {
|
|
|
9187
10106
|
async function handleStatus(args, runtime, ctx) {
|
|
9188
10107
|
const { state, coreMessages } = await runtime.stateFor(ctx);
|
|
9189
10108
|
const config = runtime.configFor(ctx);
|
|
9190
|
-
const
|
|
9191
|
-
const
|
|
10109
|
+
const coveredIds = collectCoveredMessageIds(state);
|
|
10110
|
+
const systemPromptText = getSystemPromptText(ctx);
|
|
10111
|
+
const systemPromptTokens = systemPromptText ? defaultCountTokens(systemPromptText) : 0;
|
|
10112
|
+
const sentTokens = estimateTokens(coreMessages, coveredIds) + systemPromptTokens;
|
|
9192
10113
|
const turn = runtime.core.processTurn({
|
|
9193
10114
|
messages: coreMessages,
|
|
9194
10115
|
state,
|
|
9195
10116
|
config,
|
|
9196
|
-
tokenCount:
|
|
10117
|
+
tokenCount: sentTokens
|
|
9197
10118
|
});
|
|
9198
10119
|
const processed = turn.messages;
|
|
9199
10120
|
const base = buildStatusReport(turn.state, processed, defaultCountTokens, {
|
|
@@ -9205,7 +10126,7 @@ async function handleStatus(args, runtime, ctx) {
|
|
|
9205
10126
|
});
|
|
9206
10127
|
if (args.scope) return base;
|
|
9207
10128
|
const nudge = turn.nudge;
|
|
9208
|
-
const ranges = nudge?.compressibleRanges ?? [];
|
|
10129
|
+
const ranges = viableRanges(nudge?.compressibleRanges ?? []);
|
|
9209
10130
|
const protectedRanges = nudge?.protectedRanges ?? [];
|
|
9210
10131
|
const extra = [];
|
|
9211
10132
|
if (nudge) {
|
|
@@ -9236,23 +10157,6 @@ async function handleStatus(args, runtime, ctx) {
|
|
|
9236
10157
|
${extra.join("\n")}` : base;
|
|
9237
10158
|
}
|
|
9238
10159
|
|
|
9239
|
-
// src/compat.ts
|
|
9240
|
-
function normalizeSystemPrompt(input) {
|
|
9241
|
-
if (input === void 0) return "";
|
|
9242
|
-
if (Array.isArray(input)) return input.join("\n");
|
|
9243
|
-
return input;
|
|
9244
|
-
}
|
|
9245
|
-
function formatSystemPromptForEvent(base, append) {
|
|
9246
|
-
const normalized = normalizeSystemPrompt(base);
|
|
9247
|
-
return `${normalized}
|
|
9248
|
-
|
|
9249
|
-
${append}`;
|
|
9250
|
-
}
|
|
9251
|
-
function getSystemPromptText(ctx) {
|
|
9252
|
-
const result = ctx.getSystemPrompt?.();
|
|
9253
|
-
return normalizeSystemPrompt(result);
|
|
9254
|
-
}
|
|
9255
|
-
|
|
9256
10160
|
// src/commands.ts
|
|
9257
10161
|
function makeCommands(runtime) {
|
|
9258
10162
|
return [
|
|
@@ -9320,110 +10224,39 @@ ${text}`);
|
|
|
9320
10224
|
}
|
|
9321
10225
|
];
|
|
9322
10226
|
}
|
|
9323
|
-
function fmtTokens(n) {
|
|
9324
|
-
return formatCompactTokens(n);
|
|
9325
|
-
}
|
|
9326
|
-
function bar(value, total, width = 20) {
|
|
9327
|
-
if (total === 0) return "";
|
|
9328
|
-
const filled = Math.max(0, Math.min(width, Math.round(value / total * width)));
|
|
9329
|
-
return "\u2588".repeat(filled) + "\u2591".repeat(width - filled);
|
|
9330
|
-
}
|
|
9331
10227
|
async function statusReport(runtime, ctx) {
|
|
9332
10228
|
const { state, coreMessages } = await runtime.stateFor(ctx);
|
|
9333
10229
|
const config = runtime.configFor(ctx);
|
|
9334
10230
|
const realUsage = ctx.getContextUsage?.();
|
|
9335
|
-
const tokenCount = realUsage?.tokens && realUsage.tokens > 0 ? realUsage.tokens : defaultCountTokens(coreMessages.map((m) => m.text ?? "").join("\n"));
|
|
9336
|
-
const turn = runtime.core.processTurn({ messages: coreMessages, state, config, tokenCount });
|
|
9337
|
-
const nudge = turn.nudge;
|
|
9338
|
-
const bd = nudge?.contextBreakdown;
|
|
9339
|
-
const limit = config.modelContextLimit;
|
|
9340
|
-
const classified = bd ? bd.system + bd.tool + bd.summaries + bd.code + bd.text : 0;
|
|
9341
10231
|
const systemPromptText = getSystemPromptText(ctx);
|
|
9342
10232
|
const systemPromptTokens = systemPromptText ? defaultCountTokens(systemPromptText) : 0;
|
|
9343
|
-
const
|
|
9344
|
-
const
|
|
9345
|
-
const
|
|
9346
|
-
const
|
|
9347
|
-
const
|
|
9348
|
-
|
|
9349
|
-
|
|
9350
|
-
|
|
9351
|
-
|
|
9352
|
-
|
|
9353
|
-
|
|
9354
|
-
|
|
9355
|
-
|
|
9356
|
-
|
|
9357
|
-
const growth = bd.growth;
|
|
9358
|
-
if (growth > 0 && displayTotal > 0) {
|
|
9359
|
-
lines.push(`Growth: +${fmtTokens(growth)} since last nudge`);
|
|
9360
|
-
}
|
|
9361
|
-
if (displayTotal > 0) {
|
|
9362
|
-
lines.push("");
|
|
9363
|
-
lines.push("Token Breakdown:");
|
|
9364
|
-
const categories = [
|
|
9365
|
-
{ label: "Tool", value: bd.tool },
|
|
9366
|
-
{ label: "SysPrompt", value: systemPromptTokens },
|
|
9367
|
-
{ label: "Framework", value: framework },
|
|
9368
|
-
{ label: "Text", value: bd.text },
|
|
9369
|
-
{ label: "Code", value: bd.code },
|
|
9370
|
-
{ label: "Summaries", value: bd.summaries }
|
|
9371
|
-
];
|
|
9372
|
-
for (const cat of categories) {
|
|
9373
|
-
if (cat.value <= 0) continue;
|
|
9374
|
-
const pct2 = displayTotal > 0 ? Math.round(cat.value / displayTotal * 100) : 0;
|
|
9375
|
-
const b = bar(cat.value, displayTotal);
|
|
9376
|
-
lines.push(` ${cat.label.padEnd(10)} ${b} ${String(pct2).padStart(3)}% ${fmtTokens(cat.value)}`);
|
|
9377
|
-
}
|
|
9378
|
-
}
|
|
9379
|
-
}
|
|
9380
|
-
lines.push("");
|
|
9381
|
-
if (nudge) {
|
|
9382
|
-
if (nudge.shouldInject) {
|
|
9383
|
-
const tierInfo = nudge.tier ? ` [T${nudge.tier} distillation]` : "";
|
|
9384
|
-
lines.push(`Nudge: ACTIVE${tierInfo} \u2014 ${nudge.reason}`);
|
|
9385
|
-
} else {
|
|
9386
|
-
lines.push(`Nudge: idle \u2014 ${nudge.reason}`);
|
|
9387
|
-
}
|
|
9388
|
-
}
|
|
9389
|
-
const ranges = nudge?.compressibleRanges ?? [];
|
|
9390
|
-
const protectedRanges = nudge?.protectedRanges ?? [];
|
|
9391
|
-
if (ranges.length > 0 || protectedRanges.length > 0) {
|
|
9392
|
-
lines.push("");
|
|
9393
|
-
lines.push(formatRanges(ranges, protectedRanges));
|
|
9394
|
-
}
|
|
9395
|
-
if (activeBlocksList.length > 0) {
|
|
9396
|
-
lines.push("");
|
|
9397
|
-
lines.push(`Blocks: ${activeBlocksList.length} active / ${totalBlocksList.length} total (${fmtTokens(state.stats.tokensCompressed)} tokens compressed)`);
|
|
9398
|
-
for (const b of activeBlocksList) {
|
|
9399
|
-
const topic = b.topic ? `: ${b.topic}` : "";
|
|
9400
|
-
const summaryTok = defaultCountTokens(b.summary || "");
|
|
9401
|
-
const origTok = b.compressedTokens > 0 ? b.compressedTokens : summaryTok;
|
|
9402
|
-
lines.push(` [${b.blockId}] T${b.tier} ${fmtTokens(origTok)}\u2192${fmtTokens(summaryTok)}${topic}`);
|
|
9403
|
-
}
|
|
9404
|
-
} else if (totalBlocksList.length > 0) {
|
|
9405
|
-
lines.push("");
|
|
9406
|
-
lines.push(`Blocks: 0 active / ${totalBlocksList.length} total (${fmtTokens(state.stats.tokensCompressed)} tokens compressed)`);
|
|
9407
|
-
} else {
|
|
9408
|
-
lines.push("");
|
|
9409
|
-
lines.push("Blocks: none (nothing compressed yet)");
|
|
9410
|
-
}
|
|
9411
|
-
lines.push("");
|
|
10233
|
+
const sessionTokens = realUsage?.tokens && realUsage.tokens > 0 ? realUsage.tokens : defaultCountTokens(coreMessages.map((m) => m.text ?? "").join("\n"));
|
|
10234
|
+
const coveredIds = collectCoveredMessageIds(state);
|
|
10235
|
+
const sentTokens = estimateTokens(coreMessages, coveredIds) + systemPromptTokens;
|
|
10236
|
+
const turn = runtime.core.processTurn({ messages: coreMessages, state, config, tokenCount: sentTokens });
|
|
10237
|
+
const versionStr = "0.1.38" ? `billion-context-pi@${"0.1.38"}` : void 0;
|
|
10238
|
+
let text = buildStatusPanel({
|
|
10239
|
+
version: versionStr,
|
|
10240
|
+
tokenCount: sessionTokens,
|
|
10241
|
+
systemPromptTokens,
|
|
10242
|
+
state: turn.state,
|
|
10243
|
+
nudge: turn.nudge,
|
|
10244
|
+
modelContextLimit: config.modelContextLimit,
|
|
10245
|
+
unprunedTokens: coreMessages.reduce((sum, m) => sum + defaultCountTokens(m.text ?? ""), 0)
|
|
10246
|
+
});
|
|
9412
10247
|
const delegateUsage = getDelegateUsage();
|
|
9413
10248
|
if (delegateUsage && delegateUsage.totalTokens > 0) {
|
|
9414
|
-
lines.push("");
|
|
9415
10249
|
const cost = delegateUsage.cost.total;
|
|
9416
10250
|
const costStr = cost > 0 ? ` ($${cost.toFixed(4)})` : "";
|
|
9417
|
-
|
|
9418
|
-
|
|
10251
|
+
text += "\n\n\u2500\u2500 Session delegate usage (excluded from main totals) \u2500\u2500\n";
|
|
10252
|
+
text += `Tokens: ${delegateUsage.input.toLocaleString()} in, ${delegateUsage.output.toLocaleString()} out (${delegateUsage.totalTokens.toLocaleString()} total)${costStr}`;
|
|
9419
10253
|
}
|
|
9420
|
-
|
|
9421
|
-
lines.push("Tag visibility: tags injected to LLM only (deep copy), not persisted in session, not shown in terminal.");
|
|
9422
|
-
return lines.join("\n");
|
|
10254
|
+
return text;
|
|
9423
10255
|
}
|
|
9424
10256
|
|
|
9425
10257
|
// src/system-prompt.ts
|
|
9426
|
-
|
|
10258
|
+
function buildAcpSystemPrompt(prompts) {
|
|
10259
|
+
return `
|
|
9427
10260
|
ACP context management
|
|
9428
10261
|
|
|
9429
10262
|
ACP TAGS
|
|
@@ -9447,7 +10280,7 @@ You have four context-management tools:
|
|
|
9447
10280
|
- search_context \u2014 Search compressed block summaries (and optionally visible messages) by keyword. Use BEFORE decompressing to find the right block. Example: search_context({ query: "auth token refresh" }).
|
|
9448
10281
|
- acp_status \u2014 Context status with compressible ranges. No args = overview + totals. scope:"uncompressed" for range view; add view:"messages" for per-message listing. scope:"compressed" for block details.
|
|
9449
10282
|
|
|
9450
|
-
${
|
|
10283
|
+
${prompts.compressPhilosophy}
|
|
9451
10284
|
|
|
9452
10285
|
WHEN TO COMPRESS
|
|
9453
10286
|
|
|
@@ -9465,7 +10298,7 @@ WHEN NOT TO COMPRESS
|
|
|
9465
10298
|
- Important user messages \u2014 preserve their exact intent, constraints, and acceptance criteria. If a message in the range must stay verbatim, exclude it from the compress range instead of compressing it.
|
|
9466
10299
|
- Protected tool outputs \u2014 hard-excluded from compression ranges, survive intact in visible context.
|
|
9467
10300
|
|
|
9468
|
-
${
|
|
10301
|
+
${prompts.howToCompressRules}
|
|
9469
10302
|
|
|
9470
10303
|
MULTI-TIER COMPRESSION
|
|
9471
10304
|
|
|
@@ -9473,9 +10306,9 @@ Summaries accumulate as the session grows. When tier-1 summaries pile up, the sy
|
|
|
9473
10306
|
|
|
9474
10307
|
To compress blocks: use block IDs as boundaries: compress({ content: [{ startId: "b3", endId: "b15", summary: "..." }] }). This deactivates the consumed blocks and creates a new higher-tier block.
|
|
9475
10308
|
|
|
9476
|
-
${
|
|
10309
|
+
${prompts.tier2DistillRules}
|
|
9477
10310
|
|
|
9478
|
-
${
|
|
10311
|
+
${prompts.tier3CondenseRules}
|
|
9479
10312
|
|
|
9480
10313
|
THE PHILOSOPHY OF DECOMPRESS
|
|
9481
10314
|
|
|
@@ -9485,6 +10318,7 @@ CONTEXT BREAKDOWN
|
|
|
9485
10318
|
|
|
9486
10319
|
When context usage passes a threshold, the system appends a breakdown showing where tokens are spent. Compress the largest ranges first when the current step no longer needs them.
|
|
9487
10320
|
`;
|
|
10321
|
+
}
|
|
9488
10322
|
var ACP_DELEGATE_PROMPT = `
|
|
9489
10323
|
ACP_DELEGATE NOTIFICATIONS
|
|
9490
10324
|
|
|
@@ -9613,7 +10447,7 @@ function wireToolGuardrails(pi, runtime) {
|
|
|
9613
10447
|
|
|
9614
10448
|
// src/update.ts
|
|
9615
10449
|
import { readFile, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
|
|
9616
|
-
import { join as join5, dirname as
|
|
10450
|
+
import { join as join5, dirname as dirname4 } from "path";
|
|
9617
10451
|
import { fileURLToPath } from "url";
|
|
9618
10452
|
import { execFile } from "child_process";
|
|
9619
10453
|
import { homedir as homedir3 } from "os";
|
|
@@ -9646,7 +10480,7 @@ async function readLastCheck() {
|
|
|
9646
10480
|
}
|
|
9647
10481
|
async function writeLastCheck(timestamp) {
|
|
9648
10482
|
try {
|
|
9649
|
-
await mkdir3(
|
|
10483
|
+
await mkdir3(dirname4(THROTTLE_FILE), { recursive: true });
|
|
9650
10484
|
await writeFile3(THROTTLE_FILE, String(timestamp), "utf-8");
|
|
9651
10485
|
} catch {
|
|
9652
10486
|
}
|
|
@@ -9660,20 +10494,20 @@ async function readPackageJson(path4) {
|
|
|
9660
10494
|
}
|
|
9661
10495
|
}
|
|
9662
10496
|
function findNpmRoot(extDir) {
|
|
9663
|
-
let dir =
|
|
10497
|
+
let dir = dirname4(extDir);
|
|
9664
10498
|
for (; ; ) {
|
|
9665
|
-
if (dir.endsWith("node_modules")) return
|
|
9666
|
-
const parent =
|
|
10499
|
+
if (dir.endsWith("node_modules")) return dirname4(dir);
|
|
10500
|
+
const parent = dirname4(dir);
|
|
9667
10501
|
if (parent === dir) return void 0;
|
|
9668
10502
|
dir = parent;
|
|
9669
10503
|
}
|
|
9670
10504
|
}
|
|
9671
10505
|
async function findExtensionDir() {
|
|
9672
|
-
let dir =
|
|
10506
|
+
let dir = dirname4(fileURLToPath(import.meta.url));
|
|
9673
10507
|
for (; ; ) {
|
|
9674
10508
|
const pkg = await readPackageJson(join5(dir, "package.json"));
|
|
9675
10509
|
if (pkg?.name === PACKAGE_NAME) return dir;
|
|
9676
|
-
const parent =
|
|
10510
|
+
const parent = dirname4(dir);
|
|
9677
10511
|
if (parent === dir) return void 0;
|
|
9678
10512
|
dir = parent;
|
|
9679
10513
|
}
|
|
@@ -9722,7 +10556,7 @@ async function checkForUpdate(autoUpdate, notify) {
|
|
|
9722
10556
|
const data = await res.json();
|
|
9723
10557
|
const latest = data.version;
|
|
9724
10558
|
if (!latest) return;
|
|
9725
|
-
const current = runtimeVersion ?? "0.1.
|
|
10559
|
+
const current = runtimeVersion ?? "0.1.38";
|
|
9726
10560
|
const hasUpdate = isNewer(latest, current);
|
|
9727
10561
|
debug.event("update-check", {
|
|
9728
10562
|
current,
|
|
@@ -9758,7 +10592,7 @@ async function getRuntimeVersion() {
|
|
|
9758
10592
|
|
|
9759
10593
|
// src/setup-subagent-tools.ts
|
|
9760
10594
|
import { readFile as readFile2, writeFile as writeFile4, stat, copyFile, rename } from "fs/promises";
|
|
9761
|
-
import { existsSync as
|
|
10595
|
+
import { existsSync as existsSync3 } from "fs";
|
|
9762
10596
|
import { homedir as homedir4 } from "os";
|
|
9763
10597
|
import { join as join6 } from "path";
|
|
9764
10598
|
import { CONFIG_DIR_NAME as CONFIG_DIR_NAME3 } from "@earendil-works/pi-coding-agent";
|
|
@@ -9823,7 +10657,7 @@ async function ensureSubagentAcpTools(settingsPath) {
|
|
|
9823
10657
|
return { path: path4, action: "skipped", reason: "all builtin agents already have ACP tools" };
|
|
9824
10658
|
}
|
|
9825
10659
|
const backupPath = `${path4}.acp-bak`;
|
|
9826
|
-
if (!
|
|
10660
|
+
if (!existsSync3(backupPath)) {
|
|
9827
10661
|
try {
|
|
9828
10662
|
await copyFile(path4, backupPath);
|
|
9829
10663
|
} catch {
|
|
@@ -9923,7 +10757,9 @@ var KNOWN = /* @__PURE__ */ new Set([
|
|
|
9923
10757
|
"toolOutputMaxBytes",
|
|
9924
10758
|
"delegate",
|
|
9925
10759
|
"compress",
|
|
9926
|
-
"displayUsage"
|
|
10760
|
+
"displayUsage",
|
|
10761
|
+
"prompts",
|
|
10762
|
+
"acknowledgePromptsRisk"
|
|
9927
10763
|
]);
|
|
9928
10764
|
function pickKnown(parsed) {
|
|
9929
10765
|
const out = {};
|
|
@@ -9971,7 +10807,7 @@ function wireSessionLifecycle(pi, runtime) {
|
|
|
9971
10807
|
resetDelegateUsage();
|
|
9972
10808
|
setDelegateDisplayUsage("separate");
|
|
9973
10809
|
const sid = ctx.sessionManager.getSessionId();
|
|
9974
|
-
logInfo("session", { event: "start", sid, cwd: ctx.cwd, debug: runtime.adapter.debug ?? null, version: true ? "0.1.
|
|
10810
|
+
logInfo("session", { event: "start", sid, cwd: ctx.cwd, debug: runtime.adapter.debug ?? null, version: true ? "0.1.38" : null });
|
|
9975
10811
|
try {
|
|
9976
10812
|
const user = await loadUserConfig(ctx.cwd);
|
|
9977
10813
|
runtime.setAdapter(applyUserConfig(runtime.adapter, user));
|
|
@@ -9980,6 +10816,12 @@ function wireSessionLifecycle(pi, runtime) {
|
|
|
9980
10816
|
} catch (e) {
|
|
9981
10817
|
logThrow("config", e, { sid, phase: "session_start" });
|
|
9982
10818
|
}
|
|
10819
|
+
try {
|
|
10820
|
+
runtime.setPrompts(resolvePrompts(runtime.adapter.prompts, { acknowledgeRisk: runtime.adapter.acknowledgePromptsRisk === true }));
|
|
10821
|
+
} catch (e) {
|
|
10822
|
+
logWarn("config", { event: "prompts-resolve-failed", error: e instanceof Error ? e.message : String(e) });
|
|
10823
|
+
runtime.setPrompts(defaultPrompts);
|
|
10824
|
+
}
|
|
9983
10825
|
if (resolveDelegate(runtime.adapter).enabled) {
|
|
9984
10826
|
pi.registerTool(makeDelegateTool(pi));
|
|
9985
10827
|
pi.registerTool(makeDelegateWaitTool(pi));
|
|
@@ -10005,17 +10847,17 @@ function wireContextTransform(pi, runtime) {
|
|
|
10005
10847
|
const config = runtime.configFor(ctx);
|
|
10006
10848
|
const coveredIds = collectCoveredMessageIds(state);
|
|
10007
10849
|
const realUsage = ctx.getContextUsage?.();
|
|
10008
|
-
const
|
|
10009
|
-
const
|
|
10850
|
+
const systemPromptText = getSystemPromptText(ctx);
|
|
10851
|
+
const systemPromptTokens = systemPromptText ? defaultCountTokens(systemPromptText) : 0;
|
|
10852
|
+
const sentTokens = estimateTokens(coreMessages, coveredIds) + systemPromptTokens;
|
|
10853
|
+
const tokenCount = sentTokens;
|
|
10010
10854
|
debug.event("context-in", {
|
|
10011
10855
|
sid,
|
|
10012
10856
|
eventMsgs: event.messages?.length ?? 0,
|
|
10013
10857
|
entries: entries.length,
|
|
10014
10858
|
coreMsgs: coreMessages.length,
|
|
10015
10859
|
tokenCount,
|
|
10016
|
-
|
|
10017
|
-
realTokens: realUsage?.tokens ?? null,
|
|
10018
|
-
realPercent: realUsage?.percent ?? null,
|
|
10860
|
+
sessionTokens: realUsage?.tokens ?? null,
|
|
10019
10861
|
limit: config.modelContextLimit,
|
|
10020
10862
|
blocksBefore: state.blocks.length,
|
|
10021
10863
|
activeBefore: state.blocks.filter((b) => b.active).length
|
|
@@ -10040,7 +10882,7 @@ function wireContextTransform(pi, runtime) {
|
|
|
10040
10882
|
prunedMsgs: coreMessages.length - turn.messages.length + turn.messages.filter((m) => m.id.startsWith("acp_summary")).length,
|
|
10041
10883
|
nudgeShouldInject: turn.nudge?.shouldInject ?? false,
|
|
10042
10884
|
nudgeReason: turn.nudge?.reason ?? null,
|
|
10043
|
-
nudgeVoice: turn.nudge ? renderNudgeText(turn.nudge).voice : null,
|
|
10885
|
+
nudgeVoice: turn.nudge ? renderNudgeText(turn.nudge, runtime.prompts).voice : null,
|
|
10044
10886
|
nudgePct: turn.nudge ? Math.round(turn.nudge.contextUsage * 100) : null,
|
|
10045
10887
|
nudgeTier: turn.nudge?.tier ?? null,
|
|
10046
10888
|
nudgeCompressibleCount: turn.nudge?.compressibleRanges.length ?? 0,
|
|
@@ -10054,11 +10896,12 @@ function wireContextTransform(pi, runtime) {
|
|
|
10054
10896
|
const debugOn2 = debug.enabled;
|
|
10055
10897
|
if (turn.nudge?.shouldInject) {
|
|
10056
10898
|
const emergency = turn.nudge.breakdown?.emergencyOverride === 1;
|
|
10899
|
+
turn.nudge.compressibleRanges = viableRanges(turn.nudge.compressibleRanges);
|
|
10057
10900
|
const turnKey = lastUserMessageId(entries) ?? sid;
|
|
10058
10901
|
const alreadyShown = !emergency && runtime.nudgeShownFor(turnKey);
|
|
10059
10902
|
if (!alreadyShown) {
|
|
10060
|
-
rebuilt.push(nudgeMessage(turn.nudge, turn.state.blocks.filter((b) => b.active)));
|
|
10061
|
-
const rendered = renderNudgeText(turn.nudge);
|
|
10903
|
+
rebuilt.push(nudgeMessage(turn.nudge, turn.state.blocks.filter((b) => b.active), runtime.prompts));
|
|
10904
|
+
const rendered = renderNudgeText(turn.nudge, runtime.prompts);
|
|
10062
10905
|
const top = [...turn.nudge.compressibleRanges].sort((a, b) => b.tokens - a.tokens)[0];
|
|
10063
10906
|
const example = top ? `
|
|
10064
10907
|
|
|
@@ -10092,8 +10935,9 @@ ${rendered.text}${example}`);
|
|
|
10092
10935
|
function wireSystemPrompt(pi, runtime) {
|
|
10093
10936
|
pi.on("before_agent_start", (event) => {
|
|
10094
10937
|
const delegate = runtime.adapter.delegate !== false;
|
|
10095
|
-
const
|
|
10096
|
-
|
|
10938
|
+
const acp = buildAcpSystemPrompt(runtime.prompts);
|
|
10939
|
+
const prompt = delegate ? `${acp}
|
|
10940
|
+
${ACP_DELEGATE_PROMPT}` : acp;
|
|
10097
10941
|
return { systemPrompt: formatSystemPromptForEvent(event.systemPrompt, prompt) };
|
|
10098
10942
|
});
|
|
10099
10943
|
}
|
|
@@ -10109,8 +10953,8 @@ function collectOriginals(entries) {
|
|
|
10109
10953
|
}
|
|
10110
10954
|
return map;
|
|
10111
10955
|
}
|
|
10112
|
-
function nudgeMessage(nudge, blocks) {
|
|
10113
|
-
const rendered = renderNudgeText(nudge);
|
|
10956
|
+
function nudgeMessage(nudge, blocks, prompts) {
|
|
10957
|
+
const rendered = renderNudgeText(nudge, prompts);
|
|
10114
10958
|
const lines = [rendered.text];
|
|
10115
10959
|
if (blocks.length > 0) {
|
|
10116
10960
|
const totalSummary = blocks.reduce((s, b) => s + Math.ceil((b.summary || "").length / 4), 0);
|