greprag 5.74.18 → 5.74.19
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.
|
@@ -59,6 +59,7 @@ exports.classifyContent = classifyContent;
|
|
|
59
59
|
exports.crushField = crushField;
|
|
60
60
|
exports.crushMessages = crushMessages;
|
|
61
61
|
const crush_1 = require("./crush");
|
|
62
|
+
const crypto_1 = require("crypto");
|
|
62
63
|
// ---------- CCR marker (inlined — see header: ccr-store.ts drags sql.js) ------
|
|
63
64
|
/** First 16 hex of sha256 — must match ccr-store.ts CCR_MARKER_HASH_LEN. */
|
|
64
65
|
const CCR_MARKER_HASH_LEN = 16;
|
|
@@ -157,13 +158,19 @@ function runEngine(type, content) {
|
|
|
157
158
|
* passthrough, and token-validate-or-revert. The stash side effect fires ONLY
|
|
158
159
|
* on a real crush (after validation passes), so a reverted/skipped field is
|
|
159
160
|
* never persisted. */
|
|
160
|
-
function crushField(content, stash, thresholdBytes = exports.DEFAULT_THRESHOLD_BYTES) {
|
|
161
|
+
function crushField(content, stash, thresholdBytes = exports.DEFAULT_THRESHOLD_BYTES, seenHashes) {
|
|
161
162
|
if (typeof content !== 'string')
|
|
162
163
|
return null;
|
|
163
164
|
if (CCR_MARKER_RE.test(content))
|
|
164
165
|
return null; // idempotent
|
|
165
166
|
if (Buffer.byteLength(content, 'utf8') < thresholdBytes)
|
|
166
167
|
return null; // below floor
|
|
168
|
+
// Compute the stash hash first — if we've already stashed this exact
|
|
169
|
+
// content (e.g. it was retrieved via greprag retrieve), skip re-crushing
|
|
170
|
+
// to break the infinite marker→retrieve→same-marker loop.
|
|
171
|
+
const contentHash = (0, crypto_1.createHash)('sha256').update(content, 'utf8').digest('hex').slice(0, CCR_MARKER_HASH_LEN);
|
|
172
|
+
if (seenHashes?.has(contentHash))
|
|
173
|
+
return null;
|
|
167
174
|
const type = classifyContent(content);
|
|
168
175
|
const result = runEngine(type, content);
|
|
169
176
|
if (result.stats.passthrough)
|
|
@@ -177,6 +184,7 @@ function crushField(content, stash, thresholdBytes = exports.DEFAULT_THRESHOLD_B
|
|
|
177
184
|
if (finalTokens >= (0, crush_1.estimateTokens)(content))
|
|
178
185
|
return null;
|
|
179
186
|
const hash = stash(content, type);
|
|
187
|
+
seenHashes?.add(hash);
|
|
180
188
|
return `${crushed}\n${ccrMarker(hash)}`;
|
|
181
189
|
}
|
|
182
190
|
// ---------- Message-array walker ----------------------------------------------
|
|
@@ -203,7 +211,7 @@ function resolveRetrieveCommand(p) {
|
|
|
203
211
|
* ORIGINAL content, and crush is deterministic, so the reused value is exactly
|
|
204
212
|
* what crushField would recompute; the original was already stashed on the
|
|
205
213
|
* first (miss) crush, so skipping re-stash is safe for retrieve. */
|
|
206
|
-
function crushAndApply(obj, key, stash, thresholdBytes, stats, memo) {
|
|
214
|
+
function crushAndApply(obj, key, stash, thresholdBytes, stats, memo, seenHashes) {
|
|
207
215
|
const content = obj[key];
|
|
208
216
|
if (typeof content !== 'string')
|
|
209
217
|
return;
|
|
@@ -225,13 +233,13 @@ function crushAndApply(obj, key, stash, thresholdBytes, stats, memo) {
|
|
|
225
233
|
stats.memoHits++;
|
|
226
234
|
}
|
|
227
235
|
else {
|
|
228
|
-
replacement = crushField(content, stash, thresholdBytes);
|
|
236
|
+
replacement = crushField(content, stash, thresholdBytes, seenHashes);
|
|
229
237
|
stats.crushFieldCalls++;
|
|
230
238
|
memo.set(k, replacement);
|
|
231
239
|
}
|
|
232
240
|
}
|
|
233
241
|
else {
|
|
234
|
-
replacement = crushField(content, stash, thresholdBytes);
|
|
242
|
+
replacement = crushField(content, stash, thresholdBytes, seenHashes);
|
|
235
243
|
stats.crushFieldCalls++;
|
|
236
244
|
}
|
|
237
245
|
if (replacement === null) {
|
|
@@ -247,30 +255,18 @@ function crushAndApply(obj, key, stash, thresholdBytes, stats, memo) {
|
|
|
247
255
|
* - text part → `.text`
|
|
248
256
|
* - tool part → `.state.output` (only when state.status === 'completed';
|
|
249
257
|
* an 'error' state's `.error` is precious — left alone). */
|
|
250
|
-
function processPart(part, stash, thresholdBytes, stats, memo) {
|
|
258
|
+
function processPart(part, stash, thresholdBytes, stats, memo, seenHashes) {
|
|
251
259
|
if (!part || typeof part !== 'object')
|
|
252
260
|
return;
|
|
253
261
|
const p = part;
|
|
254
262
|
if (p.type === 'text' && typeof p.text === 'string') {
|
|
255
|
-
crushAndApply(p, 'text', stash, thresholdBytes, stats, memo);
|
|
263
|
+
crushAndApply(p, 'text', stash, thresholdBytes, stats, memo, seenHashes);
|
|
256
264
|
return;
|
|
257
265
|
}
|
|
258
266
|
if (p.type === 'tool' && p.state && typeof p.state === 'object') {
|
|
259
267
|
const state = p.state;
|
|
260
268
|
if (state.status === 'completed' && typeof state.output === 'string') {
|
|
261
|
-
|
|
262
|
-
// already stashed once; crushing it again creates an infinite
|
|
263
|
-
// marker→retrieve→same marker loop.
|
|
264
|
-
// The tool name / command may live on p.input.command (tool_use
|
|
265
|
-
// part) or on p.name / p.tool (tool_result part) depending on
|
|
266
|
-
// the opencode message format version.
|
|
267
|
-
const cmd = resolveRetrieveCommand(p);
|
|
268
|
-
if (cmd && cmd.includes('greprag retrieve')) {
|
|
269
|
-
stats.partsSkipped++;
|
|
270
|
-
}
|
|
271
|
-
else {
|
|
272
|
-
crushAndApply(state, 'output', stash, thresholdBytes, stats, memo);
|
|
273
|
-
}
|
|
269
|
+
crushAndApply(state, 'output', stash, thresholdBytes, stats, memo, seenHashes);
|
|
274
270
|
}
|
|
275
271
|
}
|
|
276
272
|
}
|
|
@@ -285,6 +281,7 @@ function crushMessages(messages, opts) {
|
|
|
285
281
|
crushFieldCalls: 0, memoHits: 0,
|
|
286
282
|
};
|
|
287
283
|
const thresholdBytes = opts.thresholdBytes ?? exports.DEFAULT_THRESHOLD_BYTES;
|
|
284
|
+
const seenHashes = opts.seenHashes ?? new Set();
|
|
288
285
|
if (!Array.isArray(messages))
|
|
289
286
|
return stats;
|
|
290
287
|
for (const msg of messages) {
|
|
@@ -293,7 +290,7 @@ function crushMessages(messages, opts) {
|
|
|
293
290
|
continue;
|
|
294
291
|
for (const part of parts) {
|
|
295
292
|
try {
|
|
296
|
-
processPart(part, opts.stash, thresholdBytes, stats, opts.memo);
|
|
293
|
+
processPart(part, opts.stash, thresholdBytes, stats, opts.memo, seenHashes);
|
|
297
294
|
}
|
|
298
295
|
catch {
|
|
299
296
|
// per-part swallow — a crush/stash failure must never break the turn
|
|
@@ -1600,6 +1600,7 @@ function crushCode(content, config = {}) {
|
|
|
1600
1600
|
}
|
|
1601
1601
|
|
|
1602
1602
|
// ../greprag/packages/cli/src/opencode-plugin-crush.ts
|
|
1603
|
+
var import_crypto = require("crypto");
|
|
1603
1604
|
var CCR_MARKER_HASH_LEN = 16;
|
|
1604
1605
|
function ccrMarker(hash) {
|
|
1605
1606
|
return `<<ccr:${hash.slice(0, CCR_MARKER_HASH_LEN)}>>`;
|
|
@@ -1645,13 +1646,16 @@ function runEngine(type, content) {
|
|
|
1645
1646
|
return crushLog(content);
|
|
1646
1647
|
}
|
|
1647
1648
|
}
|
|
1648
|
-
function crushField(content, stash, thresholdBytes = DEFAULT_THRESHOLD_BYTES) {
|
|
1649
|
+
function crushField(content, stash, thresholdBytes = DEFAULT_THRESHOLD_BYTES, seenHashes) {
|
|
1649
1650
|
if (typeof content !== "string")
|
|
1650
1651
|
return null;
|
|
1651
1652
|
if (CCR_MARKER_RE.test(content))
|
|
1652
1653
|
return null;
|
|
1653
1654
|
if (Buffer.byteLength(content, "utf8") < thresholdBytes)
|
|
1654
1655
|
return null;
|
|
1656
|
+
const contentHash = (0, import_crypto.createHash)("sha256").update(content, "utf8").digest("hex").slice(0, CCR_MARKER_HASH_LEN);
|
|
1657
|
+
if (seenHashes?.has(contentHash))
|
|
1658
|
+
return null;
|
|
1655
1659
|
const type = classifyContent(content);
|
|
1656
1660
|
const result = runEngine(type, content);
|
|
1657
1661
|
if (result.stats.passthrough)
|
|
@@ -1661,19 +1665,11 @@ function crushField(content, stash, thresholdBytes = DEFAULT_THRESHOLD_BYTES) {
|
|
|
1661
1665
|
if (finalTokens >= estimateTokens(content))
|
|
1662
1666
|
return null;
|
|
1663
1667
|
const hash = stash(content, type);
|
|
1668
|
+
seenHashes?.add(hash);
|
|
1664
1669
|
return `${crushed}
|
|
1665
1670
|
${ccrMarker(hash)}`;
|
|
1666
1671
|
}
|
|
1667
|
-
function
|
|
1668
|
-
const input = p.input;
|
|
1669
|
-
if (input && typeof input.command === "string")
|
|
1670
|
-
return input.command;
|
|
1671
|
-
const toolName = typeof p.name === "string" && p.name || typeof p.tool === "string" && p.tool || typeof p.toolName === "string" && p.toolName;
|
|
1672
|
-
if (toolName)
|
|
1673
|
-
return toolName;
|
|
1674
|
-
return "";
|
|
1675
|
-
}
|
|
1676
|
-
function crushAndApply(obj, key, stash, thresholdBytes, stats, memo) {
|
|
1672
|
+
function crushAndApply(obj, key, stash, thresholdBytes, stats, memo, seenHashes) {
|
|
1677
1673
|
const content = obj[key];
|
|
1678
1674
|
if (typeof content !== "string")
|
|
1679
1675
|
return;
|
|
@@ -1694,12 +1690,12 @@ function crushAndApply(obj, key, stash, thresholdBytes, stats, memo) {
|
|
|
1694
1690
|
replacement = memo.get(k) ?? null;
|
|
1695
1691
|
stats.memoHits++;
|
|
1696
1692
|
} else {
|
|
1697
|
-
replacement = crushField(content, stash, thresholdBytes);
|
|
1693
|
+
replacement = crushField(content, stash, thresholdBytes, seenHashes);
|
|
1698
1694
|
stats.crushFieldCalls++;
|
|
1699
1695
|
memo.set(k, replacement);
|
|
1700
1696
|
}
|
|
1701
1697
|
} else {
|
|
1702
|
-
replacement = crushField(content, stash, thresholdBytes);
|
|
1698
|
+
replacement = crushField(content, stash, thresholdBytes, seenHashes);
|
|
1703
1699
|
stats.crushFieldCalls++;
|
|
1704
1700
|
}
|
|
1705
1701
|
if (replacement === null) {
|
|
@@ -1711,23 +1707,18 @@ function crushAndApply(obj, key, stash, thresholdBytes, stats, memo) {
|
|
|
1711
1707
|
stats.partsCrushed++;
|
|
1712
1708
|
stats.bytesAfter += Buffer.byteLength(replacement, "utf8");
|
|
1713
1709
|
}
|
|
1714
|
-
function processPart(part, stash, thresholdBytes, stats, memo) {
|
|
1710
|
+
function processPart(part, stash, thresholdBytes, stats, memo, seenHashes) {
|
|
1715
1711
|
if (!part || typeof part !== "object")
|
|
1716
1712
|
return;
|
|
1717
1713
|
const p = part;
|
|
1718
1714
|
if (p.type === "text" && typeof p.text === "string") {
|
|
1719
|
-
crushAndApply(p, "text", stash, thresholdBytes, stats, memo);
|
|
1715
|
+
crushAndApply(p, "text", stash, thresholdBytes, stats, memo, seenHashes);
|
|
1720
1716
|
return;
|
|
1721
1717
|
}
|
|
1722
1718
|
if (p.type === "tool" && p.state && typeof p.state === "object") {
|
|
1723
1719
|
const state = p.state;
|
|
1724
1720
|
if (state.status === "completed" && typeof state.output === "string") {
|
|
1725
|
-
|
|
1726
|
-
if (cmd && cmd.includes("greprag retrieve")) {
|
|
1727
|
-
stats.partsSkipped++;
|
|
1728
|
-
} else {
|
|
1729
|
-
crushAndApply(state, "output", stash, thresholdBytes, stats, memo);
|
|
1730
|
-
}
|
|
1721
|
+
crushAndApply(state, "output", stash, thresholdBytes, stats, memo, seenHashes);
|
|
1731
1722
|
}
|
|
1732
1723
|
}
|
|
1733
1724
|
}
|
|
@@ -1742,6 +1733,7 @@ function crushMessages(messages, opts) {
|
|
|
1742
1733
|
memoHits: 0
|
|
1743
1734
|
};
|
|
1744
1735
|
const thresholdBytes = opts.thresholdBytes ?? DEFAULT_THRESHOLD_BYTES;
|
|
1736
|
+
const seenHashes = opts.seenHashes ?? /* @__PURE__ */ new Set();
|
|
1745
1737
|
if (!Array.isArray(messages))
|
|
1746
1738
|
return stats;
|
|
1747
1739
|
for (const msg of messages) {
|
|
@@ -1750,7 +1742,7 @@ function crushMessages(messages, opts) {
|
|
|
1750
1742
|
continue;
|
|
1751
1743
|
for (const part of parts) {
|
|
1752
1744
|
try {
|
|
1753
|
-
processPart(part, opts.stash, thresholdBytes, stats, opts.memo);
|
|
1745
|
+
processPart(part, opts.stash, thresholdBytes, stats, opts.memo, seenHashes);
|
|
1754
1746
|
} catch {
|
|
1755
1747
|
stats.partsSkipped++;
|
|
1756
1748
|
}
|
|
@@ -4498,6 +4490,7 @@ function grepragBinCached() {
|
|
|
4498
4490
|
return _cachedGrepragBin;
|
|
4499
4491
|
}
|
|
4500
4492
|
var _crushShapeLogged = false;
|
|
4493
|
+
var _crushSeenHashes;
|
|
4501
4494
|
function inlineCrushEnabled() {
|
|
4502
4495
|
const v = process.env.GREPRAG_OPENCODE_CRUSH;
|
|
4503
4496
|
return v === "true" || v === "1";
|
|
@@ -4740,7 +4733,8 @@ ALWAYS pass \`--from-session ${sid82}\` when you run \`greprag send\`, so recipi
|
|
|
4740
4733
|
const thresholdEnv = parseInt(process.env.GREPRAG_OPENCODE_CRUSH_MIN_BYTES || "", 10);
|
|
4741
4734
|
const stats = crushMessages(msgs, {
|
|
4742
4735
|
stash: makeCcrStash(),
|
|
4743
|
-
thresholdBytes: Number.isFinite(thresholdEnv) && thresholdEnv > 0 ? thresholdEnv : void 0
|
|
4736
|
+
thresholdBytes: Number.isFinite(thresholdEnv) && thresholdEnv > 0 ? thresholdEnv : void 0,
|
|
4737
|
+
seenHashes: _crushSeenHashes ?? (_crushSeenHashes = /* @__PURE__ */ new Set())
|
|
4744
4738
|
});
|
|
4745
4739
|
if (stats.partsCrushed > 0 || stats.partsReverted > 0) {
|
|
4746
4740
|
dlog(
|
package/dist/opencode-plugin.js
CHANGED
|
@@ -792,6 +792,9 @@ function grepragBinCached() {
|
|
|
792
792
|
* crusher's assumptions. See the chip report: live propagation + shape are
|
|
793
793
|
* the two things only a real opencode restart can verify. */
|
|
794
794
|
let _crushShapeLogged = false;
|
|
795
|
+
/** Per-session set of hashes already stashed — prevents re-crushing content
|
|
796
|
+
* that was already compressed once (e.g. retrieved via greprag retrieve). */
|
|
797
|
+
let _crushSeenHashes;
|
|
795
798
|
/** GREPRAG_OPENCODE_CRUSH gate — opt-in, default OFF for safe rollout. */
|
|
796
799
|
function inlineCrushEnabled() {
|
|
797
800
|
const v = process.env.GREPRAG_OPENCODE_CRUSH;
|
|
@@ -1115,6 +1118,7 @@ const GrepRAGMemoryPlugin = async (ctx) => {
|
|
|
1115
1118
|
const stats = (0, opencode_plugin_crush_1.crushMessages)(msgs, {
|
|
1116
1119
|
stash: makeCcrStash(),
|
|
1117
1120
|
thresholdBytes: Number.isFinite(thresholdEnv) && thresholdEnv > 0 ? thresholdEnv : undefined,
|
|
1121
|
+
seenHashes: _crushSeenHashes ?? (_crushSeenHashes = new Set()),
|
|
1118
1122
|
});
|
|
1119
1123
|
if (stats.partsCrushed > 0 || stats.partsReverted > 0) {
|
|
1120
1124
|
dlog(`messages.transform: crushed=${stats.partsCrushed} reverted=${stats.partsReverted} ` +
|